Tags

,

or, how to change the background of a cell in a WPF Toolkit DataGrid data-bound to a DataTable.

This seemingly simple task is either very complex or I am missing something very fundamental here. My requirement was to change the background colour of a cell to light red if that cell contained the value 1.0. My first impulse was to root around the object model looking for a Cells collection. After all there’s a Columns collection so I expected at least a Rows and, with some luck, a Cells. No such luck. After losing a few hours I admitted defeat and asked the question in the WPF MSDN newsgroups. I got a single answer but thankfully it sent me in the right direction.

Like in many other things in WPF I was in the “wrong” mindset to approach the problem, what I should look for was some way of binding something to the cell style through some converter. The answer suggested using some XAML based on:

<wpf:DataGrid.CellStyle>
    <Style TargetType="{x:Type wpf:DataGridCell}">
        <Setter Property="Background"
                Value="{Binding SomeProperty,
                Converter={StaticResource cellBackgroundConverter}}" />
    </Style>
</wpf:DataGrid.CellStyle>

This is spot on for controlling the style of cells that are bound to objects. The converter would be coded along these lines:

public object Convert(
    object value,
    Type targetType,
    object parameter,
    CultureInfo culture)
{
    var obj = (MyCustomObject)value;
    return obj.IsHighlighted 
        ? new SolidBrush(Colors.LightSalmon)
        : SystemColors.AppWorkspaceColor;
}

Now the problem with DataGrids data-bound to DataTables is that the value passed to the converter is a DataRow, not exactly very useful since I also need the column and it is nowhere to be found. So ending the suspense, the solution is to use MultiDataBinding. This will allow us to pass the row and the DataGridCell to the MultiConverter and from both we can derive the correct cell (there’s a download with all the code at the end of this post).

In your XAML file, in the resources (for some reason it will not work in the style of the DataGrid) add:

<myApp:CellHighlighterConverter x:Key="cellHighlighterConverter" />

<Style x:Key="CellHighlighterStyle">
  <Setter Property="dg:DataGrid.Background">
    <Setter.Value>
      <MultiBinding
        Converter="{StaticResource cellHighlighterConverter}" >
        <MultiBinding.Bindings>
          <Binding RelativeSource="{RelativeSource Self}"/>
          <Binding Path="Row" Mode="OneWay"/>
        </MultiBinding.Bindings>
     </MultiBinding>
   </Setter.Value>
  </Setter>
</Style>

And in the DataGrid declaration add the parameter:

CellStyle="{StaticResource cellHighlighterConverter }"

And finally create a new converter with the following code:

public class CellHighlighterConverter : IMultiValueConverter
{
  public object Convert(
    object[] values,
    Type targetType,
    object parameter,
    CultureInfo culture)
  {
    if (values[1] is DataRow)
    {
        //Change the background of any
        //cell with 1.0 to light red.
        var cell = (DataGridCell)values[0];
        var row = (DataRow)values[1];
        var columnName = cell.Column.SortMemberPath;

        if (row[columnName].IsNumeric()
           row[columnName].ToDouble() == 1.0)
        return new SolidColorBrush(Colors.LightSalmon);
    }
    return SystemColors.AppWorkspaceColor;
  }

  public object[] ConvertBack(
      object value,
      Type[] targetTypes,
      object parameter,
      CultureInfo culture)
  {
      throw new System.NotImplementedException();
  }
}

Final Notes

The Path=”Row,Mode=OneWay” binding must be set OneWay or you get a nasty exception outside your code (“A TwoWay or OneWayToSource binding cannot work on the read-only, etc, etc”). Took me a while to find out which binding this was referring to because the exception was only thrown when I changed tabs (my DataGrid sits on a TabControl).

Another oddity is that I am using the SortMemberPath to fish for the column name. This sounds so wrong… but I did not find any other way.

And finally, a disclaimer, I am not sure if this is the right way or even the best way. I surely would like to hear from anyone with a leaner solution since I am going to have a lot of large DataGrids with a lot of colours throughout my application.

Yet More Notes

A few hours after posting this I found a series of posts about this same problem. Since they follow a very different route I am leaving here a link for reference:

WPF Codeplex: change style of a single cell in a datagrid

 And the Code

You can download a simple WPF application demonstrating the solution from here

About these ads