Dapfor .Net Grid Introduction

Part1: Data Types
Part2: Data Binding
Part3: Event-driven model
Part4: Data formatting and presentation
Part5: Threadsafety
Part6: Threadsafe BindingList
Part7: Appearance and painting
Part8: Cell highlighting
Part9: Data filtering
Part10: Data sorting
Part11: Data grouping
Part12: Headers and columns
Part13: Editors
Part14: Performance. Practical recommendations
Part15: Real-time blotter
Part16: Currency converter
Part17: Instrument browser
Part18: Order book
Part19: Basket Viewer


.Net Grid Tutorial Part11: Data grouping

Grouping means combining rows in groups by their values. During grouping one or multiple special rows with Row.IsGroup flag are created. They combine regular rows with equal values for the grouping field, which is set by the column.

The programming interface is very simple and clear. Grouping information is set in the header. For this purpose it  is sufficient to set Column.Grouped flag to true for the required column. Column.Visible=false property can be used to hide a column on the panel of visible columns.

The grid supports grouping by multiple columns. This requires sequential setting of Column.Grouped flag  for required columns. To determine columns used for grouping, Header class provides Header.GroupedColumns property  that can be used to learn all information of columns used for grouping. Header.GroupedColumns.Clear() method  can be used to cancel grouping, and to change sequence of columns used for grouping, Column.GroupIndex = required_index can be called.

Data can be also grouped simultaneously in multiple headers on different hierarchy levels. As has been shown above, the grid  provides a simple grouping management interface. Let’s see what happens to data when it is added to a grouped grid and how it is changed in real time.

Adding and removing data

When new data is added, the grid checks whether previously created group exists for newly added data. If a group exists,  data is added as child data for this group, and if such group doesn’t exist, it is created. When rows are deleted, the grid checks  whether the group contains at least one row. If the group becomes empty, it is automatically removed from the grid.

Modifying data

When data is modified, the programmer has to inform the grid about it using Row.Update() method. When event-driven model is used, the grid can receive and process notifications from INotifyPropertyChanged or  IBindingList interfaces. In such scenario, when the grid receives notification and performs multi-threading synchronization (if needed), the grid automatically calls Row.Update() method for a corresponding row.  After that, the grid checks whether the row corresponds to the group where it is located and moves this row to a new group, if required.  Of course, it checks for the need to remove the old group and create a new one.

As can be seen from above, with event-driven model, the business logic code becomes trivial. As an example, let’s take  an employee moving from one department to another.


class Employee : INotifyPropertyChanged
{
private readonly string _name;
private string _department;
private readonly string _position;

public Employee(string name, string department, string position)
{
_name = name;
_department = department;
_position = position;
}

public string Name
{
get { return _name; }
}

public string Department
{
get { return _department; }
set
{
if(_department != value)
{
_department = value;
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Department"));
}
}
}
}

public string Position
{
get { return _position; }
}

public event PropertyChangedEventHandler PropertyChanged;
}

//Create some employees and bind grid to the collection:
List<Employee> employees = new List<Employee>();
employees.Add(new Employee("John Smith", "Department of climate", "Director IT Security"));
employees.Add(new Employee("Walter Gaber", "Department of energy", "Divisional Support Unit Officer "));
employees.Add(new Employee("James Nickolls", "Department of energy", "Scientific Programer"));
employees.Add(new Employee("Cora Cornell", "Department of climate", "Head of Department"));
employees.Add(new Employee("Andrew Jebb", "Department of climate", "Divisional Support Unit Officer"));
employees.Add(new Employee("Julia Treace", "Department of energy", "Operations Manager"));
employees.Add(new Employee("Francis Maunton", "Department of climate", "Climate Change Coordinator"));

grid.DataSource = employees;

Let’s group data by departments.


grid.Headers[0]["Department"].Grouped = true;
grid.Headers[0]["Department"].Visible = false;

Now let’s transfer the employee to a new department.


//Transfer Cora Cornell to a new department:
employees[3].Department = "Department of education";
Grouping in event-driven model

Data grouping and filtering

The grid enables joint data grouping and filtering. Filtering can be performed with any method described in .Net Grid tutorial (Part6: Data filtering). If filtering causes all rows in a group to become invisible, the grou will become invisible as well. If event-driven programming model is used, the business logic remains unchanged.

Data sorting and grouping

It is possible to perform data sorting and grouping at the same time using either regular sorting described in .Net Grid tutorial (Part7: Data sorting) and docked rows or ICustomSort interface. It is also possible to sort data by columns used for grouping.

Appearance

The programmer can easily control appearance of headers with grouped columns and groups that contain data.


grid.PaintGroupPanel += delegate(object sender, PaintGroupPanelEventArgs e)
{
e.Appearance.BackColor = ControlPaint.Light(Color.LightYellow);
e.Appearance.GradientEndBackColor = Color.LightYellow;
};

grid.PaintColumnCaption += delegate(object sender, PaintColumnCaptionEventArgs e)
{
if(e.Column.Grouped)
{
e.Font = new Font(e.Font, FontStyle.Bold);
e.Appearance.ForeColor = Color.DarkRed;
e.Appearance.BackColor = Color.Orange;
e.Appearance.GradientEndBackColor = Color.LightSalmon;
e.Image = Resources.star_yellow;
e.ImageSettings.Alignment = ContentAlignment.MiddleLeft;
e.ImageSettings.Padding = new Padding(4, 0, 0, 0);
}
};

grid.PaintGroupRow += delegate(object sender, PaintGroupRowEventArgs e)
{
string departament = (string) e.Row[e.Column.Id].Value;
e.Text = string.Format("Any custom text here: {0}", departament.ToUpper());

e.Font = new Font(e.Font, FontStyle.Bold | FontStyle.Italic);
e.Appearance.ForeColor = Color.ForestGreen;
e.Appearance.BackColor = ControlPaint.LightLight(Color.LightGreen);
e.Appearance.GradientEndBackColor = Color.LightGreen;
e.Appearance.GradientDirection = GradientDirection.Vertical;
e.Appearance.GradientEnabled = true;
e.Image = Resources.bullet_triangle_blue;
e.ImageSettings.Alignment = ContentAlignment.MiddleLeft;
e.ImageSettings.Padding = new Padding(4, 0, 0, 0);
};
Appearance of grouped rows

Performance

Grid implementation makes it the best performing graphical component. It has high speed of data grouping and is capable to regroup dynamically changing data. For example, a grid containing 10 000 rows groups them in less than 140 milliseconds and can process more than 15 000 data regrouping operations per second.

Multi-threading

The grid implements complete thread protection. When it gets notifications from INotifyPropertyChanged and IBindingList interfaces, the grid systematically synchronizes the calling thread with GUI thread. It means that data object values can be changed in any thread thus making application programming much easier.