Welcome to the Xceed Community | Help
Community Search  
More Search Options

DON'T PANIC!

From WinForms to WPF - PART 2: Sorting and Grouping Data

In Part 1 of this series, I demonstrated how to provide a grid with data. This post will continue where I left off and demonstrate how to group and sort the same data, so if you have not looked at Part 1, I suggest you do so.

Ready?                                                                                                              

Data items can be sorted by adding SortDescription structures to the SortDescriptions collection of the DataGridCollectionView to which a grid is bound 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.  Let's look at some code:

view.SortDescriptions.Add( new SortDescription( "ShipCountry", ListSortDirection.Ascending ) );     
view.SortDescriptions.Add( new SortDescription( "ShipCity", ListSortDirection.Ascending ) );

In the above code, two SortDescription structures are added to the SortDescriptions collection of the DataGridCollectionView ("view") to which the grid is bound. The result is that the data items of the grid are sorted according to the values of the ShipCountry and ShipCity columns.

Groups are created in basically the same way; however, rather than adding SortDescription structures to the SortDescriptions collection, DataGridGroupDescription objects are added to the GroupDescriptions collection. For example:

view.GroupDescriptions.Add( new DataGridGroupDescription( "ShipCountry" ) );     
view.GroupDescriptions.Add( new DataGridGroupDescription( "ShipCity" ) );

As mentioned in Part 1, the DataGridCollectionViewSource is the XAML representation of the DataGridCollectionView. So let's see how the above code translates to XAML. But before we do, we need to map the System.ComponentModel namespace in the WindowsBase assembly that will allow us to use the SortDescription structure. You can add the new namespace declaration to your window or to the grid that contains the datagrid control.

xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"

Let's get on with the show!

<xcdg:DataGridCollectionViewSource x:Key="cvs_orders" Source="{Binding Path=Orders}">
   <xcdg:DataGridCollectionViewSource.SortDescriptions>
      <scm:SortDescription PropertyName="ShipCountry" Direction="Ascending" />
      <scm:SortDescription PropertyName="ShipCity" Direction="Ascending" />
   </xcdg:DataGridCollectionViewSource.SortDescriptions>           

   <xcdg:DataGridCollectionViewSource.GroupDescriptions>
      <xcdg:DataGridGroupDescription PropertyName="ShipCountry" />
      <xcdg:DataGridGroupDescription PropertyName="ShipCity" />
   </xcdg:DataGridCollectionViewSource.GroupDescriptions>
</xcdg:DataGridCollectionViewSource>

Now, when SortDescriptions and DataGridGroupDescriptions are added to a DataGridCollectionView or DataGridCollectionViewSource, they represent the INITIAL sorting and grouping criteria that are applied to the content of a grid. Once an end user changes these, the ones that were initially added will no longer be used. This can be especially problematic with group descriptions since any customization that was done to the initial descriptions, such as providing a specific GroupConfiguration, will be lost. But don't panic! As you will see, there is a very easy solution to this problem!

Custom Group Descriptions and Configurations

Each DataGridGroupDescription exposes a GroupConfiguration property, which determines the appearance the groups will have once they are created. If a custom group configuration is provided to the initial DataGridGroupDescription, it will be lost once the group is removed by the end user. This is where the GroupDescription and GroupConfiguration properties, which are exposed by the Column class, come in handy.  The DataGridGroupDescription and GroupConfiguration that are provided to these properties, respectively, will be used whenever the content of the grid is grouped by the values of the column. Of course, if you have a custom DataGridGroupDescription that already has a GroupConfiguration, you don't need to provide the GroupConfiguration to the column.

So, let's assume that we have a GroupConfiguration that needs to be used every time the content of the grid is grouped by the values of the ShipCountry column. If this were done in code, it would look something like the following:

DataTemplate template = new DataTemplate();
template.VisualTree = new FrameworkElementFactory( typeof( InsertionRow ) );

GroupConfiguration configuration = new GroupConfiguration();
configuration.Footers.Add( template );

grid.Columns[ "ShipCountry" ].GroupConfiguration = configuration;

Although the above code demonstrates how to create a template in code and add it to the Footers collection of the GroupConfiguration, DO NOT CREATE TEMPLATES IN CODE! EVER! FrameworkElementFactory is deprecated and the API in code does not allow you to do as much as it does in XAML. Now that I got that out of my system, let's look at the equivalent XAML:

<xcdg:DataGridControl.Columns>
   <xcdg:Column FieldName="ShipCountry">
      <xcdg:Column.GroupConfiguration>
         <xcdg:GroupConfiguration>
            <xcdg:GroupConfiguration.Footers>
               <DataTemplate>
                  <xcdg:InsertionRow />
               </DataTemplate>
            </xcdg:GroupConfiguration.Footers>
         </xcdg:GroupConfiguration>
      </xcdg:Column.GroupConfiguration>
   </xcdg:Column>                         
</xcdg:DataGridControl.Columns>

Much better Smile

Custom Sorting

In addition to the default type-based sorting, custom sorting can also be applied by providing an IComparer to the SortComparer property of one or more DataGridItemProperties defined in the DataGridCollectionView or DataGridCollectionViewSource to which a grid is bound. This comparer will be used whenever the values of the corresponding column are sorted (e.g., clicking on the column header). For example, I have created a custom sort comparer (AddressComparer class) that compares addresses first with their civic number, followed by the street. This comparer is then provided to the ShipAddress DataGridItemProperty and will be used whenever the ShipAddress column is sorted.

First, the code:

DataGridItemProperty addressProperty = new DataGridItemProperty( "ShipAddress", typeof( string ) );
addressProperty.SortComparer = new AddressComparer();

view.ItemProperties.Add( addressProperty );

Now, the XAML:

<local:AddressComparer x:Key="addressComparer"/>

<xcdg:DataGridCollectionViewSource.ItemProperties>
   <xcdg:DataGridItemProperty Name="ShipAddress" SortComparer="{StaticResource addressComparer}" />
</xcdg:DataGridCollectionViewSource.ItemProperties>

You'll notice that the AddressComparer class is located in the "local" namespace. This namespace must be declared in order to be able to use the AddressComparer class. The class also needs to be declared in the resources of the Grid (not the DataGridControl class), above the declaration of the DataGridCollectionViewSource.

xmlns:local="clr-namespace:WinFormsToWPF"

Knowing When Sorting and Grouping Change

A common question that has been asked by many clients is "How do I know when the grouping or sorting changes?". And many get frustrated when they don't find "group changed" or "sort changed" events. The solution, however, is easier than you might think!

Both the GroupDescriptions and SortDescriptions collections exposed by the DataGridCollectionView class implement the INotifyCollectionChanged event, which has a CollectionChanged event that can be handled to know when either collection changes. First, in code:

DataGridCollectionView view = new DataGridCollectionView( this.Orders.DefaultView );

( ( INotifyCollectionChanged )view.GroupDescriptions ).CollectionChanged += new NotifyCollectionChangedEventHandler( GroupDescriptions_CollectionChanged );
( ( INotifyCollectionChanged )view.SortDescriptions ).CollectionChanged += new NotifyCollectionChangedEventHandler( SortDescriptions_CollectionChanged );

Now, in XAML:

Actually, this can only be done in code; however, when the grid and DataGridCollectionViewSource are defined in XAML, the location where the events are subscribed to is not the same. In the code above, the CollectionChanged events were subscribed to right after the DataGridCollectionView was created. In the case where the collection view is defined in XAML, the events can only be subscribed to after the grid and collection view have been created. The location I usually choose for this is the OnInitialized override of my main window:

DataGridCollectionView view = this.OrdersGrid.ItemsSource as DataGridCollectionView;

if( view != null )
{
   ( ( INotifyCollectionChanged )view.GroupDescriptions ).CollectionChanged += new NotifyCollectionChangedEventHandler( GroupDescriptions_CollectionChanged );
   ( ( INotifyCollectionChanged )view.SortDescriptions ).CollectionChanged += new NotifyCollectionChangedEventHandler( SortDescriptions_CollectionChanged );
}

Conclusion

That's pretty much it! The next tutorial-and I promise to get it out much faster than it took me to get part 2 out-will deal with master-detail scenarios.

Thanks for reading!

Published February 6, 2009 12:26 PM by Jenny [Xceed]
Filed under:

Attachment(s): WinFormsToWPF_Part2.zip

Comments

No Comments
Anonymous comments are disabled
Contact | Site Map | Reviews | Legal Terms of Use | Trademarks | Privacy Statement Copyright 2011 Xceed Software Inc.