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

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • markmcgookin
    Recognized Expert Contributor
    • Dec 2006
    • 648

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

    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:
    Code:
    <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}" />
    Grid header style (roates text)
    Code:
    <DataTemplate x:Key="RotateHeaderTemplate" >
    	<TextBlock Text="{Binding}" Foreground="Blue" >
                <TextBlock.LayoutTransform>
                    <RotateTransform Angle="-90" />
                </TextBlock.LayoutTransform>
    	</TextBlock>
    </DataTemplate>
    And this is what I have tried so far to get the column header to change (called on the "Window_Activat ed" event as that is called after the constructor when the grid/wpf tree is actually built)

    Code:
    Style newStyle = new System.Windows.Style()
    			{
    				TargetType = typeof(DataGridColumn)
    			};
    
    			//SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#F70F49"))
    			newStyle.Setters.Add(new Setter(DataGridColumn.HeaderStringFormatProperty, new SolidColorBrush(Colors.Red)));
    			this.dgSkillsMatrix.Columns[4].HeaderStyle = newStyle;
    Any help would REALLY be appreciated.

    Cheers,

    Mark
  • Frinavale
    Recognized Expert Expert
    • Oct 2006
    • 9749

    #2
    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#)
    Code:
    public class ForegroundConverter : IValueConverter
    {
    
    	public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    	{
    		string str = value as string;
    	//Compare against your list instead of checking if str contains "a"
    		if (str != null && (str.Contains("a") | str.Contains("A"))) {
    			return Brushes.Orange;
    		}
    		return Brushes.Blue;
    	}
    
    	public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    	{
    		throw new NotImplementedException();
    	}
    }
    (VB.NET)
    Code:
    Public Class ForegroundConverter
        Implements IValueConverter
    
        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
            Dim str As String = TryCast(value, String)
      'Compare against your list instead of checking if str contains "a"
            If str IsNot Nothing AndAlso (str.Contains("a") Or str.Contains("A")) Then
                Return Brushes.Orange
            End If
            Return Brushes.Blue
        End Function
    
        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
            Throw New NotImplementedException
        End Function
    End Class
    Now all you have to do is use this converter in your XAML markup for the DataGrid.

    Here's my DataGrid:
    Code:
      <Window.Resources>
            <local:FooBars x:Key="fooBars" />
            <local:ForegroundConverter x:Key="foregroundConverter" />
      </Window.Resources>
      <Grid>
          <DataGrid ItemsSource="{Binding FooBarListing, Source={StaticResource fooBars}}" Background="Transparent">
            <DataGrid.ColumnHeaderStyle>
              <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="Template">
                  <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                      <TextBlock Text="{Binding}"
                                 Foreground="{Binding Converter={StaticResource foregroundConverter}}">
                        <TextBlock.LayoutTransform>
                          <RotateTransform Angle="-90" />
                        </TextBlock.LayoutTransform>
                      </TextBlock>
                    </ControlTemplate>
                  </Setter.Value>
                </Setter>
              </Style>
            </DataGrid.ColumnHeaderStyle>
          </DataGrid>
      </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#)
    Code:
    public class FooBars
    {
    	private List<FooBar> _names;
    	public List<FooBar> FooBarListing {
    		get { return _names; }
    	}
    	public FooBars()
    	{
    		_names = new List<FooBar>();
    		_names.Add(new FooBar {
    			UserID = "markmcgookin",
    			Data = "data about mark"
    		});
    		_names.Add(new FooBar {
    			UserID = "Frinavale",
    			Data = "data about Frinavale"
    		});
    		_names.Add(new FooBar {
    			UserID = "NeoPa",
    			Data = "data about NeoPa"
    		});
    		_names.Add(new FooBar {
    			UserID = "Mary",
    			Data = "data about Mary"
    		});
    	}
    }
    
    public class FooBar
    {
    	public string UserID { get; set; }
    	public string Data { get; set; }
    }
    (VB.NET)
    Code:
    Public Class FooBars
        Private _names As List(Of FooBar)
        Public ReadOnly Property FooBarListing As List(Of FooBar)
            Get
                Return _names
            End Get
        End Property
        Public Sub New()
            _names = New List(Of FooBar)
            _names.Add(New FooBar() With {.UserID = "markmcgookin", .Data = "data about mark"})
            _names.Add(New FooBar() With {.UserID = "Frinavale", .Data = "data about Frinavale"})
            _names.Add(New FooBar() With {.UserID = "NeoPa", .Data = "data about NeoPa"})
            _names.Add(New FooBar() With {.UserID = "Mary", .Data = "data about Mary"})
        End Sub
    End Class
    
    Public Class FooBar
        Public Property UserID As String
        Public Property Data As String
    End Class
    -Frinny
    Last edited by Frinavale; Dec 21 '11, 05:32 PM.

    Comment

    • markmcgookin
      Recognized Expert Contributor
      • Dec 2006
      • 648

      #3
      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

      Code:
      public class SkillsMatrixHeaderColourConverter : IMultiValueConverter
      	{
      		public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      		{
      			if (values[0] is DataGridColumnHeader)
      			{
      
      				if ((values[0] as DataGridColumnHeader).Column != null)
      				{
      					var columnName = (values[0] as DataGridColumnHeader).Column.Header.ToString();
      
      					if (columnName.Contains("*"))
      					{
      						return new SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#F70F49"));
      					}
      					else
      					{
      						return null;
      					}
      				}
      				else
      				{
      					return null;
      				}
      			}
      			else
      			{
      				return null;
      			}
      		}
      And just applied it in the XAML like this

      Code:
      <local:SkillsMatrixHeaderColourConverter x:Key="HeaderConverter" />
      In a style

      Code:
      <Style x:Key="dataGridColumnHeader" TargetType="DataGridColumnHeader">
      			<Setter Property="ContentTemplate" Value="{StaticResource RotateHeaderTemplate}" />
      				<Setter Property="DataGridCell.Background">
      					<Setter.Value>
      						<MultiBinding Converter="{StaticResource HeaderConverter}" >
      							<MultiBinding.Bindings>
      								<Binding RelativeSource="{RelativeSource Self}"/>
      								<Binding Path="Row" Mode="OneWay"/>
      							</MultiBinding.Bindings>
      						</MultiBinding>
      					</Setter.Value>
      				</Setter>
      		</Style>
      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.

      Comment

      • Frinavale
        Recognized Expert Expert
        • Oct 2006
        • 9749

        #4
        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

        Comment

        • markmcgookin
          Recognized Expert Contributor
          • Dec 2006
          • 648

          #5
          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.

          Comment

          • Frinavale
            Recognized Expert Expert
            • Oct 2006
            • 9749

            #6
            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 DataGridColumnH eader 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.
            Last edited by Frinavale; Dec 21 '11, 05:51 PM.

            Comment

            Working...