WPF Toolkit DataGrid Part IV: TemplateColumns and Row Grouping

This post is part of the WPF Toolkit DataGrid series. Here is a list with the complete set of blog posts:

Introduction

In Part III we got to know a bit better how stuff gets rendered on the WPF DataGrid. We have gone through several samples on how to configure the way data gets displayed on each column by either creating new column types or by styling existing ones.

In Part IV we will have a look into more advanced concepts. First we will see how we can use the DataGridTemplateColumn to imitate the behavior of a DataGridComboBoxColumn and to show a small chart representing the age deviation from average for each player. We will then have a look on how we can group rows on our DataGrid using two separate methodologies.

 

Roadmap

  • Creating a ComboBox column from a TemplateColumn
  • Using DataGridTemplateColumns to create a chart column
  • Grouping rows using ItemsControl.GroupStyle
  • Fake grouping with the help of the ViewModel

Download sample source code

Here is a list with the samples presented on this blog post:

Creating a ComboBox column from a TemplateColumn

The ItemsSource property of DataGridComboBox column does not allow for you to directly bind to data on your DataContext, at least for now (WPF Toolkit – March 2009 release). It is likely that in a future release binding support will be added (if you want further information on this checkout this CodePlex workitem).

There are a lot of ways in which you could implement this! For instance, you could extend DataGridComboBoxColumn adding binding support on it, you could add the binding support directly to DataGridComboBoxColumn recompiling WPF Toolkit, etc.

The solution I came up with in my application was a simpler one, IMHO. I restyled a DataGridTemplateColumn to behave like a DataGridComboBox column.

This is very simple to accomplish. The only issue I found was that the effort required to actually finding the property to bind to from within the created template – I had to use a tricky binding expression.

You must start by adding to your view model the data you are going to bind to:

(…)
public IList<string> Categories { get; set; }
(…)
public TeamModel()
{
    Categories = new List<string> 
        { "Mini", "Cadet", "Junior", "Senior", "Veteran" };
}
(…)

In order for you to use a DataGridTemplateColumn you must create two specific templates, one for the cell in its default view and one for when it is in editing mode. In the default view of our cell only text will be displayed. You can just add a new template that uses a label to render the selected text:

<DataTemplate x:Key="ComboBoxCellDataTemplate">
    <Label x:Name="lblCombo" Content="{Binding Category}" 
           Style="{StaticResource BaseLabelCellStyle}" />
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Categories}" Value="Both">
            <Setter TargetName="lblCombo" Property="IsEnabled" Value="False" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

As you may have noticed you had to use RelativeSource to find the properties to bind to. Now you have to create the editing mode template that uses a ComboBox and lets the users select which category he wants to set a player in.

<DataTemplate x:Key="ComboBoxCellEditingTemplate">
    <ComboBox x:Name="comboBox"
              ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Categories}"
              SelectedItem="{Binding Category}"/>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Enabled}" Value="False">
            <Setter TargetName="comboBox" Property="IsEnabled" Value="False" />
        </DataTrigger>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Categories}" Value="Both">
            <Setter TargetName="comboBox" Property="IsEnabled" Value="False" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

After adding these two DataTemplates to the resources you can change the definition of the Category column to use the DataGridTemplateColumn with these two specific templates.

<WpfToolkit:DataGridTemplateColumn
    Header="Category" Width="1*"
    CellTemplate="{StaticResource ComboBoxCellDataTemplate}"
    CellEditingTemplate="{StaticResource ComboBoxCellEditingTemplate}"/>

It could not get simpler than this! You have just made a ComboBoxColumn out of a DataGridTemplateColumn.

This is how our sample application is looking after these changes:

WpfToolkitDataGrid-ss015a

You can download a working sample of this code by following this link.

 

Using DataGridTemplateColumns to create a chart column

Each player shown in the DataGrid has an age associated with him. For each player we will be presenting a chart column picturing the deviation of his age towards the team average.

All calculations regarding the team age average and each players deviation should be done in the backend. Basically you will have to change the code behind file for TeamView since there is where we setup the view model for the view (this is definitely a bad practice – it is being done this way for simplicity). In the DeviationChart column we will expose the DeviationPercentage previously calculated. This value will be used to calculate the width of the bars to draw our chart with.

Essentially this is the code used to calculate these values:

(…)
SetupTeamAgeAverage(playerAges.Where(player => player.Parent == alphaTeam));

(…)

private static void SetupTeamAgeAverage(IEnumerable<IPlayerAge> teamPlayers)
{
    double teamAgeAverage = teamPlayers.Average(x => x.Age);
    foreach (var player in teamPlayers)
    {
        player.Deviation = player.Age - teamAgeAverage;
        player.DeviationPercentage = 
            (player.Age - teamAgeAverage) / teamAgeAverage;
    }
}
(…)

Now that the data is processed you need to create a new element style to apply on the DataGridTemplateColumn that picks this value and transforms it to a chart.

Deviation percentage goes from -1 to 1. If it is negative we will create a bar on the left of the chart, otherwise a bar will be added on the right. The length of the bar will be proportional to the amount of deviation. All this will be done using standard grids, background colors and star notation on grid column widths.

Since we do not have a direct way of converting a value into a percentage of width we will be doing so in a hacky way by using an IValueConverter (you can see the actual implementation of the converter by downloading the sample code).

After creating the converter you will have to add this new style to the resources of your view:

