Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Triggering an animation from an event using MVVM

I seem to have reached some sort of MVVM breaking point here.

I would like for a control to have its opacity animated for half a second (DoubleAnimation from 0.5 to 1.0) when the underlying view model object have its "Status" property changed. I achieved this at first using a DataTrigger but since I haven't found a way to react to ANY change, just a given value, I had to always flip the VM objects "Status" property to a special "pending" value before setting it to its intended value. (Is there a way to react to ANY change btw?)

This was hacky so I started fiddling with EventTriggers instead...

This is what I've tried so far:

  1. Using a normal EventTrigger

This seems to require a RoutedEvent but that, in turn, requires that my underlying view model object inherits from DependencyObject.

  1. Using i:Interaction.Triggers

That way I can listen to and react to normal .NET events but I haven't found a way to start a StoryBoard using that approach.

  1. Using i:Interaction.Triggers and writing a Behavior

This experiment fell short on the fact I found no way to attach my custom behavior to its associated control.

This is what the XAML looked like:

   <cc:MyControl>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Updated">
                <i:Interaction.Behaviors>
                    <cv:OpacityBehavior Duration="0:0:0:5" />
                </i:Interaction.Behaviors>
            </i:EventTrigger>
        </i:Interaction.Triggers>

And here's the custom behavior:

class OpacityBehavior : Behavior<MyControl>
{
    public Duration Duration { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
        var associatedObject = lookupVisualParent(this);
        associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
    }
}

That didn't work because the XAML parser required it to be attached directly to "MyControl" but I need to attach it to the event trigger. I then tried this approach:

class OpacityBehavior : Behavior<DependencyObject>
{
    public Duration Duration { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
        var associatedObject = lookupVisualParent(this);
        associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
    }

    private UIElement lookupVisualParent(DependencyObject dObj)
    {
        if (dObj is UIElement)
            return (UIElement) dObj;

        if (dObj == null)
            return null;

        return lookupVisualParent(LogicalTreeHelper.GetParent(dObj));
    }
}

This failed on the fact that lookupVisualParent doesn't work. The logical parent of the behavior is always null.

It strikes me this should be a fairly common task? Is there a good solution to this problem? I find it strange that I will have write my view model classes so that they derive from DependencyObject in order to start an animation when an event fires.

Cheers

like image 1000
Jonas Rembratt Avatar asked Oct 19 '11 16:10

Jonas Rembratt


1 Answers

You could simply use a flag: set a flag on your VM called 'RecentlyChangedFlag'. Pulse it to true, then false, whenever the appropriate value(s) change. You could do that like this:

private bool _changedFlag;
public bool ChangedFlag
{
    get
    {
        if (_changedFlag)
        {
            _changedFlag = false;
            OnPropertyChanged("ChangedFlag");
            return true;
        }
        // (else...)
        return false;
    }
    protected set
    {
        _changedFlag = value;
        OnPropertyChanged("ChangedFlag");
    }
}

I.e., with the above code set ChangedFlag = true when you want to signal the animation to start. It will be reset to false after WPF queries the true value.

Then have the animation occur when the value of RecentlyChangedFlag is true, as an EnterAction for instance.

Hope that helps.

like image 169
Kieren Johnstone Avatar answered Oct 13 '22 10:10

Kieren Johnstone