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