Maxime FRAPPAT

Hum …no thanks ! – Lordinaire

Page 17 of 18

[WinRT] Interact with VisualState and Storyboard with MVVM – Part II

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 !

[WinRT] Interact with VisualState and Storyboard with MVVM – Part I

At this time, Behavior and DataTrigger doesn’t exist in WinRT and that’s too bad because it was very easy to interact with Storyboard or VisualState. So, how to do that with WinRT component ? The answer is DependencyObject !

public class InteractionGrid: DependencyObject
{

    #region VisualState

    public static string GetVisualState(DependencyObject obj)
    {
        return (string)obj.GetValue(VisualStateProperty);
    }

    public static void SetVisualState(DependencyObject obj, string value)
    {
        obj.SetValue(VisualStateProperty, value);
    }

    public static readonly DependencyProperty VisualStateProperty = 
DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(InteractionGrid), new PropertyMetadata(null, (s, e) =>
    {
        var propertyName = (string)e.NewValue;
        var ctrl = s as Control;
        if (ctrl == null)
            throw new InvalidOperationException("This attached property only supports types derived from Control.");
        VisualStateManager.GoToState(ctrl, propertyName, true);
    }));

    #endregion

    #region Storyboard

    public static string GetStoryboard(DependencyObject obj)
    {
        return (string)obj.GetValue(StoryboardProperty);
    }

    public static void SetStoryboard(DependencyObject obj, string value)
    {
        obj.SetValue(StoryboardProperty, value);
    }

    public static readonly DependencyProperty StoryboardProperty =
DependencyProperty.RegisterAttached("Storyboard", typeof(string), typeof(InteractionGrid), new PropertyMetadata(null, (s, e) =>
    {
        var storyboardName = (string)e.NewValue;
        var ctrl = s as FrameworkElement;
        if (ctrl == null)
            throw new InvalidOperationException("This attached property only supports types derived from Control.");

        object sb;
        if (!ctrl.Resources.TryGetValue(storyboardName, out sb) || !(sb is Storyboard))
            return;

        var storyboard = sb as Storyboard;

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

    public static string GetStoryboardAction(DependencyObject obj)
    {
        return (string)obj.GetValue(StoryboardActionProperty);
    }

    public static void SetStoryboardAction(DependencyObject obj, string value)
    {
        obj.SetValue(StoryboardActionProperty, value);
    }

    public static readonly DependencyProperty StoryboardActionProperty =
DependencyProperty.RegisterAttached("StoryboardAction", typeof(string), typeof(InteractionGrid), new PropertyMetadata("Begin"));

    #endregion
}

And how to use it :

<Grid UI:InteractionGrid.Storyboard="{Binding StoryboardName}" UI:InteractionGrid.StoryboardAction="{Binding StoryboardAction}" />

In the next episode, I will go deeper with storyboard and work with the Completed event to have more complex scenarios.

Have fun !

Page 17 of 18

Powered by WordPress & Theme by Anders Norén

%d bloggers like this: