Friday, February 11, 2011

Silverlight Storyboard and FadeIn FadeOut Actions

In my last post I talked about Silverlight Property Triggers and how we could use them to trigger functionality when a property on our ViewModel was a certain value.  The keen observer will have noted that I glossed over a bit of magic that was happening in the StoryboardAction.  Today's post is going to cover how to create your very own StoryboardAction to love and cherish for all time.  Here is a quick re-cap of what our XAML looked like in the last post;

<ItemsControl
    Margin="0 130 0 0"
    HorizontalAlignment="Left"
    VerticalAlignment="Top"
    Opacity="0.0"
    ItemsSource="{Binding Items}">
            <i:Interaction.Triggers>
                <local:BooleanPropertyTrigger
                    Binding="{Binding FinishedLoading}"
                    TriggerValue="True">
                    <local:StoryboardAction>
                        <Storyboard>
                            <DoubleAnimation
                                To="1.0"
                                Duration="00:00:0.7"
                                Storyboard.TargetProperty="Opacity" />
                        </Storyboard>
                    </local:StoryboardAction>
                </local:BooleanPropertyTrigger>
                <local:BooleanPropertyTrigger
                    Binding="{Binding FinishedLoading}"
                    TriggerValue="False">
                    <local:StoryboardAction>
                        <Storyboard>
                            <DoubleAnimation
                                To="0.0"
                                Duration="00:00:0.4"
                                Storyboard.TargetProperty="Opacity" />
                        </Storyboard>
                    </local:StoryboardAction>
                </local:BooleanPropertyTrigger>
            </i:Interaction.Triggers>
                <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border
                Height="30"
                Width="200"
                Margin="0 2"
                BorderBrush="Plum"
                BorderThickness="3"
                CornerRadius="5">
                <TextBlock
                    VerticalAlignment="Center"
                    HorizontalAlignment="Left"
                    Margin="5 0 0 0"
                    Text="{Binding Name}" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Today, we added another trigger to fade the items out when we re-load our items.  Then, we are going to encapsulate the fade animations in a new action that will reduce our XAML a little bit.

First, lets take a quick look at the StoryboardAction Source.

using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Markup;
using System.Windows.Media.Animation;

[ContentProperty("Story")]
public class StoryboardAction : TriggerAction<FrameworkElement>
{
    /// <summary>
    /// Holder for a target setting state.
    /// </summary>
    private bool hasTargetSet = false;

    /// <summary>
    /// Gets or sets the storyboard for this action.
    /// </summary>
    /// <value>The storyboard to run when invoked.</value>
    public Storyboard Story { get; set; }

    /// <summary>
    /// The <see cref="Target" /> dependency property's name.
    /// </summary>
    public const string TargetPropertyName = "Target";

    /// <summary>
    /// Gets or sets the value of the <see cref="Target" />
    /// property. This is a dependency property. 
    /// You can leave this un-set if you want to use the Trigger's 
    /// AssociatedObject as the Target.
    /// </summary>
    public DependencyObject Target
    {
        get
        {
            return (DependencyObject)GetValue(TargetProperty);
        }
        set
        {
            SetValue(TargetProperty, value);
        }
    }

    /// <summary>
    /// Identifies the <see cref="Target" /> dependency property.
    /// </summary>
    public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
        TargetPropertyName,
        typeof(DependencyObject),
        typeof(StoryboardAction),
        new PropertyMetadata(null));
        

    /// <summary>
    /// Invokes the action.
    /// </summary>
    /// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
    protected override void Invoke(object parameter)
    {
        if (this.Story == null)
            return;

        if(!hasTargetSet)
        {
            // Fall back to the associated object if no target defined.
            var target = this.Target ?? AssociatedObject;
            if (target != null)
            {
                Storyboard.SetTarget(this.Story, target);
                hasTargetSet = true;
            }
        }

        // Stop any previously ran storyboards.
        if (this.Story.GetCurrentState() != ClockState.Stopped)
            this.Story.Stop();
            
        this.Story.Begin();            
    }
}

The important parts of this class are the Invoke logic.  We are doing some basic null checking, then setting the target for our animation to either the Target or the AssociatedObject if no Target was found.  Finally, we make sure the animation is not currently running, then start up our animation.

Extra Snazzy Bonus Implementation Codez


I feel like we are really connecting here, so I'm gonna throw in some extra snazzy bonus material just for you.  Here are some short hand versions of our Opacity Fade Animations that should tame the XAML Monster.



using System;
using System.Windows;
using System.Windows.Media.Animation;

public class FadeOutAction : FadeAction
{
    public FadeOutAction()
        : base(.2, 0.0)
    { }
}

public class FadeInAction : FadeAction
{
    public FadeInAction()
        : base(.6, 1.0)
    { }
}

public class FadeAction : StoryboardAction
{

    public FadeAction(double durationSeconds = .6, double fadeTo = 0.0)
    {
        var anim = new DoubleAnimation { Duration = new Duration(TimeSpan.FromSeconds(durationSeconds)), To = fadeTo };
        Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity"));

        this.Story = new Storyboard();
        this.Story.Children.Add(anim);
    }
}

Now our XAML is looking slightly better.

<ItemsControl
    Margin="0 130 0 0"
    HorizontalAlignment="Left"
    VerticalAlignment="Top"
    Opacity="0.0"
    ItemsSource="{Binding Items}">
            <i:Interaction.Triggers>
                <local:BooleanPropertyTrigger
                    Binding="{Binding FinishedLoading}"
                    TriggerValue="True">
                    <local:FadeInAction />
                </local:BooleanPropertyTrigger>
                <local:BooleanPropertyTrigger
                    Binding="{Binding FinishedLoading}"
                    TriggerValue="False">
                    <local:FadeOutAction />
                </local:BooleanPropertyTrigger>
            </i:Interaction.Triggers>
                <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border
                Height="30"
                Width="200"
                Margin="0 2"
                BorderBrush="Plum"
                BorderThickness="3"
                CornerRadius="5">
                <TextBlock
                    VerticalAlignment="Center"
                    HorizontalAlignment="Left"
                    Margin="5 0 0 0"
                    Text="{Binding Name}" />
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Next time, we will create a new FadeyBehavior to encapsulate our fading triggers.

Now Playing - Jay Z, Rick Ross - Hustlin Remix


No comments:

Post a Comment