Introduction
Earlier in December 2010, I posted three articles on DataGrid
titled as:
If you didn't read them, go through each article to know more about Silverlight DataGrid
.
One of my readers "Dan" asked me to write an article on "Group Row Header customization of Silverlight DataGrid
" and here is that article for you. If you want to do UI customization of Row Group Header, this article will definitely help you. Here, I will show you how to modify the XAML to add different content to create a multi level row group header too.
Background
Earlier, I was looking for some solution to create a multilevel Group Row Header in a Silverlight DataGrid
and found some good points over various forums including Tim's Blog. I explored them more and found that, yes, we can customize the template to create a multi level group header (but it is limited to only two levels, means if you add more levels of header the rest of the group header will look as same as the second header). I thought of writing an article explaining all these so that you can understand it properly. But due to lack of time, I was unable to complete it.
By that time, one of the readers "Dan" asked me to help him to do the same. I thought of completing the article so that other people will also get help from it in the future.
Here is a screenshot of which we want to achieve by reading this article:
Here, I will show you the customization in detail. Go through it and if you need any specific help on this, let me know. As the XAML code will not fit here properly, I will show only that part which is required for you to understand. But you can easily download the whole source code from the download section.
Begin with the Template
Let's start with the existing application that we created earlier in the previous three articles. In this article, we will edit the template to show a different group header (two levels). To start working with it, you need the default DataGridGroupRowHeader
style. You can find the default style for it from MSDN and here is the link. The style should apply to the DataGrid
's RowGroupHeaderStyles
. Just copy it in your XAML page as shown below:
<sdk:DataGrid
IsReadOnly="True" Margin="10" Height="200" ItemsSource="{Binding Employees}">
<sdk:DataGrid.RowGroupHeaderStyles>
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="#FFE4E8EA" />
<Setter Property="Height" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sdk:DataGridRowGroupHeader">
<sdk:DataGridFrozenGrid x:Name="Root"
Background="{TemplateBinding Background}">
<sdk:DataGridFrozenGrid.Resources>
<ControlTemplate x:Key="ToggleButtonTemplate"
TargetType="ToggleButton">
<Grid>
.
.
.
<Rectangle Grid.Column="1" Grid.ColumnSpan="5"
Fill="#FFD3D3D3"
Height="1" Grid.Row="2"/>
<Rectangle x:Name="FocusVisual" Grid.Column="1"
Grid.ColumnSpan="4" Grid.RowSpan="3"
Stroke="#FF6DBDD1" StrokeThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="false" Opacity="0" />
<sdk:DataGridRowHeader x:Name="RowHeader" Grid.RowSpan="3"
sdk:DataGridFrozenGrid.IsFrozen="True"/>
</sdk:DataGridFrozenGrid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGrid.RowGroupHeaderStyles>
</sdk:DataGrid>
If you check the edited style, you will notice that there is a Rectangle
called "IndentSpacer
". This will do the actual trick for you. IndentSpacer
is used to indent subgroups. Its width specifies the amount that the immediate children of the DataGridRowGroupHeader
are indented. The default value of it is 20
.
<Rectangle Grid.Column="1" Grid.ColumnSpan="5" Fill="#FFFFFFFF" Height="1"/>
<Rectangle Grid.Column="1" Grid.Row="1" x:Name="IndentSpacer" />
<ToggleButton Grid.Column="2" Grid.Row="1" x:Name="ExpanderButton"
Height="15" Width="15" IsTabStop="False"
Template="{StaticResource ToggleButtonTemplate}" Margin="2,0,0,0"/>
See the above code to find the actual position of the IndentSpacer
inside the Template.
Converter to Switch Between Two Group Headers
In this article, we are going to see how we can customize the template to create two different group row headers while grouped with two different levels. So, let us create a Converter for it. The converter checks the width of the IndentSpacer
and based on the value, it will make the first or second group header visible or collapsed.
For our example, if the width of that rectangle is zero and the converter parameter value specifies false
, it will return Visible
. Same thing is applicable if the width is not zero and the converter parameter specifies true value. In other case, it will always return Collapsed
state. We will discuss more on it later while using the converter in the XAML. This will help you to understand the concept in depth.
Here is the converter for your reference:
public class GroupRowHeaderVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double width = System.Convert.ToDouble(value);
bool parameterState = parameter != null &&
bool.Parse(parameter.ToString());
if ((width == 0 && parameterState == false) ||
(width != 0 && parameterState))
{
return Visibility.Visible;
}
if ((width == 0 && parameterState) ||
(width != 0 && parameterState == false))
{
return Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Now add the Converter reference in your XAML page. To do this, add the xmlns
namespace in the XAML page and then add the converter as the resource. Have a look into the following snapshot:
Editing the Template to Create Multi Row Group Header
It's time to create our row group header. The default template has the following XAML code for the Row Group Header style:
<StackPanel Grid.Column="3" Grid.Row="1" Orientation="Horizontal"
VerticalAlignment="Center" Margin="0,1,0,1">
<TextBlock x:Name="PropertyNameElement" Margin="4,0,0,0"
Visibility="{TemplateBinding PropertyNameVisibility}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding Name}" />
<TextBlock x:Name="ItemCountElement" Margin="4,0,0,0"
Visibility="{TemplateBinding ItemCountVisibility}"/>
</StackPanel>
Let's modify it and add the following code:
<!---->
<StackPanel Grid.Column="3" Grid.Row="1" Orientation="Horizontal"
VerticalAlignment="Center" Margin="0,1,0,1"
Visibility="{Binding Width,
Converter={StaticResource GroupRowHeaderVisibilityConverter},
ElementName=IndentSpacer}">
<TextBlock Margin="4,0,0,0" Text="{Binding Name}" Foreground="Red"/>
</StackPanel>
<!---->
<!---->
<Grid Grid.Column="3" Grid.Row="1" VerticalAlignment="Center"
HorizontalAlignment="Stretch" Margin="0,1,0,1"
Visibility="{Binding Width, ConverterParameter=true,
Converter={StaticResource GroupRowHeaderVisibilityConverter},
ElementName=IndentSpacer}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Margin="4,0,0,0" Text="{Binding Name}" Foreground="Blue"/>
</StackPanel>
</Grid>
<!---->
Here, the first stackpanel
is your first level group header where the second stackpanel
inside the grid is your second level, third level... group headers. Both have visibility
property binded to the Width
of the IndentSpacer
with the converter that we created earlier.
So, if the width is zero and converter parameter is set to false
(default value), it will show the first stackpanel
, i.e. your first level row group header. The second stackpanel
will collapse because we are passing Converter
parameter as "true
".
In reverse case, the first stackpanel
will collapse and the second stackpanel
will get the visibility. Thus shows the next level row group headers.
I just added some default values there with different foreground color set to the text of the header. You can now customize it easily if you understand the logic behind it.
Playing with Code to do Multi Grouping
As our XAML is ready, it's time to do some coding in C# to group the records at the time of loading. First, we will go to the ViewModel
and comment out the following line marked inside the snapshot:
Remember: This comment out is only for this sample to work easily. The issue here will be, if you group it from the dropdown again and again, it will create as many headers as you grouped. For now, no need to worry about that.
Go to your code behind file, i.e., MainPage.xaml.cs file. There, just after the InitializeComponent()
method inside the constructor, call the GroupDataByColumnName()
method two times, passing two different column names.
Have a look into the following code snippet:
See It In Action
This is all about the code. Let's build the solution. Hope you will get no error. If you get, check what is the error and fix it properly. Now run the application. You will see the following UI:
You will see that the first level header has a foreground color "Red
" where the second level header has foreground color "Blue
" (as we specified in the template).
Browse through the other pages and you will see how the group row header styled itself properly. Hope, you got the basic idea. Now, you will be able to modify it properly as per your requirement. Let me know if you have any issue with this. Don't forget to vote. Feedback is always appreciated.