Grouping and Sorting in Xceed DataGrid for WPF

This week we will take a quick look at Grouping and Sorting in the DataGrid for WPF.

Learn more about Xceed DataGrid for WPF

This week we will take a quick look at Grouping and Sorting in the DataGrid for WPF.

Grouping at Runtime

By default, when a DataGridControl is created, it contains a HierarhicalGroupByControl in its fixed headers section. The group-by control provides a condensed view of the group levels and allows end users to modify the group descriptions applied to a grid.

Each group level is represented by a HierarchicalGroupByItem that can be used to change the order of the groups, sort the data items, or remove the groups altogether. If a ColumnManagerRow is present in a grid (regardless of its location), its cells (ColumnManagerCell) can be dragged onto the group-by control to create an additional group level.

Setting Group Descriptions in the CollectionView

Data items can be grouped by adding standard PropertyGroupDescription objects or DataGridGroupDescription objects (recommended) to the GroupDescriptions property of the DataGridCollectionViewSource or DataGridCollectionView to which a grid is bound.

In the following example, we are grouping orders by shipping country and city:

<Grid xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid">
	<Grid.Resources>
		<xcdg:DataGridCollectionViewSource x:Key="cvs_orders"
						   Source="{Binding Source={x:Static Application.Current},Path=Orders}">
			<xcdg:DataGridCollectionViewSource.GroupDescriptions>
				<xcdg:DataGridGroupDescription PropertyName="ShipCountry"/>
				<xcdg:DataGridGroupDescription PropertyName="ShipCity"/>
			</xcdg:DataGridCollectionViewSource.GroupDescriptions>
		</xcdg:DataGridCollectionViewSource>
	</Grid.Resources>

	<xcdg:DataGridControl x:Name="OrdersGrid"
			      ItemsSource="{Binding Source={StaticResource cvs_orders}}">
	</xcdg:DataGridControl>
</Grid>

Note: Similarly, it is also possible to do the same with a DataGridDetailDescription, and/or directly through the Items property and specifying the field name of the column by whose values to group. Each DataGridGroupDescription object that is added to this collection represents the characteristics of a group level in a grid.

Custom Grouping

It is also possible to create a custom group description by deriving from the DataGridGroupDescription class and overriding the GroupNameFromItem method. Once implemented, a custom SortComparer can be assigned to it.

First, we need to create our custom group description:

public class AlphabeticalGroupDescription : DataGridGroupDescription
{
	public AlphabeticalGroupDescription()
		: base()
	{
	}
	public AlphabeticalGroupDescription( string propertyName )
		: base( propertyName )
	{
	}

	public override object GroupNameFromItem( object item, int level, System.Globalization.CultureInfo culture )
	{
		object value = base.GroupNameFromItem( item, level, culture );
		try
		{
			string content = Convert.ToString( value );
			value = content.ToUpper().Substring( 0, 1 );
		}
		catch( InvalidCastException )
		{
		}
		return value;
	}
}

Then, we add it to our DataGridCollectionViewSource:

<Grid.Resources>
	<local:ConsonantVowelComparer x:Key="consonantVowelComparer"/>
	<xcdg:DataGridCollectionViewSource x:Key="cvs_orders"
					   Source="{Binding Source={x:Static Application.Current}, Path=Orders}">
		<xcdg:DataGridCollectionViewSource.GroupDescriptions>
			<local:AlphabeticalGroupDescription PropertyName="ShipCountry"
							    SortComparer="{StaticResource consonantVowelComparer}"/>
		</xcdg:DataGridCollectionViewSource.GroupDescriptions>
	</xcdg:DataGridCollectionViewSource>
</Grid.Resources>

Preventing Grouping

It is possible to prevent the user from being able to modify the groups, so that any groups you apply cannot be removed or added to. This is achieved by using the AllowGroupingModification property on the GroupByControl.

Because we are re-defining the Fixed Headers, we need to set UseDefaultHeadersFooters to false and manually add the headers we want.

For example:

<xcdg:DataGridControl x:Name="OrdersGrid"
		      ItemsSource="{Binding Source={StaticResource cvs_orders}}">
	<xcdg:DataGridControl.View>
		<xcdg:TableView UseDefaultHeadersFooters="False">
			<xcdg:TableView.FixedHeaders>
				<DataTemplate>
					<xcdg:GroupByControl AllowGroupingModification="False" />
				</DataTemplate>
				<DataTemplate>
					<xcdg:ColumnManagerRow />
				</DataTemplate>
			</xcdg:TableView.FixedHeaders>
		</xcdg:TableView>
	</xcdg:DataGridControl.View>
</xcdg:DataGridControl>

Sorting at Runtime

When a DataGridControl is created, by default, it contains a ColumnManagerRow in its fixed headers section that contains a ColumnManagerCell for each column in a grid. The content of one or more columns can be sorted by clicking in the corresponding ColumnManagerCell.

By default, clicking once will sort the column’s values in an ascending direction, the second click will sort them in a descending direction, while the third click will remove any sorting that has been applied to the column’s values. To sort the values of multiple columns, hold the SHIFT key while clicking on a ColumnManagerCell. The sort-direction cycle can be modified by providing a column with a new SortDirectionCycle collection or by handling the grid’s SortDirectionChanging event.

Setting Sort Descriptions in the CollectionView

Data items can be sorted by adding SortDescription objects to the SortDescriptions property of the DataGridCollectionViewSource or DataGridCollectionView to which a grid is bound.

In the following example, we are grouping orders by shipping country and city:

<Grid xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
      xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid">
	<Grid.Resources>
		<xcdg:DataGridCollectionViewSource x:Key="cvs_orders"
						   Source="{Binding Source={x:Static Application.Current}, Path=Orders}">
			<xcdg:DataGridCollectionViewSource.SortDescriptions>
				<scm:SortDescription PropertyName="ShipCountry"
						     Direction="Ascending"/>
			</xcdg:DataGridCollectionViewSource.SortDescriptions>
		</xcdg:DataGridCollectionViewSource>
	</Grid.Resources>

	<xcdg:DataGridControl x:Name="OrdersGrid"
			      ItemsSource="{Binding Source={StaticResource cvs_orders}}">
		<xcdg:DataGridControl.Columns>
			<xcdg:Column FieldName="ShipCountry" VisiblePosition="0"/>
		</xcdg:DataGridControl.Columns>
	</xcdg:DataGridControl>
</Grid>

It is also possible to do it directly through the Items property, and specifying the field name of the column by whose values to sort as well as the direction in which those values are to be sorted.

Custom Sorting

On top of the default type-based sorting, it is also possible to apply custom sorting by providing an IComparer to the SortComparer property of one or more item properties defined in the DataGridCollectionView or DataGridCollectionViewSource to which a grid is bound The comparer will be used whenever the values of the item property’s corresponding column are sorted, for example when clicking on the column header.

First, we need to create our custom IComparer:

public class AddressComparer: IComparer
{
	public AddressComparer()
	{
	}
	int IComparer.Compare( object x, object y )
	{
		string stringX = ( string )x;
		string stringY = ( string )y;
		const string digits = "0123456789";
		if( ( digits.IndexOf( stringX[ 0 ] ) >= 0 ) && ( digits.IndexOf( stringY[ 0 ] ) >= 0 ) )
		{
			int index = 0;
			System.Text.StringBuilder xNumber = new System.Text.StringBuilder();
			while( ( index < stringX.Length ) && ( digits.IndexOf( stringX[ index ] ) >= 0 ) )
			{
				xNumber.Append( stringX[ index ] );
				index++;
			}
			index = 0;
			System.Text.StringBuilder yNumber = new System.Text.StringBuilder();
			while( ( index < stringY.Length ) && ( digits.IndexOf( stringY[ index ] ) >= 0 ) )
			{
				yNumber.Append( stringY[ index ] );
				index++;
			}
			long xValue = long.Parse( xNumber.ToString() );
			long yValue = long.Parse( yNumber.ToString() );
			if( xValue > yValue )
				return 1;
			if( xValue < yValue )
				return -1;
			return stringX.CompareTo( stringY );
		}
		else
		{
			return stringX.CompareTo( stringY );
		}
	}
}

Then, we add it to our DataGridCollectionViewSource:

<Grid.Resources>
	<local:AddressComparer x:Key="addressComparer"/>
	<xcdg:DataGridCollectionViewSource x:Key="cvs_orders"
					   Source="{Binding Source={x:Static Application.Current}, Path=Orders}"
					   AutoCreateItemProperties="False">
		<xcdg:DataGridCollectionViewSource.ItemProperties>
			<xcdg:DataGridItemProperty Name="ShipCountry" />
			<xcdg:DataGridItemProperty Name="ShipCity" />
			<xcdg:DataGridItemProperty Name="ShipAddress"
						   SortComparer="{StaticResource addressComparer}"/>
			<xcdg:DataGridItemProperty Name="ShipVia" />
		</xcdg:DataGridCollectionViewSource.ItemProperties>
	</xcdg:DataGridCollectionViewSource>
</Grid.Resources>

Preventing Sorting

It is possible to prevent the user from being able to sort the columns, so that any sort you apply cannot be removed or modified. This is achieved by using the AllowSort property, available on the GroupByControl and ColumnManagerRow.

Because we are re-defining the Fixed Headers, we need to set UseDefaultHeadersFooters to false and manually add the headers we want.

For example:

<xcdg:DataGridControl x:Name="OrdersGrid"
		      ItemsSource="{Binding Source={StaticResource cvs_orders}}">
	<xcdg:DataGridControl.View>
		<xcdg:TableView UseDefaultHeadersFooters="False">
			<xcdg:TableView.FixedHeaders>
				<DataTemplate>
					<xcdg:GroupByControl AllowSort="False" />
				</DataTemplate>
				<DataTemplate>
					<xcdg:ColumnManagerRow AllowSort="False" />
				</DataTemplate>
			</xcdg:TableView.FixedHeaders>
		</xcdg:TableView>
	</xcdg:DataGridControl.View>
</xcdg:DataGridControl>

For more information, please refer to the documentation.