The ICollectionView interface includes support for grouping, which means you can easily create hierarchical views of your data. For example, to group the customers in the sample above by country and city you would simply change the code above as follows:
List<Customer> list = GetCustomerList();
PagedCollectionView view = new PagedCollectionView(list);
using (view.DeferRefresh())
{
view.GroupDescriptions.Clear();
view.GroupDescriptions.Add(new PropertyGroupDescription("Country"));
view.GroupDescriptions.Add(new PropertyGroupDescription("Active"));
}
_flexGrid.ItemsSource = view;
The "using (view.DeferRefresh())" statement is optional. It improves performance by suspending notifications from the data source until all the groups have been set up.
The image below shows the result:
The data items are grouped by country and by their active state. Users can click the icons on group headers to collapse or expand the groups, as they would do with a TreeView control.
If you want to disable grouping at the grid level, set the grid's GroupRowPosition property to GroupRowPosition.None (other options are AboveData and BelowData).
The grouping mechanism offered by the ICollectionView class is simple yet powerful. Each level of grouping is defined by a PropertyGroupDescription object. This object allows you to select the property that should be used for grouping and also a ValueConverter that determines how the property value should be used when grouping.
For example, if we wanted to group by country initials instead of by country, we would modify the code above as follows:
List<Customer> list = GetCustomerList();
PagedCollectionView view = new PagedCollectionView(list);
using (view.DeferRefresh())
{
view.GroupDescriptions.Clear();
view.GroupDescriptions.Add(new PropertyGroupDescription("Country"));
view.GroupDescriptions.Add(new PropertyGroupDescription("Active"));
var gd = view.GroupDescriptions[0] as PropertyGroupDescription;
gd.Converter = new CountryInitialConverter();
}
_flexGrid.ItemsSource = view;
The CountryInitialConverter class implements the IValueConverter interface. It simply returns the first letter of the country name causing it to be used for grouping instead of the full country name:
// converter used to group countries by their first initial
class CountryInitialConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
return ((string)value)[0].ToString().ToUpper();
}
public object ConvertBack(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
After making this small change, customers will be grouped by their country's initial instead of by the full country name:
Notice that the group rows display some information about the group they represent (property and value being grouped on, and item count).
You can customize that information by creating a new IValueConverter class and assigning it to the grid's GroupHeaderConverter property.
For example, the default group header converter (the one that shows the information in the image) is implemented as follows:
// class used to format group captions for display
public class GroupHeaderConverter : IValueConverter
{
public object Convert(object value,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var gr = parameter as GroupRow;
var group = gr.Group;
if (group != null && gr != null && targetType == typeof(string))
{
var desc = gr.Grid.View.GroupDescriptions[gr.Level] as
PropertyGroupDescription;
return desc != null
? string.Format("{0}: {1} ({2:n0} items)",
desc.PropertyName, group.Name, group.ItemCount)
: string.Format("{0} ({1:n0} items)",
group.Name, group.ItemCount);
}
return value;
}
public object ConvertBack(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
return value;
}
}