Saturday, February 12, 2011

Silverlight Fade Control Behavior - FadeyBehavior

In my last post about Silverlight Property Triggers and Storyboard Actions I showed a way for us to fade in an element when a property on our view model was a certain value.  We used this to fade in a list of items after we loaded them, and ultimately we could fade them out while we were re-loading more items.  The code looked kind of like this;

<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>

Now, the more I looked at all that XAML the more I thought we could make this a lot simpler.  By the end of this post we will hopefully end up having something much simpler to use; like this;

<ItemsControl
    Margin="0 130 0 0"
    HorizontalAlignment="Left"
    VerticalAlignment="Top"
    Opacity="0.0"
    ItemsSource="{Binding Items}">
    <i:Interaction.Behaviors>
        <local:FadeyBehavior
            Binding="{Binding FinishedLoading}" />
    </i:Interaction.Behaviors>
    <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>

So, what we've done is encompassed our two triggers and their storyboards into a new Behavior.  Our new FadeyBehavior creates and attaches our to FadeIn and FadeOut triggers along with the associated FadeInAction and FadeOutAction.  The secret sauce is in cascading the binding down to the triggers in the code behind.

using System.Windows;
using System.Windows.Data;
using System.Windows.Interactivity;

/// <summary>
/// A behavior for fading an element based on whether a bound property is true or false.
/// </summary>
public class FadeyBehavior : Behavior<FrameworkElement>
{
    /// <summary>
    /// The <see cref="Binding" /> dependency property's name.
    /// </summary>
    public const string BindingPropertyName = "Binding";

    /// <summary>
    /// Gets or sets the value of the <see cref="Binding" />
    /// property. This is a dependency property.
    /// </summary>
    public object Binding
    {
        get
        {
            return (object)GetValue(BindingProperty);
        }
        set
        {
            SetValue(BindingProperty, value);
        }
    }

    /// <summary>
    /// Identifies the <see cref="Binding" /> dependency property.
    /// </summary>
    public static readonly DependencyProperty BindingProperty = DependencyProperty.Register(
        BindingPropertyName,
        typeof(object),
        typeof(FadeyBehavior),
        new PropertyMetadata(null));


    protected override void OnAttached()
    {
        if (AssociatedObject.Opacity != 0.0)
            AssociatedObject.Opacity = 0.0;

        // Create our fade in/out triggers
        var triggerIn = new FadeInTrigger();
        var triggerOut = new FadeOutTrigger();

        // Bind the Binding property in this behavior to the underlying triggers.
        var b = new Binding("Binding") { Source = this };
        BindingOperations.SetBinding(triggerIn, BooleanPropertyTrigger.BindingProperty, b);
        BindingOperations.SetBinding(triggerOut, BooleanPropertyTrigger.BindingProperty, b);

        // Add our triggers to the associated object.
        var currTriggers = Interaction.GetTriggers(AssociatedObject);
        currTriggers.Add(triggerIn);
        currTriggers.Add(triggerOut);

        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
    }
}

For the time being, I've only had to use this for a boolean property trigger, but it could be expanded into a base class for other trigger types (String, DateTime range, WidgetA, etc.).

You can download the code to mess around with it yourself.

Now Playing - La Roux - In For The Kill

No comments:

Post a Comment