WPF Toolkit DataGrid, Part III – Playing with Columns and Cells

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

Introduction

In Part II we gave a bluish style to our DataGrid. In this part we will go through setting up how our data gets displayed within the DataGrid. We will have a look on how to create styles for our cells and cell elements along with creating some new types of DataGridColumns that extend existing ones in order to enrich them with further functionality.

 

Roadmap

  1. Aligning DataGridColumnHeaders and DataGridCells content using Styles
  2. Aligning DataGridCells content by extending a DataGridColumn
  3. Enabling and disabling DataGridRows
  4. Creating a LabeledTextBoxColumn
  5. Creating an AutoCommitCheckBoxColumn

Download sample source code

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

Aligning DataGridColumnHeaders and DataGridCells content

If you are using the DataGrid then you will surely want to control the alignment of text within the cells and column headers. Unfortunately the WPF Toolkit DataGrid does not support setting content alignment on a DataGridColumn. To align cells content you will have to create specific styles for DataGridColumnHeaders based on their default style and assign them individually to each column.

By the end of Part II this was how our DataGrid was looking:

WpfToolkitDataGrid-ss010

To be able to align DataGridColumnHeaders content go to your “DataGrid.Generic.xaml” file and add a couple of styles for right and center aligned column headers:

<!-- Right Aligned DataGridColumnHeader Style -->
<Style x:Key="RightAlignedColumnHeaderStyle" 
       TargetType="{x:Type WpfToolkit:DataGridColumnHeader}"
       BasedOn="{StaticResource ColumnHeaderStyle}">
    <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
<!-- Center Aligned DataGridColumnHeader Style -->
<Style x:Key="CenterAlignedColumnHeaderStyle" 
       TargetType="{x:Type WpfToolkit:DataGridColumnHeader}"
       BasedOn="{StaticResource ColumnHeaderStyle}">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>

With these styles defined you be able to apply them to your column headers by changing their design. For this you will need to set the DataGridColumnHeader.HeaderStyle property. In your sample go ahead and set the Age and Deviation columns HeaderStyle to the RightAlignedColumnHeaderStyle and the Deviation Chart to CenterAlignedColumnHeaderStyle:

(…)
<WpfToolkit:DataGridTextColumn
    Header="Age" Width="1*"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    Binding="{Binding Path=Age}"/>
(…)

After changing the DataGridColumnHeaders you will have to specify styles for our cells so that they also get aligned. In you resources file create specific styles to align the content vertically on the center and horizontally on the right, another style to align it horizontally on center and another to align it on the left:

(…)
<!-- Left Aligned DataGridCell Style -->
<Style x:Key="LeftAlignedCellStyle" TargetType="{x:Type WpfToolkit:DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridCell}">
                <Grid Background="{TemplateBinding Background}">
                    <ContentPresenter HorizontalAlignment="Left"
                                      VerticalAlignment="Center"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
(…)

As done previously with the DataGridColumnHeaders, you will now have to assign each style to the corresponding DataGridColumn. Go ahead and set the PlayerName’s CellStyle to LeftAlignedCellStyle – since we do need to align its content vertically. Then you will have to set Age and Deviation CellStyles to RightAlignedColumnHeaderStyle. Enabled and DeviationChart will be center aligned so you will have to set the corresponding CellStyle on their DataGridColumns.

<WpfToolkit:DataGridCheckBoxColumn
    Header="Enabled" Width=".5*"
    CellStyle="{StaticResource CenterAlignedCellStyle}"
    Binding="{Binding Path=IsEnabled}"/>
<WpfToolkit:DataGridTextColumn
    Header="Player Name" Width="2*"
    CellStyle="{StaticResource LeftAlignedCellStyle}"
    Binding="{Binding Path=Name}"/>
<WpfToolkit:DataGridTextColumn
    Header="Age" Width="1*"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    CellStyle="{StaticResource RightAlignedCellStyle}"
    Binding="{Binding Path=Age}"/>
<WpfToolkit:DataGridTextColumn
    Header="Deviation" Width="1*"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    CellStyle="{StaticResource RightAlignedCellStyle}"
    Binding="{Binding Path=Deviation}"/>
<WpfToolkit:DataGridComboBoxColumn
    Header="Category" Width="1*"
    ItemsSource="{DynamicResource Categories}"
    SelectedValueBinding="{Binding Path=Category}"
    TextBinding="{Binding Path=Category}" />
<WpfToolkit:DataGridTextColumn
    Header="Deviation Chart" Width="1*"
    HeaderStyle="{StaticResource CenterAlignedColumnHeaderStyle}"
    CellStyle="{StaticResource CenterAlignedCellStyle}"
    Binding="{Binding Path=DeviationPercentage}"/>

After correctly aligning our cells this is how our DataGrid is looking:

WpfToolkitDataGrid-ss011

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

 

Aligning DataGridCells content by extending a DataGridColumn

A little background before moving on! Each cell can be in either two states: normal state or editing state. For each of these states we can specify the template to be used (by setting DataGridColumn.ElementTemplate and DataGridColumn.EditingElementTemplate) or apply to them a specific style (by setting DataGridColumn.ElementStyle and DataGridColumn.EditingElementStyle). CellStyles are applied to the DataGridCell itself, not the elements within it. This brings us to why our previous solution has some undesired side effects.

If you noticed when you select a cell for editing you see that the Textbox does not occupy the full content of the DataGridCell. The reason for this is that the new style we have defined for our cells applies the alignment to the TextBox within the cell and not to its content.

This is WPF – 10 different ways for doing the same thing. One solution to this, and a more clean solution than the previous one, is to extend the DataGridTextColumn giving it support for content alignment by adding a couple of properties that can be used to set the alignment on the generated elements for each cell. For this you will need to add new class – ExtendedTextBoxColumn – that extends DataGridTextColumn and add to it a couple of properties – HorizontalAlignment and VerticalAlignment. Afterwards you must override the GenerateElement and GenerateEditingElement methods in order to intercept the creation of the corresponding cell elements and set the values accordingly to the ones specified when the DataGridColumn was created.

protected override FrameworkElement 
    GenerateElement(DataGridCell cell, object dataItem)
{
    var element = base.GenerateElement(cell, dataItem);

    element.HorizontalAlignment = HorizontalAlignment;
    element.VerticalAlignment = VerticalAlignment;

    return element;
}

protected override FrameworkElement 
    GenerateEditingElement(DataGridCell cell, object dataItem)
{
    var textBox = (TextBox)base.GenerateEditingElement(cell, dataItem);

    textBox.TextAlignment = GetTextAlignment();
    textBox.VerticalContentAlignment = VerticalAlignment;

    return textBox;
}

Please note that the GetTextAlignment method is just a helper method that maps a HorizontalAlignment to a TextAlignment. The only thing remaining is for you to replace the DataGridTextColumns by the newly created ExtendedTextColumn and remove its CellStyle:

<WpfToolkit:DataGridCheckBoxColumn
    Header="Enabled" Width=".5*"
    CellStyle="{StaticResource CenterAlignedCellStyle}"
    Binding="{Binding Path=IsEnabled}"/>
<Controls:ExtendedTextColumn
    Header="Player Name" Width="2*"
    HorizontalAlignment="Left" VerticalAlignment="Center"
    Binding="{Binding Path=Name}"/>
<Controls:ExtendedTextColumn
    Header="Age" Width="1*"
    HorizontalAlignment="Right" VerticalAlignment="Center"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    Binding="{Binding Path=Age}"/>
<Controls:ExtendedTextColumn
    Header="Deviation" Width="1*"
    HorizontalAlignment="Right" VerticalAlignment="Center"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    Binding="{Binding Path=Deviation}"/>
<WpfToolkit:DataGridComboBoxColumn
    Header="Category" Width="1*"
    ItemsSource="{DynamicResource Categories}"
    SelectedValueBinding="{Binding Path=Category}"
    TextBinding="{Binding Path=Category}" />
<Controls:ExtendedTextColumn
    Header="Deviation Chart" Width="1*"
    HorizontalAlignment="Center" VerticalAlignment="Center"
    HeaderStyle="{StaticResource CenterAlignedColumnHeaderStyle}"
    Binding="{Binding Path=DeviationPercentage}"/>

This is pretty basic stuff but replaces all of our styles when using text columns. It also solves the issue with the size of the Textbox for the EditingElement when in editing mode. On the left there is a screenshot of how a cell looked when in edit mode, on the right we have the cell being edited using ExtendedTextColumn:

WpfToolkitDataGrid-ss012

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

 

Enabling and disabling DataGridRows

Now let’s go and add some functionality to our DataGrid. The requirement is a simple one: whenever the user checks the CheckBox on the first column, the DataGridRow should be disabled, and he must not be able to edit the contents of any cell.

Although this is a simple requirement there is no direct way of doing it without creating a specific editing element style per column type. For each Player we have an IsEnabled field to which we bind our column in order to populate the CheckBoxes. We then have to bind each of the controls in the elements and disallow the editing on the cell based on the corresponding IsEnabled value.

To accomplish this, go ahead and add three new styles to the resources of your UserControl (these styles can be defined locally because they are specific and will probably make no sense outside this context), one for each type of control you use o your columns – TextBlock, TextBox and ComboBox.

<Style x:Key="BaseTextBlockCellStyle" TargetType="{x:Type TextBlock}">
    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
<Style x:Key="BaseTextBoxCellStyle" TargetType="{x:Type TextBox}">
    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
<Style x:Key="BaseComboBoxBoxCellStyle" TargetType="{x:Type ComboBox}">
    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>

Having defined the styles, go through the DataGridColumn specifications and set their ElementStyle and EditingElementStyle to the corresponding resources:

(…)
<Controls:ExtendedTextColumn
    Header="Deviation" Width="1*"
    HorizontalAlignment="Right" VerticalAlignment="Center"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    ElementStyle="{StaticResource BaseTextBlockCellStyle}"
    EditingElementStyle="{StaticResource BaseTextBoxCellStyle}"
    Binding="{Binding Path=Deviation}"/>
<WpfToolkit:DataGridComboBoxColumn
    Header="Category" Width="1*"
    ItemsSource="{DynamicResource Categories}"
    SelectedValueBinding="{Binding Path=Category}"
    ElementStyle="{StaticResource BaseComboBoxBoxCellStyle}"
    EditingElementStyle="{StaticResource BaseComboBoxBoxCellStyle}"
    TextBinding="{Binding Path=Category}" />
(…)

Now when we run the sample this is what we see when we try editing a value which we have disabled for editing:

WpfToolkitDataGrid-ss013

As you can see we have unchecked player “John Mufin” and the player’s name edit box is disabled.

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

 


Creating the LabelTextBoxColumn

If you run the sample and uncheck a row, you will not notice changes in the row until you select a field to edit, even though the TextBlock IsEnabled property was set to false. We could create a new style for TextBlock and change its foreground based on its enabled state. I have chosen another approach. Let’s change the cells element to be a Label instead of a TextBlock since the Label has already support for enable. When we set IsEnabled to false on a Label it will display a gray foreground.

Go ahead and add a new class that extends our ExtendedTextBoxColumn and name it LabelTextBoxColumn.

public class LabelTextBoxColumn : ExtendedTextBoxColumn
{
    private void ApplyStyle(bool isEditing, bool defaultToElementStyle, 
        FrameworkElement element)
    {
        var style = PickStyle(isEditing, defaultToElementStyle);
        if (style != null)
            element.Style = style;
    }

    private Style PickStyle(bool isEditing, bool defaultToElementStyle)
    {
        var style = isEditing ? EditingElementStyle : ElementStyle;
        if (isEditing && defaultToElementStyle && (style == null))
            style = ElementStyle;
        return style;
    }

    private void ApplyBinding(DependencyObject target, 
        DependencyProperty property)
    {
        var binding = Binding;
        if (binding != null)
            BindingOperations.SetBinding(target, property, binding);
        else
            BindingOperations.ClearBinding(target, property);
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, 
        object dataItem)
    {
        var label = new Label
{
     HorizontalAlignment = this.HorizontalAlignment,
     VerticalAlignment = this.VerticalAlignment
}; ApplyStyle(false, false, label); ApplyBinding(label, ContentControl.ContentProperty); return label; } }

This class is of very simple implementation. In fact we are only replacing the GenerateElement method with a custom implementation that creates a Label instead of a TextBlock. The ApplyStyle, PickStyle and ApplyBinding methods are all part of WPF Toolkit, I had to copy them to this sample since WPF Toolkit does not expose them publically – they are either private or internal and part of DataGridTextBoxColumn.

Since you now have a new type of column a new style must be added to bind the LabelTextBoxColumn Label element to the IsEnabled property of the current player. In fact just change the style you have for the TextBlock so that it applies to Labels:

<Style x:Key="BaseLabelCellStyle" TargetType="{x:Type Label}">
    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>

Now you have to change your DataGrid to use this new LabelTextBoxColumn. Just replace all the ExtendedTextBoxColumn by the LabelTextBoxColumn. You will also have to replace the ElementStyle by the new BaseLabelCellStyle:

<Controls:LabelTextBoxColumn
    Header="Age" Width="1*"
    HorizontalAlignment="Right" VerticalAlignment="Center"
    HeaderStyle="{StaticResource RightAlignedColumnHeaderStyle}"
    ElementStyle="{StaticResource BaseLabelCellStyle}"
    EditingElementStyle="{StaticResource BaseTextBoxCellStyle}"
    Binding="{Binding Path=Age}"/>

After these changes when you uncheck the Enabled CheckBox the row will turn gray and you will have an obvious feedback that it is disabled:

WpfToolkitDataGrid-ss014

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

 

Creating an AutoCommitCheckBoxColumn

By default the DataGrid only allows you to check a CheckBox on a column when this column is in editing mode. Basically, in order for you to disable a row you will have to uncheck the CheckBox, move the focus out of the cell and only then will your change be committed.

What a user would expect of this column was for it to be effective on the first click. He is not expecting to have to click the CheckBox and only see the results of the commit after he clicks away from this control in order for it to lose focus. This is where the AutoCommitCheckBoxColumn comes into play.

We will be creating a type of column that is tightly bounded to the CheckBox it generates listening to its changes and committing them to the DataSource.

public class AutoCommitCheckBoxColumn : DataGridCheckBoxColumn
{
    private void checkBox_Unchecked(object sender, RoutedEventArgs e)
    {
        CommitCellEdit((FrameworkElement)sender);
    }

    private void checkBox_Checked(object sender, RoutedEventArgs e)
    {
        CommitCellEdit((FrameworkElement)sender);
    }

    protected override FrameworkElement GenerateEditingElement(
        DataGridCell cell, object dataItem)
    {
        var checkBox = (CheckBox)base.GenerateEditingElement(cell, dataItem);

        checkBox.Checked += checkBox_Checked;
        checkBox.Unchecked += checkBox_Unchecked;

        return checkBox;
    }
}

We just have to hook the Checked and Unchecked events and force a commit on the cell. Now whenever the user clicks the CheckBox he will have an instant response giving him a higher impression of feedback.

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

 


End of Part III

I hope you have enjoyed this new post on the WPF Toolkit DataGrid, it has been a pleasure writing it! In this part we have seen how to create custom DataGridColumns. Our main focus was on how to correctly display data and allow the user to interact with it. We have seen how to control each element rendered within a cell.

On part IV we will play with DataGridTemplateColumns and get our team grouping to work. Stay tuned Dear Reader!

Shout it kick it on DotNetKicks.com

