In my previous post, I explained how to interact with storyboard like start, pause or stop them. Now I want to know when my animation is finished in order to change a state, launch a specific action, … To achieve this goal, I need to use the Completed event and the famous Invoke method :)

I modify the previous DependencyObject to be able to take a list of storyboard linked to a method (declare in the ViewModel) in parameter. This list is the callback call when the completed event is raised.

First, I create a new DependencyObject called CallMethodAction :

public class CallMethodAction : DependencyObject
{
    public static readonly DependencyProperty MethodNameProperty =
    DependencyProperty.Register("MethodName", typeof(string), typeof(CallMethodAction), new PropertyMetadata(default(string)));

    public string MethodName
    {
        get { return (string)GetValue(MethodNameProperty); }
        set { SetValue(MethodNameProperty, value); }
    }

    public static readonly DependencyProperty ActionNameProperty =
    DependencyProperty.Register("ActionName", typeof(string), typeof(CallMethodAction), new PropertyMetadata(default(string)));

    public string ActionName
    {
        get { return (string)GetValue(ActionNameProperty); }
        set { SetValue(ActionNameProperty, value); }
    }
}

In the InteractionGrid, I add my new list of CallMethodAction :

#region StoryboardCompletedTriggers

public static List GetStoryboardCompletedTriggers(DependencyObject obj)
{
    return (List)obj.GetValue(StoryboardCompletedTriggersProperty);
}

public static void SetStoryboardCompletedTriggers(DependencyObject obj, List value)
{
    obj.SetValue(StoryboardCompletedTriggersProperty, value);
}

public static readonly DependencyProperty StoryboardCompletedTriggersProperty =
DependencyProperty.RegisterAttached("StoryboardCompletedTriggers", typeof(List), typeof(InteractionGrid), new PropertyMetadata(new List()));

#endregion

Now, I need to find if my list contains a CallMethodAction entry who match with the storyboard started. If found, I just handle the Completed event to launch the associated method with Invoke.

private static void GetCompletedTrigger(string storyboardName, Storyboard storyboard, FrameworkElement ctrl)
{
    var triggers = ctrl.GetValue(StoryboardCompletedTriggersProperty) as List;
    try
    {
        if (triggers != null)
        {
            var trigger = triggers.Find(t => t.ActionName.Equals(storyboardName));
            if (trigger == null)
                return;

            storyboard.Completed += (e, s) =>
            {
                var ctx = ctrl.DataContext;
                if (ctx == null)
                    return;

                MethodInfo methodInfo = ctx.GetType().GetRuntimeMethod(trigger.MethodName, new Type[0]);

                // Use the instance to call the method without arguments
                methodInfo.Invoke(ctx, null);
            };
        }
    }
    catch (ArgumentNullException ex)
    {
        return;
    }
}

This method can be set just before starting the storyboard :

...
var action = (string)s.GetValue(StoryboardActionProperty);
switch (action)
{
    case "Begin":
        GetCompletedTrigger(storyboardName, storyboard, ctrl);
        storyboard.Begin();
        break;
    case "Pause":
        storyboard.Pause();
        break;
    case "Stop":
        storyboard.Stop();
        break;
}
...

That’s all. I use it like that :

<UI:InteractionGrid Storyboard="{Binding StoryboardAction}">
    <UI:InteractionGrid.StoryboardCompletedTriggers>
        <UI:CallMethodAction MethodName="TotoCompleted" ActionName="Toto" />
        <UI:CallMethodAction MethodName="TitiCompleted" ActionName="Titi" />
    </UI:InteractionGrid.StoryboardCompletedTriggers>
...
</UI:InteractionGrid>

We can improve the concept by adding DataContext to CallMethodAction if we want to use another context than the current.

Have fun !