Dennis Dennis - 4 months ago 41
C# Question

How to set custom DataGrid inactive selection color?

I'm wondering, is there any way to set custom

DataGrid
selection color, when
DataGrid
or window, containing
DataGrid
, becomes inactive?

E.g., here's the
DataGrid
and
ListBox
, displaying the same data. Both controls has one selected item. Initially,
DataGrid
has input focus:

enter image description here

Everything looks OK - selected item in
ListBox
is grayed. Then, let's move focus to the
ListBox
:

enter image description here

Now behavior of
DataGrid
is incorrect - selection color hasn't changed.

I know about
SystemColors.HighlightBrushKey
and
SystemColors.ControlBrushKey
. This XAML was placed in window's resources:

<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="BlueViolet"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="DarkGray"/>


But looks like
DataGrid
ignores the second one -
SystemColors.ControlBrushKey
, and I want
DataGrid
to behave like any other controls (
ListBox
,
ComboBox
,
ListView
).

Something similar I can achieve with triggers:

<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="DarkGray"/>
</MultiTrigger>
</Style.Triggers>
</Style>


enter image description here

But this solution is incomplete. First of all, it grays selected, but unfocused cell, even grid selection unit is
FullRow
. The second thing - trigger doesn't fires when application window loses its focus.

Any suggestions?

UPDATE.

This bug was fixed in .NET 4.5, so, it isn't actual anymore.

Answer

I've found the solution, but it doesn't looks elegant.

Basic problems are:

  • DataGrid.IsFocused is permanently false, because focus has concrete cell, not a grid itself.
  • There's no way to determine in cell style, is there any focused cell in grid. You can only test IsFocused for the current cell.
  • Data grid doesn't reacts on deactivation of a parent window.

The only way to determine, if data grid has a focus, is a checking of DataGrid.CurrentCell property. Unfortunately, it is a struct, and you can't make a trigger, that checks this property for {x:Null}.

To solve these problems, I need two attached properties.
First of them is intended to determine, is there any focused cell in grid. The result must be bool, the source is DataGridCellInfo, so, first of all, the converter must be written:

[ValueConversion(typeof(DataGridCellInfo), typeof(bool))]
public sealed class DataGridCellInfoToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || value.GetType() != typeof(DataGridCellInfo) || targetType != typeof(bool))
            return DependencyProperty.UnsetValue;

        // IsValid will be false, if there's no focused cell.
        return ((DataGridCellInfo)value).IsValid;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return DependencyProperty.UnsetValue;
    }
}

The attached property:

    public static bool GetHasFocusedCell(DependencyObject obj)
    {
        return (bool)obj.GetValue(HasFocusedCellProperty);
    }

    public static void SetHasFocusedCell(DependencyObject obj, bool value)
    {
        obj.SetValue(HasFocusedCellProperty, value);
    }

    public static readonly DependencyProperty HasFocusedCellProperty = DependencyProperty.RegisterAttached(
        "HasFocusedCell",
        typeof(bool), 
        typeof(FocusedCellBehavior),
        new UIPropertyMetadata(false));

The second attached property must be changed, when parent window of grid becomes inactve:

    public static bool GetIsParentWindowActive(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsParentWindowActiveProperty);
    }

    public static void SetIsParentWindowActive(DependencyObject obj, bool value)
    {
        obj.SetValue(IsParentWindowActiveProperty, value);
    }

    public static readonly DependencyProperty IsParentWindowActiveProperty = DependencyProperty.RegisterAttached(
        "IsParentWindowActive", 
        typeof(bool), 
        typeof(FocusedCellBehavior), 
        new UIPropertyMetadata(false));

Now, let's bind attached properties in XAML:

        <!-- A converter to define, is there any focused cell in DataGrid -->
        <local:DataGridCellInfoToBooleanConverter x:Key="DataGridCellInfoToBooleanConverter"/>

    <DataGrid Grid.Row="0" SelectionUnit="FullRow" SelectionMode="Single"
              ItemsSource="{Binding}" 
              local:FocusedCellBehavior.HasFocusedCell="{Binding CurrentCell, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource DataGridCellInfoToBooleanConverter}}"
              local:FocusedCellBehavior.IsParentWindowActive="{Binding IsActive, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>

Next, I need a cell style to set appropriate background color:

        <!-- A style of selected cell in DataGrid, when there's no any focused cells in DataGrid -->
        <Style TargetType="{x:Type DataGridCell}" x:Key="InactiveSelectedCellStyle">
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                </Trigger>
            </Style.Triggers>
        </Style>

and a grid style to fire triggers, when attached properties will change their values:

        <!-- 
            A style of DataGrid, that defines a couple of triggers, which being fired 
            when helper attached properties will change their values 
        -->
        <Style TargetType="{x:Type DataGrid}">
            <Style.Triggers>
                <Trigger Property="local:FocusedCellBehavior.IsParentWindowActive" Value="False">
                    <Setter Property="CellStyle" Value="{StaticResource InactiveSelectedCellStyle}"/>
                </Trigger>
                <Trigger Property="local:FocusedCellBehavior.HasFocusedCell" Value="False">
                    <Setter Property="CellStyle" Value="{StaticResource InactiveSelectedCellStyle}"/>
                </Trigger>
            </Style.Triggers>
        </Style>

Are there any better solutions?

Comments