Condividi tramite


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

Comments

  • Anonymous
    September 16, 2008
    PingBack from http://blogs.msdn.com/vinsibal/archive/2008/08/11/net-3-5-sp1-and-wpf-datagrid-ctp-is-out-now.aspx

  • Anonymous
    September 19, 2008
    In the v1 release (and CTP) of the WPF DataGrid there will be support for Clipboard.Copy but no support

  • Anonymous
    September 30, 2008
    I've merged the resources from PresentationFramework.Aero.dll in App.xaml, and added the DataGrid to a window, along with other controls. When running on XP, all other controls appear with AeroNormalColor theme, while the grid appears on Classic theme. How to address this issue?

  • Anonymous
    October 03, 2008
    Karen, It is possible that the DataGrid cannot find the application specific theme that you set as it defaults to Classic.  Could you explain a bit more on how you merged the resources from PresentationFramework.Aero.dll and maybe a code snippet.

  • Anonymous
    October 03, 2008
    In my App.xaml i put the following code <Application.Resources>    <ResourceDictionary>        <ResourceDictionary.MergedDictionaries>            <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Aero;V3.0.0.0;31bf3856ad364e35;componentthemes/aero.normalcolor.xaml" />        </ResourceDictionary.MergedDictionaries>        <!-- other resources go here -->    </ResourceDictionary> </Application.Resources>

  • Anonymous
    October 09, 2008
    Karen, I'm still not able to repro.  Could you just send me your repro app.  You can send a zipped solution to vinsibal@microsoft.com.  Thanks.

  • Anonymous
    October 14, 2008
    The comment has been removed

  • Anonymous
    October 16, 2008
    Prasanna rao, Are you correctly referencing WPFToolKit.dll in the csproj?  I've seen this error before when I was missing the reference.

  • Anonymous
    October 20, 2008
    Carine Maalouf, I talked about a solution to the issue offline, but I thought I'd post it here just in case anyone else ran into the same issue. Basically, when you want to override the default themes you can do what Carine has done with the ResourceDictionary.  However, DataGrid actually lives in WPFToolKit.dll so will still retain the default theme styles even after setting the merged ResourceDictionary as been set above.  To get it to work you will have to merge the WPFToolKit aero.normalcolor.xaml as well. <ResourceDictionary.MergedDictionaries>                <ResourceDictionary Source="pack://application:,,,/PresentationFramework.Aero;V3.0.0.0;31bf3856ad364e35;componentthemes/aero.normalcolor.xaml" />                <ResourceDictionary Source="pack://application:,,,/WPFToolKit;V3.5.30731.0;;componentthemes/aero.normalcolor.xaml" />            </ResourceDictionary.MergedDictionaries> Note that you will have to update the uri when the v1 release comes out to use the public key as well as the new version number.

  • Anonymous
    October 22, 2008
    Hi Vincent, I want to change the appearance of a column based on a property on the column configuration I have. So I added a style in my xaml and set the CellStyle on the grid. I then created my own CustomColumn class and added a property called IsReadOnly and set the property in the event handler for AutoGeneratingColumn. This is very similar to the DataGridStylingWithDataTable example that you are using. The IsReadOnly property is never accessed and I just can't understand why. Any help is much appreciated. Thanks Andy Here's the code:-            <Style x:Key="defaultCellStyle" TargetType="{x:Type dg:DataGridCell}">                <Style.Triggers>                    <Trigger Property="IsEditing" Value="True">                        <Setter Property="BorderBrush" Value="Red" />                        <Setter Property="BorderThickness" Value="2" />                    </Trigger>                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},                                                               Path=(Column).(local:ListEditColumn.IsReadOnly),                                                               Mode=OneWay}"                                             Value="True">                        <Setter Property="Background" Value="Tan" />                        <Setter Property="Foreground" Value="Red" />                    </DataTrigger>                </Style.Triggers>            </Style>    Private Sub AutoGeneratingColumn(ByVal sender As Object, ByVal e As DataGridAutoGeneratingColumnEventArgs)        Dim lobjColumn As ListEditColumn = New ListEditColumn        lobjColumn.CanUserSort = e.Column.CanUserSort        lobjColumn.Header = e.Column.Header        lobjColumn.DataFieldBinding = TryCast(e.Column, DataGridBoundColumn).DataFieldBinding        If lobjColumn.Header.ToString.Contains("UID") Then            lobjColumn.IsReadOnly = True            lobjColumn.CanUserSort = False        End If        e.Column = lobjColumn    End Sub    Public Class ListEditColumn        Inherits DataGridTextColumn        Private mblnIsReadOnly As Boolean = False        Public Property IsReadOnly() As Boolean            Get                Return mblnIsReadOnly            End Get            Set(ByVal value As Boolean)                mblnIsReadOnly = value            End Set        End Property

  • Anonymous
    October 30, 2008
    Andy, I believe you asked the same question on the codeplex site.  I'll put a reference here just in case other people find the same issue, http://www.codeplex.com/wpf/Thread/View.aspx?ThreadId=38292.

  • Anonymous
    November 10, 2008
    I can't see from this post how to style a cell from the value of the cell itself.  One application of this is in the red/black colouring of positive and negative values.   To do this I would write a style with a DataTrigger which uses a converter to convert a decimal value into a boolean, representing whether the value is positive or negative. I would like to do the following: <dg:DataGridTextColumn Header="DecVal1" CellStyle="{StaticResource decimalGridCell}" Binding="{Binding Path=DecVal1, StringFormat='{}{0:N0}'}" /> But I'm a bit stumped when it comes to writing the style.  The following would work but would require me to write a style for each decimal column I want to bind to. <DataTrigger Value="True" Binding="{Binding Path=DecVal1, Converter=myConverter}" etc How do I specify in the DataTrigger that I want Binding="{Value of the cell}" without having to name a specific property? (As an aside, I can achieve this by using a DataGridTemplateColumn, using a TextBlock in the DataTemplate, and applying a style which examines the Text property of the TextBlock, converting to decimal, then to a boolean for positive/negative.  This seems long-winded and unnecessary to me)

  • Anonymous
    November 10, 2008
    Cameron, Columns do not derive from FrameworkElement and therefore do not play well when it comes to styling. For your example, you could do the binding on DataGridCell's Content property and deal with it in your converter.  It's really all that clean but it's another way you can go.

  • Anonymous
    November 23, 2008
    Regarding positive/negative coloring, it would be really nice to have a CellStyleSelector much like the ItemContainerStyleSelector works for each row. Could you please provide an example of binding the Content property?

  • Anonymous
    December 18, 2008
    Jacek Sieka, Which Content property are you talking about?