Grouping with AdvGrid and AdvTree
AdvGrid and AdvTree both support grouping by column. When grouping by a column, all items which have the same value for that column are grouped together. Grouping works the same way for both tree and grid, with the exception of a few details. The main difference between them is that AdvTree takes advantage of its support for hierarchical data to enable virtualization. AdvGrid does not support virtualization when grouping. What AdvGrid has that AdvTree does not is built-in support for a column grouping panel. The user is able to drag a column header into the grouping panel to cause the items to be grouped by that column. AdvTree can use a grouping panel, but it is not built-in.
There is a grouping sample in the AdvGrid sample application.
Column grouping is controlled primarily via the GroupByLevel property of a ColumnDefinition, which has a default value of zero. To group by a column set this value to 1 or greater. There can be hierarchies of groups, or groups within groups. The top level group will have a GroupByLevel of 1, the next level of groups will have GroupByLevel of 2, and so on.
There are two ways, built-in, to let your users set the column grouping. One is with the context menu which is activated on a column header. To enable the context menu, set the IsContextMenuEnabled property of the tree’s ColumnDefinitions to true. You can then set the ContextMenuOptions property to limit the commands available to the user. ContextMenuOptions is a flags enum with possible values None, Pin, Group, Sort and Hide. For example, to enable the grouping and the pinning commands in Xaml, set ContextMenuOptions=”Pin, Group”. By default, ContextMenuOptions has all the options enabled. It is possible to set ContextMenuOptions at the level of the ColumnDefinitions and then override that value on individual columns.
The other way to let your user define the column grouping is with the grouping panel. In AdvGrid, simply set ShowColumnGroupingPanel to true. For AdvTree enabling this feature takes a little bit more work. What you will need to do is place an ItemsControl, properly styled, directly above the tree. For example:
<ItemsControl BorderBrush="{Binding ElementName=tree, Path=BorderBrush}"
ItemsSource="{Binding ElementName=tree, Path=ColumnDefinitions.GroupedColumns}"
Style="{DynamicResource {x:Static dc:AdvGrid.ColumnGroupingPanelStyleKey}}" />
<dc:AdvTree Grid.Row="1" VirtualizingMode="Recycling" ItemsSource="{Binding Songs}"
ColumnDefinitions="{StaticResource SongsColumnDefinitions}">
<dc:AdvTree.Resources>
<Style TargetType="dc:ColumnsPresenter">
<Setter Property="dc:DragDrop.IsDragSource" Value="True" />
</Style>
</dc:AdvTree.Resources>
</dc:AdvTree>
Notice that the Style of the ItemsControl is a reference to a resource with the key AdvGrid.ColumnGroupingPanelStyleKey. This is necessary to ensure the proper behavior. Also, notice the Style targeting ColumnsPresenter in the tree’s Resources. If the AllowColumnReorder property of the tree’s ColumnDefinitions is set to true then that style is not necessary.
If you don’t want to allow grouping by a specific column, set the column’s CanGroupBy property to false.
The main way to customize the appearance of a group is with a GroupItemDescriptor, by setting it as the value a column definition’s GroupItemDescriptor property, or by adding it to the GroupDescriptors collection found on both AdvTree and AdvGrid. A descriptor is applied to a group based on its position in the GroupDescriptors collection. The entry at index 0 describes the top-level groups, the entry at index 1 describes the next level, and so on. If there are more levels of grouping than entries in the collection, the last entry is used for the extra levels. Descriptors set on a column directly take precedence over those found in the GroupDescriptors collection.
Property values set on a GroupItemDescriptor are mapped directly to the corresponding properties of the UI element that displays the group. The properties of GroupItemDescriptor are: InitialiallyExpanded, AlternationCount, CanUserCollapse, HideIfEmpty, Background, HeaderStringFormat, HeaderTemplate, HeaderTemplateSelector, IsExpandedToggleButtonStyle, ContainerStyle, ContainerStyleSelector. Hopefully these are mostly self-explanatory.
ContainerStyle, if set, is applied to the UI element used for presenting the group. AdvGrid and AdvTree use different element types for grouping. AdvGrid uses AdvGroupItem controls while AdvTree uses its normal AdvTreeNode. When you define Styles for group containers, be sure to set TargetType accordingly.
When defining a custom header template for a group, the data context for the template will be a instance of CollectionViewGroupWrapper. The properties of CollectionViewGroupWrapper are as follows: Level, Name, DisplayName, ChildCount, IsBottomLevel, GroupedColumnHeader and GroupedColumn. GroupedColumn is the ColumnDefinition for the column being grouped by. Note that GroupItemDescriptor.HeaderTemplate is mapped to the ContentTemplate property of an AdvGroupItem control, and the HeaderTemplate property of an AdvTreeNode.
Here is an example of a GroupItemDescriptor being used to set the header template for every group level:
<dc:AdvGrid Name="grid" ShowRowHeaders="False" ShowColumnGroupingPanel="True"
DataContext="{StaticResource DataProvider}" ItemsSource="{Binding Songs}"
ColumnDefinitions="{StaticResource SongsColumnDefinitions}">
<dc:AdvGrid.GroupDescriptors>
<dc:GroupItemDescriptor>
<dc:GroupItemDescriptor.HeaderTemplate>
<DataTemplate DataType="dc:CollectionViewGroupWrapper">
<TextBlock>
<Run Text="{Binding Name, Mode=OneWay}" />
<Run Text="{Binding GroupedColumnHeader, Mode=OneWay, StringFormat={}({0})}" />
</TextBlock>
</DataTemplate>
</dc:GroupItemDescriptor.HeaderTemplate>
</dc:GroupItemDescriptor>
</dc:AdvGrid.GroupDescriptors>
</dc:AdvGrid>
Another way to customize the grouping control is to override the default grouping container Style. For AdvGrid do that by defining a Style which targets AdvGroupItem and uses the ComponentResourceKey AdvGrid.DefaultGroupItemStyleKey as its Key. For example, here is the default:
<Style x:Key="{x:Static dc:AdvGrid.DefaultGroupItemStyleKey}" TargetType="dc:AdvGroupItem">
<Setter Property="CanUserCollapse" Value="True" />
<Setter Property="Background" Value="#FFDADADA" />
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource AncestorType=dc:AdvGrid}, Path=HorizontalGridLineBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=dc:AdvGrid}, Path=ShowHorizontalGridLines}" Value="True">
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource AncestorType=dc:AdvGrid}, Path=HorizontalGridLineBrush}" />
</DataTrigger>
<Trigger Property="Level" Value="1">
<Setter Property="Background" Value="#FFF0F0F0" />
</Trigger>
<Trigger Property="Level" Value="2">
<Setter Property="Background" Value="#FFFAFAFA" />
</Trigger>
<Trigger Property="Level" Value="3">
<Setter Property="Background" Value="#FFFDFDFD" />
</Trigger>
</Style.Triggers>
</Style>
For AdvTree, the resource key is AdvTreeNode.DefaultGroupingStyleKey and the Style should target AdvTreeNode. Here’s the default:
<Style x:Key="{x:Static dc:AdvTreeNode.DefaultGroupingStyleKey}" TargetType="dc:AdvTreeNode">
<Setter Property="InheritsColumns" Value="Never" />
<Setter Property="Header" Value="{Binding}" />
<Setter Property="IsExpanded" Value="True" />
<Setter Property="ItemsSource" Value="{Binding Items}" />
<Setter Property="IsSelectable" Value="False" />
<Setter Property="ExpanderButtonType" Value="PlusMinus" />
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=HorizontalGridLineBrush}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Italic><Run Text="{Binding DisplayName, Mode=OneWay, StringFormat={}{0}: }" /></Italic>
<Run Text="{Binding Name, Mode=OneWay}" />
<Run Text="{Binding ChildCount, Mode=OneWay, StringFormat={}({0})}" />
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Leave a Reply!