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.

C# Copy imageCopy
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; }
            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.

C# Copy imageCopy
grid.Headers[0]["Department"].Grouped = true;
grid.Headers[0]["Department"].Visible = false;

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

C# Copy imageCopy
//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 (Part9: Data filtering). If filtering causes all rows in a group to become invisible, the group 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 (Part10: Data sorting) and docked rows or ICustomSort interface. It is also possible to sort data by columns used for grouping.


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

C# Copy imageCopy
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)
        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


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.


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.