C# Disciples

my life in Avalon ….

Animations and MVVM

This week I was asked from a colleague of mine, ‘How do I fire a command once a storyboard has completed?’. Do we have to break the MVVM here and write code behind. The answer is simple, HELL NO….

Attached Behavior to the rescue. Basically we can do this task by using an attached property, that will hook up to the Completed event of the storyboard and fire the command for us on the ViewModel. Here’s how to do this

   1: var timeline = d as Timeline;

   2: if (timeline != null && !timeline.IsFrozen)

   3: {

   4:    timeline.Completed += delegate

   5:    {

   6:        ICommand command = GetCommand(d);

   7:        object param = GetCommandParameter(d);

   8:  

   9:        if(command != null && command.CanExecute( param ))

  10:            GetCommand(d).Execute(GetCommandParameter(d));

  11:    };

  12: }

and here is how to use this in XAML

   1: <EventTrigger RoutedEvent="ButtonBase.Click">

   2:     <BeginStoryboard>

   3:         <Storyboard cmds:CommandStoryboard.Command="{Binding Close}" >

   4:             <DoubleAnimation Storyboard.TargetName="border" Storyboard.TargetProperty="Height" 

   5:                          DecelerationRatio="1.0" Duration="0:0:0.5" To="0"/>

   6:         </Storyboard>

   7:     </BeginStoryboard>

   8: </EventTrigger>

In the sample application (that is provided below with this article) shows how to animate some item in an items control and when the animation is completed the Storyboard executes a command that actually removes the item from the binding list.

I also created another simple class so that you can execute a Command from an EventTrigger. This is very handy since sometimes we want to execute a Command on a VM when some arbitrary RoutedEvent is raised. Here is the code for that

   1: public class CommandTimeline : Storyboard

   2: {

   3:     protected override Duration GetNaturalDurationCore(Clock clock)

   4:     {

   5:         return new Duration(TimeSpan.FromTicks(1));

   6:     }

   7:  

   8:     protected override Freezable CreateInstanceCore()

   9:     {

  10:         return new CommandTimeline();

  11:     }

  12: }

and here is how to consume it

   1: <EventTrigger RoutedEvent="FrameworkElement.Loaded">

   2:     <BeginStoryboard>

   3:         <cmds:CommandTimeline cmds:CommandStoryboard.Command="{Binding Test}" />

   4:     </BeginStoryboard>

   5: </EventTrigger>

As you can see this reuses the attached behavior that is used on normal storyboards. The only difference between a Storyboard and a CommandTimeline is that a CommandTimeline returns a Duration of 0 so the Completed event gets raised immediately.

PLEASE NOTE: This can be only used in an event trigger. When this is used with DataTriggers etc you will get an exception saying that the Trigger cannot be sealed. This happens because you cannot hook up to the Completed event of a storyboard in a DataTrigger/Trigger. In order to workaround this I use AttacedRoutedEvents so that I can have EventTriggers executing my commands.

Download the sample app + source code

About these ads

June 13, 2009 - Posted by | mvvm, WPF

14 Comments »

  1. Yeah baby

    Comment by sacha | June 13, 2009 | Reply

  2. :)

    Comment by marlongrech | June 13, 2009 | Reply

  3. Well done Marlon… As usual!

    Comment by Rudi Grobler | June 14, 2009 | Reply

  4. Thanks Rudi

    Comment by marlongrech | June 14, 2009 | Reply

  5. Well done Marlon!! proud of u and Im really happy to see you doing what u love :)

    Comment by Chris | June 14, 2009 | Reply

  6. Maybe a Debug.Fail(…) would be helpful if the storyboard is frozen and you can’t hook the Completed event, just so the developer would know why the command isn’t being executed.

    Comment by Josh Smith | June 15, 2009 | Reply

  7. good point Mr.Smith

    Comment by marlongrech | June 15, 2009 | Reply

  8. [...] Animations and MVVM (marlongrech) [...]

    Pingback by Dew Dump – June 13-15, 2009 | Alvin Ashcraft's Morning Dew | June 15, 2009 | Reply

  9. Nice example..Just have one thought, I see that you are subscribing to the Completed event of the storyboard but i don’t see anywhere that you are unsubscribing..Will this not be a memory leak?

    Comment by DS | July 10, 2009 | Reply

  10. Very strange. When I change:

    to:

    I get:

    Cannot convert the value in attribute ‘Style’ to object of type ‘System.Windows.Style’. Cannot freeze this Storyboard timeline tree for use across threads. Error at object ‘System.Windows.Controls.Button’ in markup file ‘EnhancedDashboard;component/view/dashboardview.xaml’ Line 862 Position 18.

    Comment by johnhamm | September 22, 2009 | Reply

  11. Apparently my xaml was interpreted as HTML in my last comment. here it is again:

    I change my working eventtrigger from:

    <EventTrigger RoutedEvent=”Button.Click”>
    <BeginStoryboard>
    <Storyboard >

    to use the attached property:

    <EventTrigger RoutedEvent=”Button.Click”>
    <BeginStoryboard>
    <Storyboard views:Attachments.DelayedCommand=”{Binding UpdateCommand}” >

    And I get the following error:
    Cannot convert the value in attribute ‘Style’ to object of type ‘System.Windows.Style’. Cannot freeze this Storyboard timeline tree for use across threads. Error at object ‘System.Windows.Controls.Button’ in markup file ‘EnhancedDashboard;component/view/dashboardview.xaml’ Line 862 Position 18.

    Basically, all I’m trying to do is make my button not execute its command until after the click animation is finished! As a workaround, I’m using Thread.Sleep(2000) in my command’s handler since teh animation is 2 seconds long! But I hate that solution.

    Comment by johnhamm | September 22, 2009 | Reply

  12. Read the please note part of the article …last sentence

    Comment by Marlon Grech | September 22, 2009 | Reply

  13. Hi,
    I’m new to WPF.
    Can you explain how to attach the property to StoryBoard? [ cmds:CommandStoryboard.Command="{Binding Close}" ].
    I could not understand…

    Comment by Babu | February 10, 2012 | Reply

  14. Can somebody let me know how to use this with datatrigger using attached routed event?

    Comment by Babu | February 12, 2012 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 845 other followers

%d bloggers like this: