WPF: DataGrid Select-all-button Styling


Problem

The SelectAll button on Microsoft’s DataGrid control cannot be styled without a workaround. This is a direct result of there being no SelectAllButtonTemplate property on the control. To get around this issue we’ll make our own property using attached properties. This way we can have nice, clean, MVVM-compliant code and XAML-markup, while still getting what we want.

Solution

This solution works by using an attached property to get a reference to the DataGrid in question whenever that attached property changes it’s value. Once we have a reference, we drill down into the control’s visual tree until we stumble upon a button whose command is assigned to the DataGrid’s SelectAllCommand; this ensures we have the correct button. Now that we have the button we can set it’s Template property to whatever ControlTemplate we want; meaning we can change its appearance, or behavior even, to whatever we want.

Attached Property: SelectAllButtonTemplate

public static class DataGridAPs
{
    public static readonly DependencyProperty SelectAllButtonTemplateProperty =
        DependencyProperty.RegisterAttached("SelectAllButtonTemplate",
        typeof(ControlTemplate), typeof(DataGridAPs),
        new UIPropertyMetadata(null, OnSelectAllButtonTemplateChanged));

    public static ControlTemplate GetSelectAllButtonTemplate(DataGrid obj)
    {
        return (ControlTemplate)obj.GetValue(SelectAllButtonTemplateProperty);
    }

    public static void SetSelectAllButtonTemplate(DataGrid obj, ControlTemplate value)
    {
        obj.SetValue(SelectAllButtonTemplateProperty, value);
    }

    private static void OnSelectAllButtonTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = d as DataGrid;
        if (dataGrid == null)
        {
            return;
        }

        EventHandler handler = null;
        handler = delegate
        {
            DependencyObject dep = dataGrid;
            while (dep != null && VisualTreeHelper.GetChildrenCount(dep) != 0
                && !(dep is Button && ((Button)dep).Command == DataGrid.SelectAllCommand))
            {
                dep = VisualTreeHelper.GetChild(dep, 0);
            }

            Button button = dep as Button;
            if (button != null)
            {
                ControlTemplate template = GetSelectAllButtonTemplate(dataGrid);
                button.Template = template;
                dataGrid.LayoutUpdated -= handler;
            }
        };

        dataGrid.LayoutUpdated += handler;
    }
}

ControlTemplate: DefaultDataGridSelectAllButtonTemplate

NOTE: The button’s width and height must either be set in the control template, or by setting the DataGrid’s RowHeaderWidth and ColumnHeaderHeight properties.

<!-- DefaultDataGridSelectAllButtonTemplate -->
<ControlTemplate x:Key="DefaultDataGridSelectAllButtonTemplate"
                 TargetType="{x:Type Button}">

    <Border x:Name="Border"
            BorderBrush="#E3E7E7"
            BorderThickness="0,0,1,1"
            VerticalAlignment="Stretch">

        <Border.Background>
            <LinearGradientBrush StartPoint=".5,0"
                                 EndPoint=".5,1">
                <GradientStop Color="#D5DBDB"
                              Offset="0" />
                <GradientStop Color="#BAC4C3"
                              Offset="1" />
            </LinearGradientBrush>
        </Border.Background>

        <Polygon x:Name="Arrow"
                 Fill="#758988"
                 HorizontalAlignment="Right"
                 Margin="8,8,3,3"
                 Points="0,10 10,10 10,0"
                 Stretch="Uniform"
                 VerticalAlignment="Bottom" />
    </Border>

    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver"
                 Value="True">

            <Setter TargetName="Border"
                    Property="Background"
                    Value="#ACB8B7" />

            <Setter TargetName="Border"
                    Property="BorderBrush"
                    Value="#E3E7E7" />

            <Setter TargetName="Arrow"
                    Property="Fill"
                    Value="#F1F3F3" />
        </Trigger>

        <Trigger Property="IsPressed"
                 Value="True">

            <Setter TargetName="Border"
                    Property="Background"
                    Value="#91A1A0" />

            <Setter TargetName="Border"
                    Property="BorderBrush"
                    Value="#E3E7E7" />

            <Setter TargetName="Arrow"
                    Property="Fill"
                    Value="#FFFFFF" />
        </Trigger>

        <Trigger Property="IsEnabled"
                 Value="False">
            <Setter TargetName="Arrow"
                    Property="Visibility"
                    Value="Collapsed" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Example

Now will see how to use the attached property and control template.

Example XAML: Window1.xaml

<Window x:Class="DataGridSelectAllDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:datagrid="http://schemas.microsoft.com/wpf/2008/toolkit"
        xmlns:local="clr-namespace:DataGridSelectAllDemo"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Style Microsoft’s DataGrid SelectAll Button using Attached Properties">

    <Window.Resources>
        <!-- DefaultDataGridSelectAllButtonTemplate -->
        <ControlTemplate x:Key="DefaultDataGridSelectAllButtonTemplate"
                         TargetType="{x:Type Button}">

            <Border x:Name="Border"
                    BorderBrush="#E3E7E7"
                    BorderThickness="0,0,1,1"
                    VerticalAlignment="Stretch">

                <Border.Background>
                    <LinearGradientBrush StartPoint=".5,0"
                                         EndPoint=".5,1">
                        <GradientStop Color="#D5DBDB"
                                      Offset="0" />
                        <GradientStop Color="#BAC4C3"
                                      Offset="1" />
                    </LinearGradientBrush>
                </Border.Background>

                <Polygon x:Name="Arrow"
                         Fill="#758988"
                         HorizontalAlignment="Right"
                         Margin="8,8,3,3"
                         Points="0,10 10,10 10,0"
                         Stretch="Uniform"
                         VerticalAlignment="Bottom" />
            </Border>

            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver"
                         Value="True">

                    <Setter TargetName="Border"
                            Property="Background"
                            Value="#ACB8B7" />

                    <Setter TargetName="Border"
                            Property="BorderBrush"
                            Value="#E3E7E7" />

                    <Setter TargetName="Arrow"
                            Property="Fill"
                            Value="#F1F3F3" />
                </Trigger>

                <Trigger Property="IsPressed"
                         Value="True">

                    <Setter TargetName="Border"
                            Property="Background"
                            Value="#91A1A0" />

                    <Setter TargetName="Border"
                            Property="BorderBrush"
                            Value="#E3E7E7" />

                    <Setter TargetName="Arrow"
                            Property="Fill"
                            Value="#FFFFFF" />
                </Trigger>

                <Trigger Property="IsEnabled"
                         Value="False">
                    <Setter TargetName="Arrow"
                            Property="Visibility"
                            Value="Collapsed" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- DataGrid Style -->
        <Style TargetType="{x:Type datagrid:DataGrid}">
            <Setter Property="local:DataGridAPs.SelectAllButtonTemplate"
                    Value="{StaticResource DefaultDataGridSelectAllButtonTemplate}" />

            <Setter Property="ColumnHeaderHeight"
                    Value="24" />

            <Setter Property="RowHeaderWidth"
                    Value="50" />

            <Setter Property="RowHeight"
                    Value="32" />
        </Style>
    </Window.Resources>

    <Grid>
        <datagrid:DataGrid AutoGenerateColumns="True"
                           ItemsSource="{Binding Customers, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
    </Grid>
</Window>

Example Code-behind: Window1.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;

namespace DataGridSelectAllDemo
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            Customers = new ObservableCollection();
            for (int i = 1; i < 100; i++)
                Customers.Add(new Customer("First" + i, "Last" + i));
        }

        public ObservableCollection Customers { get; set; }
    }
}

Customer Class: Customer.cs

namespace DataGridSelectAllDemo
{
    public class Customer
    {
        public Customer(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Example Download

http://thrash505.webs.com/BlogProjects/DataGridSelectAllDemo.zip

, , , , , , , , ,

  1. #1 by Anonymous on March 9, 2014 - 4:50 am

    Utterly perfect solution to this rather annoying problem. Thanks HEAPS.

  2. #2 by Jim on February 29, 2012 - 4:09 pm

    Thats a fantastic work on the datagrid. Thanks for the post. I have a small doubt here. How can we add row numbers sequentially to the datagrid rows displayed in the grid. In your sample, we have row headers as buttons, how can we add row number as caption to that button?

    Thanks for your time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: