Exploring MVVM: Grouping with the DataGrid

Model-View-ViewModel (MVVM) is one of those really interesting design patterns that are used in WPF.It provides a separation between the UI and business logic and uses data binding techniques to connect them together.Karl Shifflett has some great material on MVVM that you can read about here.

Anyway, I thought I’d dig into some of the details starting with grouping items in a DataGrid.I was originally building a test app for modeling animations of DependencyProperties and ended up using a DataGrid to show the DPs, their current values, and animation properties.I found that I really needed all the sorting, grouping, and filtering capabilities to analyze test values so I wanted to update the DataGrid and use the MVVM pattern (as much as possible).

Grouping by column

To group by column I decided to use a context menu on the column header.In the context menu, the MenuItems are hooked up to commands on my ViewModel like so:

<ContextMenu x:Key=”cm_columnHeaderMenu”>

<MenuItem Name=”mi_group”

Header=”Group by this column”

Command=”{Binding RelativeSource={RelativeSource FindAncestor,

AncestorType={x:Type local:Window1}},

Path=DataContext.GroupColumn}”

CommandParameter=”{Binding RelativeSource={RelativeSource Self},

Path=DataContext}”/>

<MenuItem Name=”mi_clearGroups”

Header=”Clear grouping”

Command=”{Binding RelativeSource={RelativeSource FindAncestor,

AncestorType={x:Type local:Window1}},

Path=DataContext.UngroupColumns}” />

</ContextMenu>

My ViewModel is the DataContext for the main window and implements these commands with a MVVM commanding style introduced by Josh Smith called RelayCommand.You can find it in his Crack.NET solution as well as other MVVM samples here and here.Here is the code for grouping:

DPCollection is the CollectionView that I set on the DataGrid.ItemsSource.So triggering this command will set a group on a particular property and sort by its name.The important part is really how the command is being implemented in the ViewModel and through a delegate style of commanding such as RelayCommand.

Expanding and Collapsing Groups

I really wanted to follow a pattern similar to Josh’s and Bea’s TreeView examples but unfortunately the grouping implementation is not so extensible.When you setup GroupDescriptions, the ItemContainerGenerator of the ItemsControl will create these GroupItem visuals and will set the DataContext of each GroupItem to a CollectionViewGroupInternal object.This CollectionViewGroupInternal object holds information about the items in the group, its name, count, etc.The xaml for my GroupStyle without custom expanding or collapsing looks like this:

<GroupStyle x:Key=”gs_Default”>

<GroupStyle.HeaderTemplate>

<DataTemplate>

<StackPanel>

<TextBlock Text=”{Binding Path=Name}”/>

</StackPanel>

</DataTemplate>

</GroupStyle.HeaderTemplate>

<GroupStyle.ContainerStyle>

<Style TargetType=”{x:Type GroupItem}”>

<Setter Property=”Template”>

<Setter.Value>

<ControlTemplate TargetType=”{x:Type GroupItem}”>

<Expander IsExpanded=”{Binding Path=??}”>

<Expander.Header>

<DockPanel TextBlock.FontWeight=”Bold”>

<TextBlock Text=”{Binding Path=Name}”/>

<TextBlock Text=”{Binding Path=ItemCount}”/>

</DockPanel>

</Expander.Header>

<ItemsPresenter />

</Expander>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</GroupStyle.ContainerStyle>

</GroupStyle>

Notice the properties “Name” and “ItemCount” which are properties on the CollectionViewGroup which is a base class of CollectionViewGroupInternal.Also notice the Expander.IsExpanded property.I needed some way to bind some kind of ViewModel property to Expander.IsExpanded.

The problem is that CollectionViewGroupInternal is Internal as you might have suspected and I was unsuccessful in applying a class adaptor to the base class object without affecting the grouping functionality. So I decided to put an IsExpanded property directly on each of the items instead.While this is more of a hack as IsExpanded means nothing to the item by itself, it is relatively cheaper and really may be a more realistic solution than re-implementing grouping on a custom CollectionView.Here is the updated xaml:

<GroupStyle x:Key=”gs_Default”>

<GroupStyle.HeaderTemplate>

<DataTemplate>

<StackPanel>

<TextBlock Text=”{Binding Path=Name}”/>

</StackPanel>

</DataTemplate>

</GroupStyle.HeaderTemplate>

<GroupStyle.ContainerStyle>

<Style TargetType=”{x:Type GroupItem}”>

<Setter Property=”Template”>

<Setter.Value>

<ControlTemplate TargetType=”{x:Type GroupItem}”>

<Expander IsExpanded=”{Binding Path=Items[0].IsExpanded}”>

<Expander.Header>

<DockPanel TextBlock.FontWeight=”Bold”>

<TextBlock Text=”{Binding Path=Name}”/>

<TextBlock Text=”{Binding Path=ItemCount}”/>

</DockPanel>

</Expander.Header>

<ItemsPresenter />

</Expander>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</GroupStyle.ContainerStyle>

</GroupStyle>

I have made the assumption that it will check the first item in the group but that is ok as I only care able expanding or collapsing all groups.When I clear grouping or group a different column, all IsExpanded properties are reset so there are no side effects on the next group.

Finally, the command for expanding looks like this:

publicICommand ExpandAllGroups

{

get

{

if (_expandAllGroups == null)

_expandAllGroups = newRelayCommand(

() =>

{

if (DPCollection.Groups != null)

{

foreach (object groupItem in DPCollection.Groups)

{

var group = groupItem asCollectionViewGroup;

group.Items[0] asElementViewModel).IsExpanded = true;

}}

});

return _expandAllGroups;

}

}

I have attached a full sample here, which is a stripped down version of the test app I was building.

maybe you are not the right one to address for this issue, but maybe you could give me a hint. We use the WPF CTP Datagrid for a set of ~50 records (14 columns). Since the records increased from a few to the mentioned 50, in-cell editing became unbelievably slow, i.e. it takes seconds for the characters to appear after the key was pressed.

The (well maybe not so) funny thing is that this occurs only on 64-bit, Windows Vista Home Premium HP TouchSmart with 4 GB of RAM and Intel DualCore CPU. The behaviour does not occur on slower 32bit-machines (we dont have an extra 64-bit machine to test). The WPF Visual Profiler shows that there is a lot of time spent for "Layout". Whatever that means — since it does not occur while editing on the ‘not so good’ machines. We do not hook onto any Datagrid-specific events, editing messages or whatsoever.

Most remarkable to us was the fact that edit controls placed on a window which is child of the window containing the Datagrid show the same behaviour.

I solved the problem, but without knowing what the reason was. I found out that the problem did not occur when I removed the columns with the "IsReadOnly"-property set to "True". To work around using this property, I used a DataGridTemplateColumn with a DataGrid.CellTemplate and a TexBlock in it, but without a DataGrid.CellEditingTemplate. That was it. Now my customer is happy again. But still, this behaviour seems pretty strange to me.

For people facing the same problem, here is a small before/after-XAML-snippet:

in your example you put an IsExpanded property directly on each of the items.

I have a similar problem, by which each item in the collection is styled depending on the actual data of the item. ( with many different style elements like typeface, color, size, IsExpanded … ). I hesitate to clutter up the model with this type of data.

I wonder if in the spirit of MVVM I should implement this with value converters or if it is not better to have the ItemSource bind to a collection of ItemViewModels which provides the style data and references the items data