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
No comments:
Post a Comment