36 thoughts on “WPF Toolkit DataGrid, Part III – Playing with Columns and Cells

  1. Pingback: DotNetShoutout
  2. Hi,
    Thats a grt article… plz continue the excellent work’.

    could you give us some information about TREEVIEW control in WPF..

    LIKE How to add nodes, n How to add images to those nodes..
    Thank you

    1. Hi Venu! I am glad you liked the article. Playing with the TreeView could be the basis for a great WPF article in the future. But time is not an abundant resource in these days :), since currently I am working on the DataGrid series. Have a look at Bea Stollnitz blog , she has a series of articles on the TreeView.

    1. Hi Vijayanand! I am very glad to ear that the articles have helped you.

      My approach on this would be to hook up the Checked event on the CheckBox and changing all the CheckBoxes on the ViewModel.

      First you might think on directly binding all the DataGrid CheckBoxes to a master CheckBox using a DataTrigger but this would imply that the master CheckBox would always dictate the state for all CheckBoxes (you would not be able to toggle them individually). If you use direct binding instead of a DataTrigger you would have all CheckBoxes linked together, this way whenever you hit a CheckBox it would toggle all of them.

  3. I really enjoy this series. I am hoping to use the WPF datagrid control in the near future, but for a lack of time to learn the control I am waiting for articles just like this to speed up the learning process for me. Currently I use Listboxes with a custom data template for the itemsource to create datagrid like features but as you can imagine it is a cumbersome process. As a software development company, we just dont have this sort of time available to go through aligning controls to make them look like columns etc. Cant wait for the next article

    1. Hi Rory. Before using the WPF Datagrid I also adapted another control to do the work, in my case I used the ListView. But that just was not designed to be a DataGrid. There was so much or little one could do! 🙂

  4. Dear Mr.Moura
    Thank you for your great work.
    You know I can follow you article for my working.

    I have a question to ask.
    I would like to show a decimal value in [#0.00] format in the cell.
    In the Editing mode, it can be. But in the showing mode, it can’t.
    Would you tell me how can I do it?

    The following is my xaml’s define:

    Thanks for your help.

    1. Hi Hongdaorong. You’re welcome!

      You cannot add Xaml here. If you wish to please attach the file through a separate link. This should be a pretty straight forward issue to fix. If this is a LabelTextBoxColumn, please know that the label is created on the column code, not in the xaml. Either way you can always use an IValueConverter or StringFormat part of the Binding markup extension. Hope this helps!

  5. I port the sample program to WPF4. The AutoCommitCheckBoxColumn works fine in WPF3.5 but not WPF4. Anyone has the solution?

  6. Nice job on the AutoCommitCheckBox. I don’t understand what MS is thinking some times. You would think a simple Binding=”{Binding Active, UpdateSourceTrigger=PropertyChanged}” would do the trick.

    Thanks again

  7. Great, great!
    But one question: In each row is an exclamation point, although the validation is ok. What can be the reason?

  8. @Samuel Moura
    I’v found the problem: my BoolToVisibilityConverter tested the 3rd Argument (object parameter):
    if (parameter == null)
    return Binding.DoNothing;

    This generates the exclamation. (it’s .Net3.5).

  9. Samuel, can you give me an sample, how to implement an ValidationErrorTemplate in the context of your styles? I try’d this, but it does not work 🙁

  10. I am having issues getting the autocommitcheckboxcolumn to work. I keep getting “no source available” debugger messages when trying to debug on the datagrid events so it makes things difficult to tell if my edit is working.

    I am not using the PlayerAge/IPlayerAge data model. I am using a sql query to fill a datatable and assigning that datatable to the datagrid ItemsSource. What am I not doing correctly? My row goes into disabled state after I move the row focus. It seems as if when I call CommitEdit it does not update the GUI. I tried filling a bindinglistcollectionview and then setting the grid’s itemssource to the bindinglistcollectionview but I got the same behavior when disabling a row.

    I am getting these output messages:
    System.Windows.Data Error: 39 : BindingExpression path error: ‘IsEnabled’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=IsEnabled; DataItem=’Object’ (HashCode=30245787); target element is ‘CheckBox’ (Name=”); target property is ‘IsChecked’ (type ‘Nullable`1’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘IsEnabled’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=IsEnabled; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘IsEnabled’ (type ‘Boolean’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘Parameter’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=Parameter; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘Content’ (type ‘Object’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘IsEnabled’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=IsEnabled; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘IsEnabled’ (type ‘Boolean’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘Value’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=Value; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘Content’ (type ‘Object’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘IsEnabled’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=IsEnabled; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘IsEnabled’ (type ‘Boolean’)
    System.Windows.Data Error: 39 : BindingExpression path error: ‘Description’ property not found on ‘object’ ”Object’ (HashCode=30245787)’. BindingExpression:Path=Description; DataItem=’Object’ (HashCode=30245787); target element is ‘Label’ (Name=”); target property is ‘Content’ (type ‘Object’)

  11. @Tanner
    I was able to fix the autocommitcheckbox behavior after binding my query to a observablecollection instead of datatable.

    I am guessing that the problem was that my datatable did not implement a PropertyChangedEvent so the UI never knew there was a change?

    There is still much to learn regarding wpf. Thank you for putting these articles together!

Leave a Reply

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