<Style x:Key="BarChartCellStyle" TargetType="{x:Type WpfToolkit:DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridCell}">
                <Grid Background="{TemplateBinding Background}">
                    <Grid x:Name="BarChart" Background="LightBlue" 
                          Height="14"  Margin="2,0,2,0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition x:Name="lowerToleranceColumn"
                                              Width="*"/>
                            <ColumnDefinition Width="1"/>
                            <ColumnDefinition x:Name="upperToleranceColumn"
                                              Width="*"/>
                        </Grid.ColumnDefinitions>

                        <Grid Margin="2,0,1,0">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="{Binding DeviationPercentage, 
                                    Converter={StaticResource percentageWidthConverter}, 
                                    ConverterParameter=1}"/>
                                <ColumnDefinition Width="{Binding DeviationPercentage,
                                    Converter={StaticResource percentageWidthConverter},
                                    ConverterParameter=2}"/>
                            </Grid.ColumnDefinitions>
                            <Border x:Name="LowerBarChart" Grid.Column="1" 
                                    Height="10" Background="MediumBlue">
                                <TextBlock x:Name="LowerToleranceLabel" 
                                           Text="&lt;" Foreground="White"
                                           HorizontalAlignment="Left" VerticalAlignment="Center" 
                                           Margin="2,-2,0,0"/>
                            </Border>
                        </Grid>

                        <Border Grid.Column="1" Width="1" Background="Gray" />

                        <Grid Grid.Column="2" Margin="1,0,2,0">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="{Binding DeviationPercentage, 
                                    Converter={StaticResource percentageWidthConverter}, 
                                    ConverterParameter=3}"/>
                                <ColumnDefinition Width="{Binding DeviationPercentage,
                                    Converter={StaticResource percentageWidthConverter},
                                    ConverterParameter=4}"/>
                            </Grid.ColumnDefinitions>
                            <Border x:Name="UpperBarChart" Height="10" Background="DarkOrange">
                                <TextBlock x:Name="UpperToleranceLabel" 
                                           Text="&gt;" Foreground="White" 
                                           HorizontalAlignment="Right" VerticalAlignment="Center"
                                           Margin="0,-2,2,0"/>
                            </Border>
                        </Grid>

                        <Border BorderBrush="White" BorderThickness="1,1,0,0" Grid.ColumnSpan="3"/>
                        <Border BorderBrush="LightGray" BorderThickness="0,0,1,1" Grid.ColumnSpan="3"/>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now that you have a new style on your view’s resource dictionary you can go ahead and set the corresponding styles in the DataGridTemplateColumn. To do so you will have to replace the Deviation Chart column on your DataGrid by a DataGridTemplateColumn:

(…)
<WpfToolkit:DataGridTemplateColumn
    Header="Deviation Chart" Width="1*"
    HeaderStyle="{StaticResource CenterAlignedColumnHeaderStyle}"
    CellStyle="{StaticResource BarChartCellStyle}"/>
(…)

By replacing this column we gave our DataGrid a much cooler look. For each player an age chart is displayed on the right:

WpfToolkitDataGrid-ss015 

You can download a working sample of this code by following this link.

 

Grouping rows using ItemsControl.GroupStyle

From the beginning of this series we have displayed data that was supposed to be grouped. As you have noticed there are two teams and several players per team. A team should not be displayed as a regular row on our DataGrid, it should be some sort of group header that we can collapse and expand as required, showing the players that are part of it.

In this implementation we will follow the recommendations on how to do grouping on WPF Toolkit’s DataGrid.

For you to be able to do grouping you will have to create a CollectionViewSource and specify by which field the grouping will be done. In this case grouping will be done on each player’s Parent field, which corresponds to the actual Team instance.

<CollectionViewSource x:Key="PlayerData">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Parent"/>
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

Note that Source is not specified for the CollectionViewSource. This is due to the fact that the list is being created in code behind; as a consequence, it cannot be instantiated in xaml. You will have to set the Source for this collection after building the players list on the code behind file:

var dataProvider = (CollectionViewSource)FindResource("PlayerData");
dataProvider.Source = playerAges;

After setting up this grouped collection view source you will have to change the way binding is done on the DataGrid. Previously we were binding to a field on our view model. Now you will have to bind to a static resource.

(…)
<WpfToolkit:DataGrid
    ItemsSource="{Binding Source={StaticResource PlayerData}}"
    HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
    CanUserAddRows="False" CanUserDeleteRows="False"
    CanUserResizeRows="False" CanUserSortColumns="False"
    AutoGenerateColumns="False"
    RowHeaderWidth="17" RowHeight="25">
 (…)

The only thing left is for you to create the style of the group’s header. An expander will be used to present the grouped data. On the expander’s header each team’s name will be displayed:

<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type GroupItem}">
                <Expander IsExpanded="True" 
                          Background="{StaticResource GroubHeaderBackgroundBrush}"
                          Foreground="{StaticResource DefaultControlForegroundBrush}">
                    <Expander.Header>
                        <TextBlock Text="{Binding Name.Name}"/>
                    </Expander.Header>
                    <ItemsPresenter />
                </Expander>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now that everything is in place you have to setup the DataGrid to use this specific style when rendering the groups:

<WpfToolkit:DataGrid.GroupStyle>
    <GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
        <GroupStyle.Panel>
            <ItemsPanelTemplate>

                <WpfToolkit:DataGridRowsPresenter/>
            </ItemsPanelTemplate>
        </GroupStyle.Panel>
    </GroupStyle>
</WpfToolkit:DataGrid.GroupStyle>

After these changes this is how our DataGrid is looking:

WpfToolkitDataGrid-ss016 
If you click on a group header so that it collapses its contents this is how it will look:

WpfToolkitDataGrid-ss017 
This implementation works well when you do not allow editing on you cells. If you allow the users to edit something on your DataGrid you will notice that the edited value goes to the bottom of the list instead of remaining in its actual row. The following screenshot is the outcome of editing the age of John Smith. Notice that John was on top and is now presented at the bottom of the Alpha Team player list.

WpfToolkitDataGrid-ss018

Our grouped data is being handled by ListCollectionView. I took a peek on the code for ListCollectionView using Reflector and found out that the source of this problem is that when you edit an item the item is resorted for grouping purposes like follows:

public void CommitEdit()
{
(…)
        if (this.IsGrouping)
        {
            this.RemoveItemFromGroups(item);
            this.AddItemToGroups(item);
        }
(…)
}

BAAM! Obviously that by removing and adding the item back again to the list will cause the item to be presented last. Not only does this affect the order of our list –which is not the intended result – it also deselects the row we are working with (I confess I did not take the time to see this through, I was short on time to finish up what I was working on time, thus the workaround presented on the following subject).

You can download a working sample of this code by following this link.

 

Fake grouping with the help of the ViewModel

The grouping method previously demonstrated, although of clear implementation, has several issues that could not be easily fixed. In my application I ended up using another method for grouping data, a more archaic method but will allow us greater control over how data behaves.

The actual grouping of data will be our view model’s responsibility. Group headers will be authentic rows but with a different style. The logic for hiding or showing rows accordingly to their groups being expanded or not will be controlled by the view model through data binding.

In the PlayerAge class – the data behind each row – there is a field named IsExpanded that is only set on Parent rows (the teams). We can control if the child rows are collapsed or not by adding a DataTrigger that binds to the IsExpanded property of the Parent row for each player.

There will be two different control templates: one for players and one for teams. You will have to create a new style for your DataGridRows. By default you will set the ControlTemplate to be the one regarding the Players:

<ControlTemplate TargetType="{x:Type WpfToolkit:DataGridRow}">
    <Border x:Name="DGR_Border"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            SnapsToDevicePixels="True">
        <WpfToolkit:SelectiveScrollingGrid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <WpfToolkit:DataGridCellsPresenter
                Grid.Column="1"
                ItemsPanel="{TemplateBinding ItemsPanel}"
                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            <WpfToolkit:DataGridDetailsPresenter 
                WpfToolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding 
                    RelativeSource={RelativeSource AncestorType={x:Type WpfToolkit:DataGrid}}, 
                    Path=AreRowDetailsFrozen,
                    Converter={x:Static WpfToolkit:DataGrid.RowDetailsScrollingConverter},
                    ConverterParameter={x:Static WpfToolkit:SelectiveScrollingOrientation.Vertical}}"
                Grid.Column="1" Grid.Row="1"
                Visibility="{TemplateBinding DetailsVisibility}" />
            <WpfToolkit:DataGridRowHeader 
                WpfToolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"  Grid.RowSpan="2"
                Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type WpfToolkit:DataGrid}}, 
                    Path=HeadersVisibility, 
                    Converter={x:Static WpfToolkit:DataGrid.HeadersVisibilityConverter}, 
                    ConverterParameter={x:Static WpfToolkit:DataGridHeadersVisibility.Row}}"/>
        </WpfToolkit:SelectiveScrollingGrid>
    </Border>
    <ControlTemplate.Triggers>
        <DataTrigger Binding="{Binding Parent.IsExpanded}" Value="False">
            <Setter TargetName="DGR_Border" Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

The most important part of this ControlTemplate is the DataTrigger on the bottom. Has you can see we set the visibility of our row based on if the parent is expanded or not.

Now you will have to add a new ControlTemplate for the parent rows (teams). For this purpose you will have to set a DataTrigger on your Style that checks whether the Parent of the bounded item is null or not – indicating that it is a parent, or not (for the complete style please see the sample code for this post).

 

<Style.Triggers>
    <DataTrigger Binding="{Binding Parent}" Value="{x:Null}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridRow}">
                    <Border x:Name="DGR_Border"
                            Background="{StaticResource GroubHeaderBackgroundBrush}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            SnapsToDevicePixels="True">
                        <WpfToolkit:SelectiveScrollingGrid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>

                            <WpfToolkit:DataGridCellsPresenter
                                Grid.Column="1"
                                Background="Red"
                                ItemsPanel="{TemplateBinding ItemsPanel}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            <WpfToolkit:DataGridDetailsPresenter
                                WpfToolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding
                                    RelativeSource={RelativeSource AncestorType={x:Type WpfToolkit:DataGrid}},
                                    Path=AreRowDetailsFrozen,
                                    Converter={x:Static WpfToolkit:DataGrid.RowDetailsScrollingConverter},
                                    ConverterParameter={x:Static WpfToolkit:SelectiveScrollingOrientation.Vertical}}"
                                Grid.Column="1" Grid.Row="1"
                                Visibility="{TemplateBinding DetailsVisibility}" />
                            <WpfToolkit:DataGridRowHeader 
                                WpfToolkit:SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
                                Grid.RowSpan="2"
                                Visibility="{Binding RelativeSource={RelativeSource 
                                    AncestorType={x:Type WpfToolkit:DataGrid}},
                                    Path=HeadersVisibility,
                                    Converter={x:Static WpfToolkit:DataGrid.HeadersVisibilityConverter},
                                    ConverterParameter={x:Static WpfToolkit:DataGridHeadersVisibility.Row}}"/>
                        </WpfToolkit:SelectiveScrollingGrid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </DataTrigger>
