Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 208
Dennis Avatar asked Oct 07 '22 11:10

Dennis


1 Answers

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?

like image 129
Dennis Avatar answered Oct 10 '22 03:10

Dennis