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