Monday, March 3, 2014

Changing the Background of a DataGridColumnHeader

This blog is for WPF v4.0

Try this...


<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <DataGrid Name="DataGrid" ItemsSource="{Binding Dogs}"/>
    </Grid>
</Window>


VB.Net
Class MainWindow Public Class Dog Public Property Name As String Public Property Size As String Public Sub New(Name As String, Size As String) Me.Name = Name Me.Size = Size End Sub End Class Public Property Dogs As New List(Of Dog) From {New Dog("Terrier", "Small"), New Dog("Wippet", "Small"), New Dog("Labrador", "Big"), New Dog("Collie", "Medium"), New Dog("Corgi", "Small")} End Class
C#.Net
public class Dog { public string Name { get; set; } public string Size { get; set; } public Dog(string Name, string Size) { this.Name = Name; this.Size = Size; } } public partial class MainWindow : Window { private List _Dogs = new List(); public List Dogs { get { return _Dogs; } } public MainWindow() { InitializeComponent(); Dogs.Add(new Dog("Terrier", "Small")); Dogs.Add(new Dog("Wippet", "Small")); Dogs.Add(new Dog("Labrador", "Big")); Dogs.Add(new Dog("Collie", "Medium")); Dogs.Add(new Dog("Corgie", "Small")); } }

This demonstrates the default column header. Click on the column headers to resort the columns. You can also resequence the columns by dragging the header and resize them by dragging hidden thumbs at either end of the header The header itself obviously a button with an optional sort indicator above the text. It looks pretty nasty.

Let's try to change the background of the header so it isn't so obviously a button. The DataGrid has a ColumnHeaderStyle attribute. We could use that but for simplicity let's create a default style for our column headers by adding this to the XAML...

<Window.Resources>
    <Style TargetType="{x:Type DataGridColumnHeader}">
        <Setter Property="Background" Value="LightGray"/>
    </Style>
</Window.Resources>

If you run the project with the new style the header looks much better. But wait - where did our sort indicators go? It turns out that the DataGridColumnHeader is deliberately styled so that the sort indicator is hidden if you change the background color. Sometimes I just don't understand how Microsoft stays in business. Why would you style such an ugly control and then break other critical functionality when the developer tries to fix it.

We have to re-template the DataGridColumnHeader. While we're in there, lets have some fun! This is what we're going to do...

  • Change the background color
  • Use a border to create an underline
  • Change the color of the border when the mouse is over the header
  • Move the sort indicator to the side of the header text instead of above
  • Make the column width thumbs invisible but change the cursor when it's over them

Step 1 - Beef the style up so it looks like this...

        <Style TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="MinWidth" Value="0"/>
            <Setter Property="MinHeight" Value="0"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="Background" Value="LightGray"/>
            <Setter Property="Cursor" Value="Hand"/>
        </Style>

Step 2 - Write the ControlTemplate that defines the header area, the sort indicator, the border, and the thumbs. The Grid controls the layout of the header, with the Content area on the left and the Sort Indicator on the right. Note the Sort Indicator is defined using a Path. The two rectangles produce a visible left and right edge for the column headers. The Thumbs must be defined and allow the user to resize the columns. We will define the ThumbStyle later. Notice the name of the Border. We need this so we can alter it using triggers in step 3. Add this before the closing Style tag.

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Border x:Name="BackgroundBorder" BorderThickness="0,0,0,2"
                                Background="LightGray"
                                BorderBrush="Black"
                                Grid.ColumnSpan="2"/>
                        <ContentPresenter Margin="6,3,6,3" VerticalAlignment="Center"/>
                        <Path x:Name="SortArrow" Visibility="Collapsed" Data="M 0,0 L 1,0 0.5,1 z" Stretch="Fill"
                             Grid.Column="1" Width="8" Height="6" Fill="Black" Margin="0,0,8,0"
                              VerticalAlignment="Center" RenderTransformOrigin="0.5, 0.4"/>
                        <Rectangle Width="1" Fill="#EEEEEE" HorizontalAlignment="Right" Grid.ColumnSpan="2"/>
                        <Rectangle Width="1" Margin="0,0,1,0" Fill="#DDDDDD" HorizontalAlignment="Right" Grid.ColumnSpan="2"/>
                        <Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ThumbStyle}"/>
                        <Thumb x:Name="PART_RightHeaderGripper" Grid.Column="1" HorizontalAlignment="Right" Style="{StaticResource ThumbStyle}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
 

Step 3 - Add triggers to change the color of the border when the mouse moves over the column header. Add the following triggers after the Grid closing tag.

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="BackgroundBorder" Property="Background" Value="LightGray"/>
                            <Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="Orange"/>
                        </Trigger>
                    </ControlTemplate.Triggers>

Step 4 - Add triggers to display and/or rotate the Sort Indicator when the column is sorted.

                        <Trigger Property="SortDirection" Value="Ascending">
                            <Setter TargetName="SortArrow" Property="Visibility" Value="Visible"/>
                            <Setter TargetName="SortArrow" Property="RenderTransform">
                                <Setter.Value>
                                    <RotateTransform Angle="180"/>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                        <Trigger Property="SortDirection" Value="Descending">
                            <Setter TargetName="SortArrow" Property="Visibility" Value="Visible"/>
                        </Trigger>

Step 5 - Hide the LeftHeaderGripper in column 0 - we don't need it.

                        <Trigger Property="DisplayIndex" Value="0">
                            <Setter TargetName="PART_LeftHeaderGripper" Property="Visibility" Value="Collapsed"/>
                        </Trigger>


As promised, here is the ThumbStyle definition. Insert it above the DataGridColumnHeader style. I use an invisible rectangle with a cursor of SizeWE. You could color it or use something completely different if you chose.

<Style TargetType="{x:Type Thumb}" x:Key="ThumbStyle">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Rectangle Width="1" Stroke="Transparent" Cursor="SizeWE"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here is the result of all your hard styling

You might notice some redundancy in my style. For example I set the background to LightGray in several places. You can alter some of the colors or add your own setters to get a wide variety of effects.

2 comments: