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;
}
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.
employees[3].Department = "Department of education";
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);
};
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.