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 (15)

  1. In the v1 release (and CTP) of the WPF DataGrid there will be support for Clipboard.Copy but no support

  2. Karen says:

    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?

  3. vinsibal says:

    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.

  4. Carinemaalouf says:

    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>

  5. vinsibal says:

    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.

  6. prasanna_rao@hotmail.com says:

    I am unable to compile the soln.

    Error 1 The type reference cannot find a public type named ‘DataGridRow’. Line 15 Position 81. C:Program FilesMicrosoft Visual Studio 9.0SamplesC#SamplesDGridCTPDataGridCTPSampleDataGridCTPSampleDataGridBasicSample_Demo.xaml 15 81 DataGridCTPSample

    I am using VS2008 SP1 with .Net 3.5 SP1

    Prasanna

  7. vinsibal says:

    Prasanna rao,

    Are you correctly referencing WPFToolKit.dll in the csproj?  I’ve seen this error before when I was missing the reference.

  8. vinsibal says:

    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.

  9. Andy Mitchell says:

    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

  10. vinsibal says:

    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.

  11. 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)

  12. vinsibal says:

    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.

  13. Jacek Sieka says:

    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?

  14. vinsibal says:

    Jacek Sieka,

    Which Content property are you talking about?