GridControl tutorial part10: Data grouping

Grouping means combining rows in groups by values. During grouping one or multiple special rows with Row.IsGroup flag are created. They combine regular rows with equal values of 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.GroupedColumnsproperty that can be used to get all information of columns used for grouping. Header.GroupedColumnCollection.Clear() method can be used to cancel grouping, while Column.GroupIndex = required_index can be called to change sequence of columns used for grouping.

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 a notification and performs multi-threading synchronization (if needed), the grid automatically calls Row.Update() method for the 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 also checks whether it is necessary to remove the old group and to create a new one.

As shown 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# 
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.ItemsSource = employees;

Let’s group data by departments.

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

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

C# 
//Transfer Cora Cornell 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 Wpf Grid tutorial (Part11: 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 Wpf Grid tutorial (Part9: 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.

XAML 
<Window x:Class="TestApplication.Window9"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Dapfor Wpf GridControl" 
        Icon="/TestApplication;component/Images/dapfor.ico" 
        xmlns:df="clr-namespace:Dapfor.Wpf.Controls;assembly=Dapfor.Wpf"
        Height="280" Width="400" >

    <!-- Declare resources -->
    <Window.Resources>
        <!-- Use a custom ControlTemplate for the 'Department' column -->
        <BitmapImage x:Key="Image" UriSource="/Images/dapfor.ico" DecodePixelWidth="16" DecodePixelHeight="16" />
        <DataTemplate x:Key="ColumnTemplate">
            <StackPanel Orientation="Horizontal" Height="16" HorizontalAlignment="Center">
                <Image Source="{StaticResource Image}"/>
                <TextBlock x:Name="columnText" Text="{Binding Path=Title}" Margin="4,0,4,0" />
                <Image Source="{StaticResource Image}"/>
            </StackPanel>
        </DataTemplate>

        <!-- Use a custom DataTemplate for the group rows -->
        <DataTemplate x:Key="GroupRowTemplate">
            <StackPanel Orientation="Horizontal" Height="16" HorizontalAlignment="Stretch" Background="Orange">
                <Image Source="{StaticResource Image}" Margin="4, 0, 4, 0"/>
                <TextBlock Text="{Binding Path=GroupValue}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>

    <!-- Create a GridControl -->
    <df:GridControl x:Name="grid" TemplateGroupRow="{StaticResource GroupRowTemplate}">
        <df:GridControl.Headers>
            <df:Header ScrollType="Stretch" VisibleGroupPanel="True" >
                <df:Header.Columns>
                    <df:Column Id="Name" Title="Name" Width="100" CellHorizontalAlignment="Left" CellMargin="4,0,0,0" />
                    <df:Column Id="Department" Title="Department" Width="100" CellHorizontalAlignment="Left" CellMargin="4,0,0,0" ColumnTemplate="{StaticResource ColumnTemplate}" />
                    <df:Column Id="Position" Title="Position" Width="100" CellHorizontalAlignment="Left" CellMargin="4,0,0,0" />
                </df:Header.Columns>
            </df:Header>
        </df:GridControl.Headers>
    </df:GridControl>
</Window>
Performance

Grid implementation makes it the best performing graphical component. It has high data grouping speed 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 over 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.

Back to Wpf GridControl tutorial