</Style.Triggers>

The ControlTemplate’s trigger was removed given that this row is a parent and will not collapse. After inserting this new style for DataGridRows on the view’s resource dictionary, you will immediately see the result, were group headers display different from other cells. The problem is that we still have not implemented a mechanism that allows us to modify the expanded state of the group.

An example on how to control the IsExpanded state of your team is to create a specific CellStyle for the first column that by knowing which type of row it is binding to will display a toggle button that allows the user to expand or collapse the group:

<Style x:Key="FirstColumnCellStyle" TargetType="{x:Type WpfToolkit:DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridCell}">
                <Grid Background="{TemplateBinding Background}">
                    <ContentPresenter HorizontalAlignment="Right"
                                      VerticalAlignment="Center" Margin="0,0,4,0" />
                </Grid>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding ToleranceState}"
                                 Value="NotToleranced">
                        <Setter Property="IsEnabled" Value="False" />
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Parent}" Value="{x:Null}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridCell}">
                        <Grid Background="{TemplateBinding Background}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <ToggleButton VerticalAlignment="Center" 
                                          IsChecked="{Binding IsExpanded}" 
                                          Height="12" Width="12" Content="+" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>
</Style>

By now you will be able to expand or collapse each team’s players. This is how the DataGrid looks when these styles get applied.

WpfToolkitDataGrid-ss019

Now when you run the sample you can edit any cell and see that it does not change. I have kept the same style for the header rows but you can easily change it so that no cells are rendered on it, you could even make it look like the one on the previous topic using an expander.

You can download a working sample of this code by following this link.

 

End of Part IV

In this part we have delve into more advanced concepts. We have seen how to work around the issue with binding and the DataGridComboBoxColumn. We also saw the amazing power and flexibility of the DataGridTemplateColumn. The second half of the blog post walked you through two different methods of grouping data within the DataGrid.

By now, and assuming you have gone through all the IV parts, you should be able to represent any type of data as you see fit.

In the next and final part of this series we will look at DataGridCellDetails as well as handling insertion / removal of data from your DataGrid. Stay tuned for Part V Dear Reader!

Shout it kick it on DotNetKicks.com

83 thoughts on “WPF Toolkit DataGrid Part IV: TemplateColumns and Row Grouping

  1. Pingback: DotNetShoutout
  2. I would like to hear of your experience using the wpf toolkit datagrid to display data in a pivot table, or to display data that is defined with 3 object references. i.e. one dimension defines the columns, another the rows, and a third is the value of the cell at that point (and the cell background colour).

    I suspect I will have to bind the datagrid to an object tree rather than a DataTable in the code behind, but I am not sure how to construct it in LINQToSQL.

    Any tips would be great!

    Lynette

    1. Greetings Lynette!

      I have no experience doing pivot tables but, from what I understand, this is most likely a problem to be solved on the ViewModel, not on the View / DataGrid it self.

      I imagine you would have combos to specify which field should be analyzed on columns and which should be shown on rows (these can easily be embedded on the DataGrid by overriding its style). Then I would hook up the view model to switch the data displayed on the DataGrid to show the selected value (for instance, a sum) for each cell.

      I do hope this helps!

  3. hi
    I was wondering what it would take to do multi-level grouping. Example if there is a grouping on top of teams by division or city etc.
    I am able to do it but visually it does not look good as the group names are not indented if they are a child of the parent group.

    It shows up like this:
    + Division
    + Alpha Team

    I want it to show up like this:
    + Division
    + Alpha Team

    Any thoughts will be appreciated.
    I am not using MVVM currently just using ItemsControl.GroupStyle

  4. sorry the spacing was stripped out, putting in periods for spaces:

    I want it to show up like this:
    + Division
    ……….+ Alpha Team

    1. Hi Csaket. That would depend on how your data is structured. If you have a list of items where you say that “someone” is the parent of that item you could easily adapt the second grouping technique I present in this blog post. You would just need to have a way to bind the left margin of the cell content to its depth index.

      I am not sure that you can do the same with ItemsControl and its GroupStyle. Checkout HierarchicalDataTemplate and TreeViews. Even though check Bea Stollnitz blog for more information on how to do this.

      I hope this helps :)!

    1. Hi Joseph,

      Why use the ListView and not a DataGrid? 🙂 I am sure you will be able to reproduce this with the ListView, but it will give you a lot more trouble.

      The DataGrid has everything you need. Part of what you are asking is what I will be presenting in Part V (I am so late with this!!!) namely the use of DataGridDetailsPresenter. The DataGridDetailsPresenter can be used to show the details for each row (just like in the screenshot you were referring to).

      As for sorting I suggest having a look at this Vincent Sibal’s blog post.

      I hope this helps!

  5. Thanks a lot for the great post. I learn a lot from your blog. For the grouping problem of sample 14, you can resolve it by making IPlayerAge as IEditableObject. Then, you can edit an item in a group without any problem.

  6. Hi Alan! I thank You for the feedback. Always great to know about people I am reaching!

    Can you elaborate on how IEditableObject could prevent the items on a group from being sorted after editing?

    From what I can tell whenever an item changes the ListCollectionView forces the groups to be recalculated, hence removing and adding the item back again. Am I missing something? 🙂 🙂 🙂

    Thanks again for your great feedback!

  7. Samuel,

    This blog post may be able to answer your question:

    http://drwpf.com/blog/Home/tabid/36/BlogDate/2008-10-31/Default.aspx

    I had modified your sample by the adding the following to the PlayerAge:

    private PlayerAge m_Copy;

    public void BeginEdit()
    {
    if (m_Copy == null)
    m_Copy = new PlayerAge(Parent, Id, IsExpanded, IsEnabled, Name, Age, Deviation, Category, DeviationPercentage);
    }

    public void EndEdit()
    {
    m_Copy = null;
    }

    public void CancelEdit()
    {
    this.Parent = m_Copy.Parent;
    this.Id = m_Copy.Id;
    this.IsExpanded = m_Copy.IsExpanded;
    this.Name = m_Copy.Name;
    this.Age = m_Copy.Age;
    this.Deviation = m_Copy.Deviation;
    this.Category = m_Copy.Category;
    this.DeviationPercentage = m_Copy.DeviationPercentage;
    }

    In addition, I modify the IPlayerAge to:

    public interface IPlayerAge : IEditableObject

    After the modification, you can modify the cell and the modified record will not go to the bottom. You can try this.

    p.s. : I use the latest WPFToolbox for this test. Please use this to make sure that our testing environments are the same.

  8. One thing I would like to clarify. After you edit the whole record, the modified record will still be moved to the end of list. IEditableObject just prevent the record move to the end after you edit one cell as the whole record will be treated as a whole transaction.

    1. Well I have tested this and it seems not to fix the issue. Have you tried this on the sample application? Or are you presenting your own implementation adapted?

      If the latest, in your case it may be working if you are sorting your records by some field and that field does not change. When the groups are recreated on the ListCollectionView it re-sorts the rows and they seem not to change places on the UI.

      I have peaked ListCollectionView source code with Reflector once again and I see the special case with IEditableObject but it does not affect the code where the actual removing and adding for sorting purposes is done:

      public void CommitEdit()
      {
      if (this.IsAddingNew)
      {
      throw new InvalidOperationException(SR.Get("MemberNotAllowedDuringTransaction", new object[] { "CommitEdit", "AddNew" }));
      }
      base.VerifyRefreshNotDeferred();
      if (this._editItem != null)
      {
      object item = this._editItem;
      IEditableObject obj3 = this._editItem as IEditableObject;
      this._editItem = null;
      if (obj3 != null)
      {
      obj3.EndEdit();
      }
      if (this.IsGrouping)
      {
      this.RemoveItemFromGroups(item);
      this.AddItemToGroups(item);
      }
      (...)

      If for some reason your sorting is forcing this to update correctly then you will not notice the change!

  9. Let me try to clarify:

    Use IEditableObject solve the following issue in the original sample 14:
    1) After we edit one cell and we try to move to another cell in the same row. The row will be deselected and move to the buttom. The user need to go to the bottom to edit the other cells. We all agree that it is unacceptable to the user.

    The IEditableObject cannot avoid remove/add statements of the ListCollectionView. It just defers this action until the user finishes editing of the row. If the ListCollectionView has no sorting, the item will still move to the end of the collection. I think that this behavior is now acceptable to the user. If the order of the item is really important, maybe we need to add a sorting in the CollectionView.

    p.s.: I tried to check if the Xceed DataGrid has similar behavior but I cannot confirm. Xceed has its own CollectionView called DataGridCollectionView. When the user select grouping, Xceed DataGrid will sort according to the first column of the current datagrid view by default. Hence, the order of the data row can maintain the same position as long as the user didn’t modify the first column.

    1. OK! Gotcha! 🙂

      Since the issue that took me to come up with the workaround was my main focus on that topic I was blinded by it!

      Thank you so much for the effort on clarifying this one!

  10. Hi Samuel,
    I read all five parts of your blog today, and it is supperbe and very easy to understand.
    I was looking for DagaGrid sample and I got very good starting point. I am eagerly waiting for your next posting. If you can add sorting sample in your next post it will be great, all in one place 🙂
    Thanks for writing such a wonderful article!

    1. Hi GS! Thanks for the motivating feedback. It is great to know that the articles were easy to follow!

      I will include the sorting on my next DataGrid article – in fact this was already being accounted for :), thanks for the hint.

  11. Hi,
    I was wondering if you could possibly help me out with a ComboBox issue.

    In order to indicate to my user that he is dealing with a ComboBox I want to have the commonly used arrow on the right of my ComboBox. However, it seems to be that this arrow is not shown by default and my user is expected to know that he is dealing with a ComboBox. Performing a double-click will show the available options but without noticing it as a ComboBox I doubt that a user would try a double-click.

    Could you please point me in the right direction in order to get this arrow that indicates the ComboBox as being a ComboBox everytime?

    Every hint is deeply appreciated.

    THX for help

    1. Hi Sportscrazy,

      This is a very simple task to achieve. In regards to the sample application, all you have to do is change the CellTemplate to display a ComboBox instead of a Label (this is what I am doing on the sample), you can easily see it working if you change the specification of the column that displays the “Category” setting its CellTemplate to {StaticResource ComboBoxCellEditingTemplate}, this way the same template will be used for both editing and display.

      If you use the DataGridComboBoxColumn you will have to supply an ElementStyle that has a ComboBox, because by default it will not display it.

      I hope this helps you!

  12. hello again,

    you don’t by accident know whether or not it is possible to configure the highlighting (color) that is used to mark the selected row? The dark blue is not really good.
    Once again thank you very much for your help.
    I really appreciate this.

    1. Quite easy as well :D. Just add a DataTrigger to the style for DataGridRow’s in which you change the background color of your row when the item is selected or the mouse is over it.

      <Style TargetType="{x:Type dg:DataGridRow}">
          <Style.Triggers>
              <Trigger Property="IsMouseOver" Value="true">
                  <Setter Property="Background" Value="Red" />
              </Trigger>
              <Trigger Property="IsSelected" Value="true">
                  <Setter Property="Background" Value="Yellow" />
              </Trigger>
          </Style.Triggers>
      </Style>
  13. Hi,

    I think that it isn’t so easy as you said. The mouseover variant works very well but the selected one doesn’t. Is it possible that there is a default selection style which can’t be overwritten in this way?

    1. Check that you are not overriding the background on the DataGridCell or within either of its templates. You can use Snoop to check where the background of your cell is coming when selected.

  14. Hi,
    I am trying to change the DataGrid text from Black to red for example, but it seems that I am not succeeding.

    Can someone tell me how to change that ??

    Wael

  15. mm Sorry – Could not commit code to explain that, but it is so simple
    In BaseLabelCellStyle – set the foreground property to any color you want , and that it is.

  16. Hi Samuel,

    Just wondering, have you been able to resolve the editing problem when items are grouped? The one where the list is resorted?

  17. Hi Samuel

    Thanks for your tutorial.
    I like that datagrid grouping style. Only that editing problem is annoying. So I spend some time trying to solve it. The only solution I found is to implement sorting.
    If you add the code below to your xaml, the edit row will not move anymore.

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

  18. still unable to post the code…

    CollectionViewSource.SortDescriptions
    scm:SortDescription PropertyName=”Name” /
    /CollectionViewSource.SortDescriptions>

  19. Very good information! Thank you. However my problem is that when I add columns from codebehind that all uses the same CellStyleTemplate with bindings (for example 5 columns of the same type), it appears that it binds to all cells in the row when the first item is populated.. The result is that all columns in a row repeats the first element added. My theory is that when a cell is added, the rest of the cells in that row also gets added and binds to thefirst element added also (after all binding Ids are the same in all cells since they all derive from the same CellStyle). This is not happening if all columns has different styles and bindings of course as in your example.. Do you have any hint to solve this??

  20. Hi,

    first, thanks for your great articles!

    I tried your DataTrigger to the style for DataGridRow’s to change the background and it generally seems to work, but I have specified a AlternatingRowBackground color for my DataGrid and now the colors that are being set by the style’s triggers only become applied on the 1st, 3dr, 5th row etc. but not on the “alternate” rows.

    I’d love to have all the rows highlighted on mouseover and selection, idially with different colors for normal and alternate rows.

    Do you have any idea how to achive this?

    Thanks!
    Karlo

    1. @Nat I think it is not supported natively to accomplished you would for certain need to build your own version of the source. I have done it previously to accommodate some special needs I had for particular features (ex.: allowing the user to change cell focus when multiple cells are selected without loosing selection).

  21. @Karlo You are probably specifying your AlternatingRowBackground Brush when you declare the DataGrid. This has an higher precendence then what is specified on styles.

    If you move your AlternatingRowBackground to a style instead of specifying it on the DataGrid node you will see that the above trigger works for both even and odd rows. 😉

  22. Hi!!,

    i’m working on similar project where in it required a data entry page with a datagrid. it allows to enter data into the grid by group like Task/Sub Task/Activity.
    you support would be a greate help for me as i’m to wpf

    Thanks
    Ahmed

  23. @Ahmed In this case you would probably do better handling all this in the view model (check the MVVM pattern). You would have to somehow differentiate your rows from each other (having a field for the row type) and give them a style triggered accordingly to this field. Something like I do with the groups but more enhanced to support subgroups. The logic is the same though!

  24. Hi.
    Im having bit of a problem. I’m currently trying this out in a test project but when im trying to run the application i get an error stating that i cannot create an instance of my usercontrol !

    hope someone can help me
    (got no build errors, runtime error)

  25. @Mikael This is pretty vague I cannot help you this way. It is not a reference problem because if it was the project would not compile. Set a BreakPoint on your UserControl constructor and check if it gets hit.

  26. I’ve learned so much from following this series… really appreciate it. Thanks for sharing your knowledge with us !
    I guess a few things are no longer required since the june release of the WPF Toolkit (such as combo-box binding). Do you think you’ll write a quick update on that?

  27. Hi Samuel, thanks for the great series, I’ve used this a lot.

    One of the things I’ve added based on this article is a column type that has either a TextBox or ComboBox depending on if the data provides a list of possible values. The cell editing template changes the visibility of the 2 to the relevant widget. The problem with it is that it takes the user 3 clicks in the cell to be allowed to change the value, either via the TextBox or getting the ComboBox to expand. Tedious and frustrating!

    Any ideas why? Or do you have a sample that does something similar?

    Thanks!

    Janene

  28. @Janene McCrillis To handle this I would create a custom column that sets focus on the proper control when preparing the cell for edition. For this you would override PrepareCellForEdit. Something like this:

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
    var comboBox = (ComboBox) CellEditingTemplate.FindName(“PART_comboBox”, editingElement);
    if (comboBox.IsEnabled)
    {
    comboBox.IsDropDownOpen = true;
    comboBox.Focus();
    }
    return (bool?) false;
    }

    This can be applied to a TextBox as well.

  29. I spent the last 4 days googling and researching about the WPF DataGrid control (I need to write a transposed, highly styled DataGrid template), and I can now say that this series of articles is the most comprehensive, helpful, friendly and proficient source of knowledge for the DataGrid control available today.

    Well done!

  30. Samuel

    many thanks for posting this. I have a question which is not directly related to the stuff here but I did not get an answer from codeplex and codeproject, so maybe I give it a try here:

    I have a datatemplatecolumn because I need a textbox and a button in one column
    First question: is there an alternative? because I do not have a real binding for the column which turnes out to be a problem (see below)
    Second question: In this datatemplatecolumn I additionally have a filter textbox in the column header. In the code for the filtering I have to retrieve the property to which the textbox (the first one besides the button) is bound. For the other “normal” columns I can read the binding path, but how do I get this from the datagridtemplatecolumn ?
    Any help would be appreciated
    Kind regards

    Klaus

  31. @Klaus Wiesel
    I am not sure I understand your problem. Are you trying to add filtering capabilities to your column? If so, what I would try first would be to create a specific DataGridColumn HeaderStyle to add the filtering TextBox along with the Filter Button.

    I would then bind this filter button to a command that would take the Text from the TextBox as a CommandParameter. I would then process the filtering on my ViewModel.

  32. @Samuel

    No, I already have the filtering. I have a textbox in each column’s header and the filtering is started when the user stops entering text in the filtering textbox. This is working fine for all columns except the datagirdtemplatecolumns.

    In the filtering functionality I retrieve the column’s binding, get the binding’s path and use this for the filtering .

    But the datagridtemplatecolumn does not have a binding (because I am free of what to put in there). In my case I have a textbox and a button in there (the button simply calls a lookup window which is used to select the content of the textbox).

    So my questions are:
    1) Is there an alternative (without the template column, maybe a special form of a bound column) to get a textbox and a button side by side in one column ?
    2) If the answer to 1) is no then: How can I get the binding (= the property name) of the textbox in the template column at runtime to have something for my filtering function
    3) As an alternative, I thought about naming my columns in a way that the name is linked with the bound property (eg Name = “dgcItemName” refers to the bound property “ItemName” so that if I could retrieve the name I cound easily get the bound property from it). My problem: Although I set the x:Name (in Xaml) for the template column I have problems to find this name when debugging: I use the visual debugger “mole” and have no clue where the value “dgcItemName” can be found at runtime

    Hope that clears my point
    Thanks for your support and the good work …
    Klaus

  33. @Klaus Wiesel
    OK! Now I got it! I would definitely go for solution 1.

    1. I would create a small user control that has the TextBox and the Button for look-up, lets call it LookUpTextBox.

    2. Since this is a sort of TextBox column with an additional functionality that allows the user to look-up some value, it seems acceptable to extend the DataGridTextColumn. I would create an extended column from this one – DataGridLookUpTextColumn.

    3. Then I would override the GenerateElement and GenerateEditingElement, in order to generate LookUpTextBoxes.

    Finally. I would then have access to the Binding when the filter needs to be invoked, pretty much has what you do for the remaining columns.

    I hope this helps out. If not, it would be great if you could gather some sort of sample you could share so that I can take a better look.

    Thanks,
    -Samuel

  34. @Samuel

    Thank you so much for your approch.

    I would like to try your suggestion, but regarding my 2) and 3): Isn’t there an easy way to get these info?

    Regards
    Klaus

  35. @Samuel

    Hi, I started but did not get as far as I wished

    I created the usercontrol, but have problems of defining the binding.

    My usercontrol so far:

    and the code:
    Partial Public Class ButtonTextbox

    Public Shared ReadOnly BindingProperty As DependencyProperty = DependencyProperty.Register(“Binding”, _
    GetType(Binding), GetType(ButtonTextbox), New FrameworkPropertyMetadata(Nothing))

    Public Property Binding() As Binding
    Get
    Return CType(GetValue(BindingProperty), Binding)
    End Get

    Set(ByVal value As Binding)
    SetValue(BindingProperty, value)
    End Set
    End Property

    Public Sub New()

    InitializeComponent()
    Me.DataContext = Me

    End Sub

    Now the extended column:
    Public Class TextBoxButtonColumn
    Inherits DataGridTextColumn

    Protected Overloads Overrides Function GenerateElement(ByVal oCell As DataGridCell, ByVal oDataItem As Object) As FrameworkElement

    Dim oElement = MyBase.GenerateElement(oCell, oDataItem)
    Return oElement

    End Function

    Protected Overloads Overrides Function GenerateEditingElement(ByVal oCell As DataGridCell, ByVal oDataItem As Object) As FrameworkElement

    Dim oBinding As New Binding
    oBinding.Path = TryCast(Me.Binding, Binding).Path
    oBinding.Source = TryCast(Me.Binding, Binding).Source
    Dim oButtonTextbox As New ButtonTextbox
    oButtonTextbox.txSelection.SetBinding(TextBox.TextProperty, TryCast(Me.Binding, Binding))

    Return oButtonTextbox

    End Function

    Public Property SqlText() As String
    Get
    Return CStr(GetValue(SqlTextProperty))
    End Get
    Set(ByVal value As String)
    SetValue(SqlTextProperty, value)
    End Set
    End Property

    And now the UI that uses the column :

    As you may see I am not quite sure how to implement GenerateEditingElement.
    I tried the snippet above, but also tried to retrieve the complete textbox from oCell and set oButtonTextbox.TextBox with this but the results remains the same: The column in non-editing mode it shows the correct data, in editing mode is empty and input is not going back to the column, the binding is not correcty set up

    Regards
    Klaus

  36. @Samuel

    sorry, the xaml got swallowed, here the xaml again for the usercontrol
    [Grid]
    [Grid.ColumnDefinitions]
    [ColumnDefinition /]
    [ColumnDefinition Width=”24″ /]
    [/Grid.ColumnDefinitions]
    [TextBox Grid.Column=”0″ x:Name=”txSelection” Text=”{Binding Binding}”/]
    [Button Grid.Column=”1″ x:Name=”pbSelect” Click=”pbSelect_Click”]
    [Image Width=”24″ Source=”{StaticResource imgView}” /]
    [/Button]
    [/Grid]

    and here the consuming UI

    [local:TextBoxButtonColumn Header=”TextBoxButtonColumn” Binding=”{Binding ItemName}”]

    [/local:TextBoxButtonColumn]
    Thanks again
    Klaus

  37. Your example in “Grouping rows using ItemsControl.GroupStyle” has been extremely helpful! Thank you!

    I’m looking for a way to expand or collapse all the groups simultanously anytime the user wants to (e.g. from a button click). Nothing I’ve tried works, so I wondered if you’d have any suggestions?

    Thanks again for your work. Your four tutorials on the WPF DataGrid have been terrific!

  38. @Klaus Wiesel Just to wrap up. I know you have managed to tackle this… 😀

    The problem I believe is that you need to create an instance of your user control on the GenerateEditingElement or return the same if it is not different from the Element itself (perhaps caching the reference, no need to create to separate instances).

  39. I was not able to display text in the group header until I realized, that I had to use the prefix “Name.” before my own property. (As you did in your example, but Name.Name was confusing). So to everyone who has this problem, you have to use {Binding Name.YourProperty}.

  40. Thanks for the article!

    I’m having some difficulty extending your example by displaying a UserControl in all the Parent row cells. I’ve assumed I’ll have to use all TemplateColumns in the table, with TextBox and CheckBox where appropriate for the child rows, and my UserControl for parent rows. If I’m making any sense, does it sound like I’m on the right track?

    Thanks again for your article – It’s been a great help in the last few days!

    Cheers,
    Dave

  41. Hi Samuel,
    Thanks for this article, it’s very good.
    I downloaded your sample for simulating groupings via the ViewModel, and got it to build with .NET 4.0, but the ToggleButtons for expanding and collapsing the groups don’t seem to do anything. It works fine in VS2008 using .NET 3.5
    Any ideas?
    Cheers,
    Steve

  42. Bit of extra info, it seems that ToggleButton.IsChecked does not update the binding to IsExpanded.

    If I create a button separately which sets this IsExpanded property, the group expands and collapses as expected…

  43. Samuel

    Can you tell us something about the functional change MS did here?

    Steve

    It seems that MS changed something in the binding mechanism from wpftoolkit to net4.

    I had to set the UpdateSourceTrigger to PropertyChanged for the Togglebutton like so IsExpanded =”{Binding IsExpanded,UpdateSourceTrigger=PropertyChanged}”

    After that it works again

    @Steve

  44. @Klaus

    Thanks Klaus, that did indeed do the trick.

    I have one other issue with this now. It seems that sometimes if I have one row in a subgroup, the group will collapse properly but a blank row is left at the very bottom of the datagrid.

    I have tried this in Snoop, and setting the visibility of a single-row group’s DGR_Border to Collapsed replicates this behaviour.

    Any ideas?

    Thanks once again for a great article, and to Klaus for the fix.

  45. @Steve

    I’ve confirmed this in your sample if you create only player in Beta Team, and put the datagrid in a stackpanel with something else below it, you will see that the data grid only changes size when the group with more than one item in it is collapsed.

  46. Thanks a lot. This series of articles was very helpful to me.
    I do have one question though. I checked “CanUserSortColumns” so that a user can click on a column header and sort by it’s contents.
    Using the code in “WPF Toolkit DataGrid Sample 03 – Styling the DataGrid”, this works as expected and a caret is displayed in the header to show the column and direction of the sort.

    However, in “WPF Toolkit DataGrid Sample 04 – Styling DataGridColumnHeaders” the sort works but the caret is missing. Is there a way to restore the caret display to the styled DataGridColumnHeader?

    Thanks,
    George

Leave a Reply

Your email address will not be published. Required fields are marked *