2

I have a base Style - DataGridRowSelectionStyle. On some DataGrids I need to extend this Style to ink the background.

DataGridRowSelectionStyle

<Style TargetType="DataGridRow" x:Key="DataGridRowSelectionStyle">
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
        </Trigger>
        <Trigger Property="IsSelected" Value="True">
            <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Trigger>
    </Style.Triggers>
</Style>

RowStyle

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">                                        
        <Style.Triggers>
            <DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}">
                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>                                                
            </DataTrigger>
            <DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}">
                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>                                                
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

Because of the Trigger order, the two base Triggers are overwritten and IsMouseOver or IsSelected is not triggered anymore.


Solution 1: Extend the RowStyle. Very bad solution, because I would not need my base Style anymore..

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">                                        
        <Style.Triggers>
            <DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}">
                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>                                                
            </DataTrigger>
            <DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}">
                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>                                                
            </DataTrigger>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
            </Trigger>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
                <Setter Property="FontWeight" Value="Bold"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

Solution 2: Create a behavior and add it to the base Style which would reorder the final Style. Problem: Behavior<TriggerCollection> or Behavior<Style> is not working!

The type 'System.Windows.Style' must be convertible to 'System.Windows.DependencyObject' in order to use it as paramter 'T' in the generic class 'System.Windows.Interactivity.Behavior'


Someone got a solution how to use a behavior in a Style or how to change the trigger order in the inherited Style?

Dominic Jonas
  • 4,717
  • 1
  • 34
  • 77

2 Answers2

1

I got a solution by using an AttachedProperty.

I cache every Trigger with an index for the final TriggerCollection. After the DataGridRow is rendered, else if (d is FrameworkElement frameworkElement) is true and the Style gets cloned with a new order of the Triggers.

public static class TriggerAttachedBehavior
{
    private static readonly Dictionary<Trigger, int> _Triggers = new Dictionary<Trigger, int>();

    /// <summary>
    /// Gets property value.
    /// </summary>
    /// <param name="attachedObj"></param>
    /// <returns></returns>
    public static int GetOderIndex(Trigger attachedObj)
    {
        return (int)attachedObj.GetValue(OderIndexProperty);
    }

    /// <summary>
    /// Sets property value.
    /// </summary>
    /// <param name="attachedObj"></param>
    /// <param name="value"></param>
    public static void SetOderIndex(Trigger attachedObj, int value)
    {
        attachedObj.SetValue(OderIndexProperty, value);
    }

    /// <summary>
    /// The <see cref="OderIndexProperty"/> DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty OderIndexProperty = DependencyProperty.RegisterAttached("OderIndex", typeof(int), typeof(TriggerAttachedBehavior), new UIPropertyMetadata(-1, OderIndexChangedCallback));

    /// <summary>
    /// Occurs when OderIndexProperty has changed.
    /// </summary>
    /// <param name="d">Dependency object.</param>
    /// <param name="args">Event arguments.</param>
    private static void OderIndexChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        if (d is Trigger attachedObj)
        {
            _Triggers.Add(attachedObj, (int)args.NewValue);
        }
        else if (d is FrameworkElement frameworkElement)
        {
            // clone style with trigger lock
            var newStyle = new Style(frameworkElement.Style.TargetType, frameworkElement.Style);
            newStyle.Triggers.Clear();

            // add all triggers except the base
            foreach (TriggerBase triggerBase in frameworkElement.Style.Triggers)
            {
                if(_Triggers.Any(t => _Equals(t.Key, triggerBase)))
                    continue;    
                newStyle.Triggers.Add(triggerBase);
            }

            // add the base class triggers
            foreach (int i in _Triggers.Values.OrderBy(t => t))
            {
                newStyle.Triggers.Add(_Triggers.First(t => t.Value == i).Key);
            }

            // apply new style
            frameworkElement.Style = newStyle;
        }
    }

    private static bool _Equals(TriggerBase x, TriggerBase y)
    {
        if (x.GetType() != y.GetType())
            return false;

        switch (x)
        {
            case DataTrigger dataTrigger:
                return false;
            case EventTrigger eventTrigger:
                return false;
            case MultiDataTrigger multiDataTrigger:
                return false;
            case MultiTrigger multiTrigger:
                return false;
            case Trigger trigger:
                return trigger.Property.Name.Equals((y as Trigger).Property.Name);
        }

        return false;
    }
}

<Style TargetType="DataGridRow" x:Key="DataGridRowSelectionStyle" BasedOn="{StaticResource DataGridRowDefaultStyle}">
    <Setter Property="behaviors:TriggerAttachedBehavior.OderIndex" Value="0"></Setter>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True" behaviors:TriggerAttachedBehavior.OderIndex="998">
            <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
        </Trigger>
        <Trigger Property="IsSelected" Value="True" behaviors:TriggerAttachedBehavior.OderIndex="999">
            <Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Trigger>
    </Style.Triggers>
</Style>
Dominic Jonas
  • 4,717
  • 1
  • 34
  • 77
0

MultiDataTrigger can be used to paint rows based on their status value only when IsMouseOver is false and IsSelected is false. Additional conditions (when they are met) prevent MultiDataTriggers from overriding base Triggers:

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}"/>
                    <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False"/>
                    <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
                </MultiDataTrigger.Conditions>

                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>
            </MultiDataTrigger>

            <MultiDataTrigger >
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}"/>
                    <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False"/>
                    <Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
                </MultiDataTrigger.Conditions>

                <Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>
ASh
  • 34,632
  • 9
  • 60
  • 82
  • Is that also your personal solution how you would solve this problem? If you change your base Style after 6 month, it could be possible that you have to fix all inherited styles including customer projects. (The style is part of a framework library and each customer gets a personal application) – Dominic Jonas Aug 01 '18 at 06:06