Frage

I have a WPF Grid and I want to highlight a cell when a value changes. This does work with the EventTrigger below, but highlights every cell when the window is loaded. Is there a "better" WPF way to only trigger after the initial value was set or the windows was loaded?

<Style TargetType="DataGridCell" x:Key="FlashStyle">
    <Style.Triggers>
        <EventTrigger RoutedEvent="Binding.TargetUpdated">
                <BeginStoryboard>
                    <Storyboard x:Name="Blink" AutoReverse="True">
                        <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
                            <EasingColorKeyFrame KeyTime="00:00:01" Value="Red" />
                        </ColorAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
        </EventTrigger>
    </Style.Triggers>
</Style>
War es hilfreich?

Lösung

As promised, I've revisited this answer to give an example. I found out my original answer couldn't work since it's not possible to modify the triggers collection at runtime. There is another solution though which I will explain here.

Updated answer

Since we're unable to make changes to the triggers collection at runtime, I see two possible options:

  1. Make a smart EventTrigger which will know how to handle the first TargetUpdated event and ignore it.
  2. Create our own new attached event, make the EventTrigger handle it, and raise it only when we want the EventTrigger to fire, i.e. every time the target is updated except the first time.

The first option may be possible by using Blend SDK and inheriting from TriggerBase<T>. I haven't tried that, but it could be a good way to go.

The second option is fairly simple, and is provided here:

public class AfterLoadBehavior
{
    #region TargetUpdatedAfterLoad Attached Event

    public static readonly RoutedEvent TargetUpdatedAfterLoadEvent = EventManager.RegisterRoutedEvent(
        "TargetUpdatedAfterLoad", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AfterLoadBehavior));

    public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
    {
        UIElement uiElement = d as UIElement;
        if (uiElement != null)
        {
            uiElement.AddHandler(TargetUpdatedAfterLoadEvent, handler);
        }
    }

    public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
    {
        UIElement uiElement = d as UIElement;
        if (uiElement != null)
        {
            uiElement.RemoveHandler(TargetUpdatedAfterLoadEvent, handler);
        }
    }

    #endregion

    #region RaiseTargetUpdatedAfterLoadEvent Attached Property

    public static bool GetRaiseTargetUpdatedAfterLoadEvent(FrameworkElement obj)
    {
        return (bool)obj.GetValue(RaiseTargetUpdatedAfterLoadEventProperty);
    }

    public static void SetRaiseTargetUpdatedAfterLoadEvent(FrameworkElement obj, bool value)
    {
        obj.SetValue(RaiseTargetUpdatedAfterLoadEventProperty, value);
    }

    public static readonly DependencyProperty RaiseTargetUpdatedAfterLoadEventProperty =
        DependencyProperty.RegisterAttached("RaiseTargetUpdatedAfterLoadEvent", typeof(bool), typeof(AfterLoadBehavior),
        new PropertyMetadata(false, OnRaiseTargetUpdatedAfterLoadEventChanged));

    private static void OnRaiseTargetUpdatedAfterLoadEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = (FrameworkElement)d;
        frameworkElement.AddHandler(Binding.TargetUpdatedEvent, new RoutedEventHandler((sender, args) =>
            {
                if (GetIsFirstValueChange(frameworkElement))
                {
                    SetIsFirstValueChange(frameworkElement, false);
                    return;
                }

                frameworkElement.RaiseEvent(new RoutedEventArgs(AfterLoadBehavior.TargetUpdatedAfterLoadEvent));
            }));
    }

    #endregion

    #region IsFirstValueChange Attached Property

    public static bool GetIsFirstValueChange(FrameworkElement obj)
    {
        return (bool)obj.GetValue(IsFirstValueChangeProperty);
    }

    public static void SetIsFirstValueChange(FrameworkElement obj, bool value)
    {
        obj.SetValue(IsFirstValueChangeProperty, value);
    }

    public static readonly DependencyProperty IsFirstValueChangeProperty =
        DependencyProperty.RegisterAttached("IsFirstValueChange", typeof(bool), typeof(AfterLoadBehavior),
        new PropertyMetadata(true));

    #endregion
}

Using this, you'll need to modify your style like so:

<Style TargetType="DataGridCell" x:Key="FlashStyle">
    <Setter Property="local:AfterLoadBehavior.RaiseTargetUpdatedAfterLoadEvent" Value="True" />
    <Style.Triggers>
        <EventTrigger RoutedEvent="{x:Static local:AfterLoadBehavior.TargetUpdatedAfterLoadEvent}">
                <BeginStoryboard>
                    <Storyboard x:Name="Blink" AutoReverse="True">
                        <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
                            <EasingColorKeyFrame KeyTime="00:00:01" Value="Red" />
                        </ColorAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
        </EventTrigger>
    </Style.Triggers>
</Style>

What this behavior does is register to the Binding.TargetUpdated event and ignores the first time it's called. For the other times, it raises the newly created TargetUpdatedAfterLoadEvent, which is handled by your EventTrigger.

So it turned out to be a bit more complicated than I originally expected, but nothing crazy is going on here and once you add this behavior to your code, it's very easy to use. Hope this helps.


Original answer (won't work)

I'm not aware of a simple XAML-only solution for this, but you can write an attached property which will register to the DataGridCell's Loaded event and will only add the event trigger once it has been loaded.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top