Quantcast
Channel: Jacob Gable's Posterous
Viewing all articles
Browse latest Browse all 17

Silverlight 4 Property Triggers

$
0
0

I spent a little time this week messing around with the newly added Triggers and TriggerActions available through the new Expression Blend 4 SDK.

Triggers and Behaviors are really just ways to attach functionality to an existing element, and the base classes that are included in the newer version of Silverlight 4 really make the job easier.  I'm going to walk through adding a trigger that fires when one of the properties on my ViewModel changes to true.  Now allegedly there is an existing trigger (DataStoreChangedTrigger) that will fire actions based on when a bound property changes, but I want to only fire my actions when my bound property becomes a specific value.

Our Goal

<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>    </i:Interaction.Triggers></ItemsControl>

The Codez

[Download the PropertyTrigger Example Source Project and play along at home]

To start out with, I create a base PropertyChangedTrigger class that will do most of the heavy lifting for us.  Essentially, we want to inherit from the TriggerBase<...> generic base class and specify that we want our Trigger to attach to a FrameworkElement (I suppose you could use another type of control class, but FrameworkElement will encompass just about any element with a DataContext, which I find useful).  Our PropertyChangedTrigger will expose a Binding property that will allow us to attach an event handler when our bound property changes so we can invoke our TriggerActions.

/// <summary>/// A base property changed trigger that/// fires whenever the bound property changes./// </summary>public class PropertyChangedTrigger : TriggerBase<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(PropertyChangedTrigger),        new PropertyMetadata(null,            new PropertyChangedCallback(Binding_ValueChanged)));    /// <summary>    /// Called after the trigger is attached to an AssociatedObject.    /// </summary>    protected override void OnAttached()    {        base.OnAttached();    }    /// <summary>    /// Called when the trigger is being detached    /// from its AssociatedObject,    /// but before it has actually occurred.    /// </summary>    protected override void OnDetaching()    {        base.OnDetaching();    }    /// <summary>    /// Occurs when Binding's value changes.    /// </summary>    /// <param name="obj">The obj on which the binding changed.</param>    /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>    private static void Binding_ValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)    {        var trig = obj as PropertyChangedTrigger;        if (trig != null && trig.ShouldTriggerFire(args.NewValue))        {            trig.OnPropertyTrigger(args.NewValue);        }    }    /// <summary>    /// Does the change logic test. By default, it will always fire on value change.    /// </summary>    /// <param name="newValue">The new value.</param>    /// <returns>True if the trigger should fire, otherwise false.</returns>    protected virtual bool ShouldTriggerFire(object newValue)    {        return true;    }    /// <summary>    /// Called when [property trigger].    /// </summary>    /// <param name="value">The value of the property.</param>    protected virtual void OnPropertyTrigger(object value)    {        base.InvokeActions(value);    }}

As you can tell, our PropertyChangedTrigger makes use of a virtual method ShouldTriggerFire(…) that will default to just fire everytime a property changes value. Next, we will override our base class to create an EqualsPropertyTrigger that only fires when the value changes to a specific one that we want.

/// <summary>/// A base class for property triggers that must be equal to fire./// </summary>/// <typeparam name="TValue">The type of the trigger value.</typeparam>/// <summary>/// A base class for property triggers that must be equal to fire./// </summary>/// <typeparam name="TValue">The type of the trigger value.</typeparam>public class EqualsPropertyTrigger<TValue> : PropertyChangedTrigger{    /// <summary>    /// Gets or sets the trigger value to match the property value for.    /// </summary>    /// <value>The trigger value.</value>    public TValue TriggerValue { get; set; }    /// <summary>    /// Logic to check whether the trigger should fire.    /// </summary>    /// <param name="newValue">The new value.</param>    /// <returns>True if the trigger should fire, otherwise false.</returns>    protected override bool ShouldTriggerFire(object newValue)    {        if (newValue == null)            return this.TriggerValue == null;        return newValue.Equals(this.TriggerValue);    }}

So now we have a nice base class for our BooleanPropertyTrigger that makes it's implementation really nice and clean.

/// <summary>/// A generic object property trigger/// </summary>public class PropertyTrigger : EqualsPropertyTrigger<object>{ }/// <summary>/// A Boolean value property trigger/// </summary>public class BooleanPropertyTrigger : EqualsPropertyTrigger<bool>{ }/// <summary>/// A string property value trigger/// </summary>public class StringPropertyTrigger : EqualsPropertyTrigger<string>{ }

Now, all that's left to do is hook it up in our XAML by adding the namespace to our trigger and making sure we have a reference to System.Windows.Interactivity (version 4.0.5.0).

Next Steps

Next, we could make a NotEqualsPropertyTrigger that fires when a value is not a certain value. It's implementation would be as easy as inheriting from the EqualsPropertyTrigger and negating the base ShouldFireTrigger(...) method.

Hopefully, like me, you've learned a little about triggers and how they can be useful. For my next blog post I'm going to incorporate the visual state manager and make a GoToStateAction along with talking a little bit about creating the StoryBoardAction you see in the example.

Now Playing - Pretty Lights - Hot Like Sauce

Permalink | Leave a comment  »


Viewing all articles
Browse latest Browse all 17

Trending Articles