By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
431,816 Members | 1,332 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 431,816 IT Pros & Developers. It's quick & easy.

Change colour of column header in WPF datagrid with autogenerated columns at runtime

markmcgookin
Expert 100+
P: 648
Hi folks,

I have a WPF Datagrid in a WPF Project Window. I have populated the grid with a datatable, and autogenerated the columns (unfortunately a necessity) and have a requirement to change the header colour of the columns dependant on certain other factors.

I have a list of the coulmn names that need highlighted, and would easily be able to figure out their indexes based on this (as I generated them myself in the datagrid)

However, I can't seem to get the column header to change colour, this has to be done in the code as I don't know at design time which columns will need highlighted. I already have a bit of a template on the header... not sure if that is "over-riding" what I am trying to do.

Grid:
Expand|Select|Wrap|Line Numbers
  1. <DataGrid FrozenColumnCount="1"  AutoGenerateColumns="True" Grid.Row="1" AlternationCount="2" HeadersVisibility="Column" Name="dgSkillsMatrix" Margin="0,0,2,1" HorizontalGridLinesBrush="White" VerticalGridLinesBrush="White" AlternatingRowBackground="#FFD0D0EB" RowBackground="#FFECECF5" FontSize="10.5" Grid.ColumnSpan="1" CellStyle="{StaticResource CellHighlighterStyle}" ColumnHeaderStyle="{StaticResource dataGridColumnHeader}" />
  2.  
Grid header style (roates text)
Expand|Select|Wrap|Line Numbers
  1. <DataTemplate x:Key="RotateHeaderTemplate" >
  2.     <TextBlock Text="{Binding}" Foreground="Blue" >
  3.             <TextBlock.LayoutTransform>
  4.                 <RotateTransform Angle="-90" />
  5.             </TextBlock.LayoutTransform>
  6.     </TextBlock>
  7. </DataTemplate>
  8.  
And this is what I have tried so far to get the column header to change (called on the "Window_Activated" event as that is called after the constructor when the grid/wpf tree is actually built)

Expand|Select|Wrap|Line Numbers
  1. Style newStyle = new System.Windows.Style()
  2.             {
  3.                 TargetType = typeof(DataGridColumn)
  4.             };
  5.  
  6.             //SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#F70F49"))
  7.             newStyle.Setters.Add(new Setter(DataGridColumn.HeaderStringFormatProperty, new SolidColorBrush(Colors.Red)));
  8.             this.dgSkillsMatrix.Columns[4].HeaderStyle = newStyle;
  9.  
Any help would REALLY be appreciated.

Cheers,

Mark
Dec 15 '11 #1

✓ answered by Frinavale

Hey Mark,

Sorry about the late reply but the solution isn't that hard to do. You just need a converter that compares the value against your list of strings and which returns a brush with the desired colour.

Here is the converter I created for testing a solution for you. It doesn't compare against a predefined list...it just check if the value contains the letter "a"... if it does then it returns an Orange brush; otherwise, it returns a blue brush.
(C#)
Expand|Select|Wrap|Line Numbers
  1. public class ForegroundConverter : IValueConverter
  2. {
  3.  
  4.     public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
  5.     {
  6.         string str = value as string;
  7.     //Compare against your list instead of checking if str contains "a"
  8.         if (str != null && (str.Contains("a") | str.Contains("A"))) {
  9.             return Brushes.Orange;
  10.         }
  11.         return Brushes.Blue;
  12.     }
  13.  
  14.     public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
  15.     {
  16.         throw new NotImplementedException();
  17.     }
  18. }
  19.  
(VB.NET)
Expand|Select|Wrap|Line Numbers
  1. Public Class ForegroundConverter
  2.     Implements IValueConverter
  3.  
  4.     Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
  5.         Dim str As String = TryCast(value, String)
  6.   'Compare against your list instead of checking if str contains "a"
  7.         If str IsNot Nothing AndAlso (str.Contains("a") Or str.Contains("A")) Then
  8.             Return Brushes.Orange
  9.         End If
  10.         Return Brushes.Blue
  11.     End Function
  12.  
  13.     Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
  14.         Throw New NotImplementedException
  15.     End Function
  16. End Class
Now all you have to do is use this converter in your XAML markup for the DataGrid.

Here's my DataGrid:
Expand|Select|Wrap|Line Numbers
  1.   <Window.Resources>
  2.         <local:FooBars x:Key="fooBars" />
  3.         <local:ForegroundConverter x:Key="foregroundConverter" />
  4.   </Window.Resources>
  5.   <Grid>
  6.       <DataGrid ItemsSource="{Binding FooBarListing, Source={StaticResource fooBars}}" Background="Transparent">
  7.         <DataGrid.ColumnHeaderStyle>
  8.           <Style TargetType="{x:Type DataGridColumnHeader}">
  9.             <Setter Property="Template">
  10.               <Setter.Value>
  11.                 <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
  12.                   <TextBlock Text="{Binding}"
  13.                              Foreground="{Binding Converter={StaticResource foregroundConverter}}">
  14.                     <TextBlock.LayoutTransform>
  15.                       <RotateTransform Angle="-90" />
  16.                     </TextBlock.LayoutTransform>
  17.                   </TextBlock>
  18.                 </ControlTemplate>
  19.               </Setter.Value>
  20.             </Setter>
  21.           </Style>
  22.         </DataGrid.ColumnHeaderStyle>
  23.       </DataGrid>
  24.   </Grid>
I am binding the foreground of the TextBlock in the header to the text that is displaying and I am using my custom converter to return the brush (color) based on the text that the TextBlock is bound to.

It's pretty simple and nice and clean.

Here is my "FooBars" and "FooBar" class that I am using as a static resource to bind to:
(C#)
Expand|Select|Wrap|Line Numbers
  1. public class FooBars
  2. {
  3.     private List<FooBar> _names;
  4.     public List<FooBar> FooBarListing {
  5.         get { return _names; }
  6.     }
  7.     public FooBars()
  8.     {
  9.         _names = new List<FooBar>();
  10.         _names.Add(new FooBar {
  11.             UserID = "markmcgookin",
  12.             Data = "data about mark"
  13.         });
  14.         _names.Add(new FooBar {
  15.             UserID = "Frinavale",
  16.             Data = "data about Frinavale"
  17.         });
  18.         _names.Add(new FooBar {
  19.             UserID = "NeoPa",
  20.             Data = "data about NeoPa"
  21.         });
  22.         _names.Add(new FooBar {
  23.             UserID = "Mary",
  24.             Data = "data about Mary"
  25.         });
  26.     }
  27. }
  28.  
  29. public class FooBar
  30. {
  31.     public string UserID { get; set; }
  32.     public string Data { get; set; }
  33. }
(VB.NET)
Expand|Select|Wrap|Line Numbers
  1. Public Class FooBars
  2.     Private _names As List(Of FooBar)
  3.     Public ReadOnly Property FooBarListing As List(Of FooBar)
  4.         Get
  5.             Return _names
  6.         End Get
  7.     End Property
  8.     Public Sub New()
  9.         _names = New List(Of FooBar)
  10.         _names.Add(New FooBar() With {.UserID = "markmcgookin", .Data = "data about mark"})
  11.         _names.Add(New FooBar() With {.UserID = "Frinavale", .Data = "data about Frinavale"})
  12.         _names.Add(New FooBar() With {.UserID = "NeoPa", .Data = "data about NeoPa"})
  13.         _names.Add(New FooBar() With {.UserID = "Mary", .Data = "data about Mary"})
  14.     End Sub
  15. End Class
  16.  
  17. Public Class FooBar
  18.     Public Property UserID As String
  19.     Public Property Data As String
  20. End Class
-Frinny

Share this Question
Share on Google+
5 Replies


Frinavale
Expert Mod 5K+
P: 9,731
Hey Mark,

Sorry about the late reply but the solution isn't that hard to do. You just need a converter that compares the value against your list of strings and which returns a brush with the desired colour.

Here is the converter I created for testing a solution for you. It doesn't compare against a predefined list...it just check if the value contains the letter "a"... if it does then it returns an Orange brush; otherwise, it returns a blue brush.
(C#)
Expand|Select|Wrap|Line Numbers
  1. public class ForegroundConverter : IValueConverter
  2. {
  3.  
  4.     public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
  5.     {
  6.         string str = value as string;
  7.     //Compare against your list instead of checking if str contains "a"
  8.         if (str != null && (str.Contains("a") | str.Contains("A"))) {
  9.             return Brushes.Orange;
  10.         }
  11.         return Brushes.Blue;
  12.     }
  13.  
  14.     public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
  15.     {
  16.         throw new NotImplementedException();
  17.     }
  18. }
  19.  
(VB.NET)
Expand|Select|Wrap|Line Numbers
  1. Public Class ForegroundConverter
  2.     Implements IValueConverter
  3.  
  4.     Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
  5.         Dim str As String = TryCast(value, String)
  6.   'Compare against your list instead of checking if str contains "a"
  7.         If str IsNot Nothing AndAlso (str.Contains("a") Or str.Contains("A")) Then
  8.             Return Brushes.Orange
  9.         End If
  10.         Return Brushes.Blue
  11.     End Function
  12.  
  13.     Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
  14.         Throw New NotImplementedException
  15.     End Function
  16. End Class
Now all you have to do is use this converter in your XAML markup for the DataGrid.

Here's my DataGrid:
Expand|Select|Wrap|Line Numbers
  1.   <Window.Resources>
  2.         <local:FooBars x:Key="fooBars" />
  3.         <local:ForegroundConverter x:Key="foregroundConverter" />
  4.   </Window.Resources>
  5.   <Grid>
  6.       <DataGrid ItemsSource="{Binding FooBarListing, Source={StaticResource fooBars}}" Background="Transparent">
  7.         <DataGrid.ColumnHeaderStyle>
  8.           <Style TargetType="{x:Type DataGridColumnHeader}">
  9.             <Setter Property="Template">
  10.               <Setter.Value>
  11.                 <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
  12.                   <TextBlock Text="{Binding}"
  13.                              Foreground="{Binding Converter={StaticResource foregroundConverter}}">
  14.                     <TextBlock.LayoutTransform>
  15.                       <RotateTransform Angle="-90" />
  16.                     </TextBlock.LayoutTransform>
  17.                   </TextBlock>
  18.                 </ControlTemplate>
  19.               </Setter.Value>
  20.             </Setter>
  21.           </Style>
  22.         </DataGrid.ColumnHeaderStyle>
  23.       </DataGrid>
  24.   </Grid>
I am binding the foreground of the TextBlock in the header to the text that is displaying and I am using my custom converter to return the brush (color) based on the text that the TextBlock is bound to.

It's pretty simple and nice and clean.

Here is my "FooBars" and "FooBar" class that I am using as a static resource to bind to:
(C#)
Expand|Select|Wrap|Line Numbers
  1. public class FooBars
  2. {
  3.     private List<FooBar> _names;
  4.     public List<FooBar> FooBarListing {
  5.         get { return _names; }
  6.     }
  7.     public FooBars()
  8.     {
  9.         _names = new List<FooBar>();
  10.         _names.Add(new FooBar {
  11.             UserID = "markmcgookin",
  12.             Data = "data about mark"
  13.         });
  14.         _names.Add(new FooBar {
  15.             UserID = "Frinavale",
  16.             Data = "data about Frinavale"
  17.         });
  18.         _names.Add(new FooBar {
  19.             UserID = "NeoPa",
  20.             Data = "data about NeoPa"
  21.         });
  22.         _names.Add(new FooBar {
  23.             UserID = "Mary",
  24.             Data = "data about Mary"
  25.         });
  26.     }
  27. }
  28.  
  29. public class FooBar
  30. {
  31.     public string UserID { get; set; }
  32.     public string Data { get; set; }
  33. }
(VB.NET)
Expand|Select|Wrap|Line Numbers
  1. Public Class FooBars
  2.     Private _names As List(Of FooBar)
  3.     Public ReadOnly Property FooBarListing As List(Of FooBar)
  4.         Get
  5.             Return _names
  6.         End Get
  7.     End Property
  8.     Public Sub New()
  9.         _names = New List(Of FooBar)
  10.         _names.Add(New FooBar() With {.UserID = "markmcgookin", .Data = "data about mark"})
  11.         _names.Add(New FooBar() With {.UserID = "Frinavale", .Data = "data about Frinavale"})
  12.         _names.Add(New FooBar() With {.UserID = "NeoPa", .Data = "data about NeoPa"})
  13.         _names.Add(New FooBar() With {.UserID = "Mary", .Data = "data about Mary"})
  14.     End Sub
  15. End Class
  16.  
  17. Public Class FooBar
  18.     Public Property UserID As String
  19.     Public Property Data As String
  20. End Class
-Frinny
Dec 21 '11 #2

markmcgookin
Expert 100+
P: 648
Friv, thanks. That's what I ended up doing in the end. I used a multi-value converter and amended the names of the columns I needed to hilight as a trigger

Expand|Select|Wrap|Line Numbers
  1. public class SkillsMatrixHeaderColourConverter : IMultiValueConverter
  2.     {
  3.         public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  4.         {
  5.             if (values[0] is DataGridColumnHeader)
  6.             {
  7.  
  8.                 if ((values[0] as DataGridColumnHeader).Column != null)
  9.                 {
  10.                     var columnName = (values[0] as DataGridColumnHeader).Column.Header.ToString();
  11.  
  12.                     if (columnName.Contains("*"))
  13.                     {
  14.                         return new SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#F70F49"));
  15.                     }
  16.                     else
  17.                     {
  18.                         return null;
  19.                     }
  20.                 }
  21.                 else
  22.                 {
  23.                     return null;
  24.                 }
  25.             }
  26.             else
  27.             {
  28.                 return null;
  29.             }
  30.         }
  31.  
And just applied it in the XAML like this

Expand|Select|Wrap|Line Numbers
  1. <local:SkillsMatrixHeaderColourConverter x:Key="HeaderConverter" />
  2.  
In a style

Expand|Select|Wrap|Line Numbers
  1. <Style x:Key="dataGridColumnHeader" TargetType="DataGridColumnHeader">
  2.             <Setter Property="ContentTemplate" Value="{StaticResource RotateHeaderTemplate}" />
  3.                 <Setter Property="DataGridCell.Background">
  4.                     <Setter.Value>
  5.                         <MultiBinding Converter="{StaticResource HeaderConverter}" >
  6.                             <MultiBinding.Bindings>
  7.                                 <Binding RelativeSource="{RelativeSource Self}"/>
  8.                                 <Binding Path="Row" Mode="OneWay"/>
  9.                             </MultiBinding.Bindings>
  10.                         </MultiBinding>
  11.                     </Setter.Value>
  12.                 </Setter>
  13.         </Style>
  14.  
Thanks though.... not sure if I really needed multi-value, but I used it elsewhere where I had to and just copied and pasted my code lol.... lazy code ftw.
Dec 21 '11 #3

Frinavale
Expert Mod 5K+
P: 9,731
Yeah triggers work too :)

I use them in quite a few places to set the styles of things depending on some property that indicates a "subtype".

Glad you solved your problem :)

I'm going to subscribe to this forum since it is the technology that I am using on a daily bases right now.

-Frinny
Dec 21 '11 #4

markmcgookin
Expert 100+
P: 648
Yeah same.

I couldn't use triggers as it's a data table built at run time, and not a collection of objects, so I was struggling to connect to a variable to kick off the trigger.
Dec 21 '11 #5

Frinavale
Expert Mod 5K+
P: 9,731
Ahh I see, you didn't know the "property" that it was going to be bound to :)

The property-name/column-name shouldn't matter to a converter...it only cares about the values.

Hmm, I'm just looking at your converter code and your binding in your XAML code....

You don't need to pass the whole DataGridColumnHeader into the converter to determine a background color based on the Text it's displaying.... Just pass the Text that it's displaying to simplify things :)

I don't even see you using "Row" (the second binding) in your converter.
Dec 21 '11 #6

Post your reply

Sign in to post your reply or Sign up for a free account.