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

Sync Multi Select Listbox with ViewModel

Today one of my colleagues at Infusion asked me how he could sync up the selection of a ListBox with his ViewModel. WPF supports already single mode selection via the ICollectionView yet when it comes to MultiSelect there is no out of the box support in WPF.

Attached Properties to the rescue ….. 🙂

I quickly when in my VS and wrote up a simple Attached property that hooks to the SelectionChanged event of the ListBox and populates a list of selected items to the ViewModel.

This is how the attached property looks like

   1: #region SelectedItems

   2:  

   3: /// <summary>

   4: /// SelectedItems Attached Dependency Property

   5: /// </summary>

   6: public static readonly DependencyProperty SelectedItemsProperty =

   7:     DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(ListBoxHelper),

   8:         new FrameworkPropertyMetadata((IList)null,

   9:             new PropertyChangedCallback(OnSelectedItemsChanged)));

  10:  

  11: /// <summary>

  12: /// Gets the SelectedItems property.  This dependency property 

  13: /// indicates ....

  14: /// </summary>

  15: public static IList GetSelectedItems(DependencyObject d)

  16: {

  17:     return (IList)d.GetValue(SelectedItemsProperty);

  18: }

  19:  

  20: /// <summary>

  21: /// Sets the SelectedItems property.  This dependency property 

  22: /// indicates ....

  23: /// </summary>

  24: public static void SetSelectedItems(DependencyObject d, IList value)

  25: {

  26:     d.SetValue(SelectedItemsProperty, value);

  27: }

  28:  

  29: /// <summary>

  30: /// Handles changes to the SelectedItems property.

  31: /// </summary>

  32: private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

  33: {

  34:     var listBox = (ListBox)d;

  35:     ReSetSelectedItems(listBox);

  36:     listBox.SelectionChanged += delegate

  37:     {

  38:         ReSetSelectedItems(listBox);

  39:     };

  40: }

  41:  

  42: #endregion

  43:  

  44: private static void ReSetSelectedItems(ListBox listBox)

  45: {

  46:     IList selectedItems = GetSelectedItems(listBox);

  47:     selectedItems.Clear();

  48:     if (listBox.SelectedItems != null)

  49:     {

  50:         foreach (var item in listBox.SelectedItems)

  51:             selectedItems.Add(item);

  52:     }

  53: }

and here is how you would use it in the XAML

   1: <ListBox ItemsSource="{Binding MyData}" Grid.Column="1" local:ListBoxHelper.SelectedItems="{Binding SelectedData}"

   2:                  SelectionMode="Extended"/>

As you can see all you have to set is the List you want to populate in the ViewModel and the rest is taken care of by the Attached Property.

Please Note: This code was implemented quickly without any testing to prove an implementation idea, so please do review it before putting it in production 🙂

Download the source code