WPF DataGrid – Styling rows and columns based on Header conditions and other properties

There have been several questions on the WPF CodePlex discussion list relating to styling rows and columns based some requirements on other elements or some other conditions. I decided to put a couple examples here on different ways you can accomplish this.

Adding a trigger to a CellStyle based on a column condition

From a DataGridCell, you can access the column that it belongs to through DataGridCell.Column. With that you set a trigger based on a DataGridColumn property or a derived DataGridColumn property. Here is an example of a trigger that sets the background of a DataGridCell to LightGray of the column is frozen.

<Style TargetType="{x:Type dg:DataGridCell}">

  <Style.Triggers>

    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Column.IsFrozen, Mode=OneWay}" Value="True">

      <Setter Property="Background" Value="LightGray" />

    </DataTrigger>

  </Style.Triggers>

</Style>

 

Updating CellStyle based on a condition in the column header

From a DataGridColumnHeader, you have access to the column through DataGridColumnHeader.Column. This gives you access to all the DPs on the DataGridColumn which includes DataGridColumn.CellStyle. With that you can update the CellStyle based on some condition that you set. Let’s say I have a checkbox in the DataGridColumnHeader template and when checked I want to highlight the entire column. Here is a possible solution:

<Style TargetType="{x:Type dg:DataGridColumnHeader}">

  <Setter Property="ContentTemplate">

    <Setter.Value>

      <DataTemplate>

        <StackPanel>

          <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridColumnHeader}}, Path=Column.CellStyle, Mode=OneWayToSource, Converter={StaticResource CellBackgroundConverter}}" Content="Highlight Cells"/>

          <TextBlock Text="{Binding}" />

        </StackPanel>

      </DataTemplate>

    </Setter.Value>

  </Setter>

</Style>

 

With the CellBackgroundConverter I can choose a particular CellStyle based on the IsChecked property. Each CellStyle would set the background to a different value.

Updating cell properties based on a condition in a derived column

An alternative to the converter example above is to update cell properties based on a derived DataGridColumn condition that you create. Let’s say I create a custom column that derives from DataGridColumn and has a property called IsHighlighted. I want to use the CheckBox in my DataGridColumnHeader to highlight the column like before. Since the DataGridColumnHeader has access to the DataGridColumn, you can also set bindings on derived DataGridColumn properties. From the DataGridColumnHeader, I can bind that CheckBox to the IsHighlighted property and use that to highlight the column instead of a converter. Here is an example:

  <Style TargetType="{x:Type dg:DataGridColumnHeader}">

  <Setter Property="ContentTemplate">

    <Setter.Value>

      <DataTemplate>

        <StackPanel>

          <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridColumnHeader}}, Path=(Column).(local:CustomColumn.IsHighlighted), Mode=OneWayToSource}" Content="Highlight Cells"/>

          <TextBlock Text="{Binding}" />

        </StackPanel>

      </DataTemplate>

    </Setter.Value>

  </Setter>

</Style>

 

I set the Mode to OneWayToSource as I only care that local:CustomColumn.IsHighlighted is updated which I mark the CheckBox as checked. With that I can use a trigger in the DataGridCell based on the derived column property.

<Style TargetType="{x:Type dg:DataGridCell}">

  <Style.Triggers>

    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(Column).(local:CustomColumn.IsHighlighted), Mode=OneWay}" Value="True">

      <Setter Property="Background" Value="Tan" />

      <Setter Property="Foreground" Value="Red" />

    </DataTrigger>

  </Style.Triggers>

</Style>

 

Updating RowStyle based on a condition from the row header

From a DataGridRowHeader, you have direct access to the DataGridRow as it is an ancestor in the visual tree. So all you need to do is find the DataGridRow and update its style. Here is an example which is similar to the CellStyle example:               

<Style TargetType="dg:DataGridRowHeader">

  <Setter Property="ContentTemplate">

    <Setter.Value>

      <DataTemplate>

        <StackPanel>

          <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=Style, Mode=OneWayToSource, Converter={StaticResource RowBackgroundConverter}}" Content="Highlight Row"/>

          <TextBlock Text="{Binding}" />

        </StackPanel>

      </DataTemplate>

    </Setter.Value>

  </Setter>

</Style>

 

Since I have direct access to the DataGridRow, I can set the Path directly on Background if I wanted to. However, I wanted to update a couple properties so I update the Style instead.

Caveat: One thing to note about doing this however is item container recycling. If you leave it on you will see some side effects. Basically as you scroll rows in and out of view they will lose the checked state. Ways you can avoid this is either to turn recycling off or keeping state yourself and overriding the ItemsControl.PrepareContainerForItemOverride to update the row.

Updating cell properties based on Database related properties

Let’s say that you want to signal that a column is the primary key or allows null values or is a particular value type. For the signal, I will set the cell backgrounds like I have been in previous examples. Since the cell has access to the DataGridColumn, I can create a derived property on the DataGridColumn and bind to that in the DataGridCell’s Style:

<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(Column).(local:CustomColumn.IsPrimaryKey), Mode=OneWay}" Value="True">

 

Setting up the binding is a little tricky however. I decided to set the property directly in the AutoGeneratingColumn event. For a non-auto-generating scenario you would need a converter on the binding to IsPrimaryKey to find the correct source to bind to. Anyway, for the auto-generating scenario, I copy the properties from DataGridColumn to set to my CustomColumn then search the MetaTable to find if the column is a primary key (I’m using linq to sql in this example).

private void DataGrid_Standard_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)

{

  CustomColumn customColumn = new CustomColumn();

  customColumn.CanUserSort = e.Column.CanUserSort;

  customColumn.Header = e.Column.Header;

  customColumn.DataFieldBinding = (e.Column as DataGridBoundColumn).DataFieldBinding;

  DataGrid dg = sender as DataGrid;

  Table<Order> orderTable = dg.ItemsSource as Table<Order>;

  MetaModel metaModel = orderTable.Context.Mapping.MappingSource.GetModel(orderTable.Context.GetType());

  MetaTable metaTable = metaModel.GetTable(typeof(Order));

  foreach (MetaDataMember identityMember in metaTable.RowType.IdentityMembers)

  {

    if (identityMember.MappedName == e.Column.Header.ToString())

    {

      customColumn.IsPrimaryKey = true;

        break;
}
}

  e.Column = customColumn;

}

 

The full solution of examples is attached.

More Related Material:

DataGrid Intro

Stock and Template Columns

Dissecting the Visual Layout

Working with DataGridComboBoxColumn (Part1)

Working with DataGridComboBoxColumn (Part2)

Overview of the editing features in the DataGrid

Other DataGrid Samples:

ScrollViewer with ToolTip

Custom sorting, column selection, single-click editing

Tri-state sorting

Clipboard Paste

DataGrid_V1_StylingSample.zip