Setting a property with an EventTrigger
Just create your own action.
namespace WpfUtil{ using System.Reflection; using System.Windows; using System.Windows.Interactivity; /// <summary> /// Sets the designated property to the supplied value. TargetObject /// optionally designates the object on which to set the property. If /// TargetObject is not supplied then the property is set on the object /// to which the trigger is attached. /// </summary> public class SetPropertyAction : TriggerAction<FrameworkElement> { // PropertyName DependencyProperty. /// <summary> /// The property to be executed in response to the trigger. /// </summary> public string PropertyName { get { return (string)GetValue(PropertyNameProperty); } set { SetValue(PropertyNameProperty, value); } } public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register("PropertyName", typeof(string), typeof(SetPropertyAction)); // PropertyValue DependencyProperty. /// <summary> /// The value to set the property to. /// </summary> public object PropertyValue { get { return GetValue(PropertyValueProperty); } set { SetValue(PropertyValueProperty, value); } } public static readonly DependencyProperty PropertyValueProperty = DependencyProperty.Register("PropertyValue", typeof(object), typeof(SetPropertyAction)); // TargetObject DependencyProperty. /// <summary> /// Specifies the object upon which to set the property. /// </summary> public object TargetObject { get { return GetValue(TargetObjectProperty); } set { SetValue(TargetObjectProperty, value); } } public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register("TargetObject", typeof(object), typeof(SetPropertyAction)); // Private Implementation. protected override void Invoke(object parameter) { object target = TargetObject ?? AssociatedObject; PropertyInfo propertyInfo = target.GetType().GetProperty( PropertyName, BindingFlags.Instance|BindingFlags.Public |BindingFlags.NonPublic|BindingFlags.InvokeMethod); propertyInfo.SetValue(target, PropertyValue); } }}
In this case I'm binding to a property called DialogResult on my viewmodel.
<Grid> <Button> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <wpf:SetPropertyAction PropertyName="DialogResult" TargetObject="{Binding}" PropertyValue="{x:Static mvvm:DialogResult.Cancel}"/> </i:EventTrigger> </i:Interaction.Triggers> Cancel </Button></Grid>
As much as I love XAML, for this kinds of tasks I switch to code behind. Attached behaviors are a good pattern for this. Keep in mind, Expression Blend 3 provides a standard way to program and use behaviors. There are a few existing ones on the Expression Community Site.
I modified Neutrino's solution to make the xaml look less verbose when specifying the value:
Sorry for no pictures of the rendered xaml, just imagine a [=] hamburger button that you click and it turns into [<-] a back button and also toggles the visibility of a Grid.
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"...<Grid> <Button x:Name="optionsButton"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:SetterAction PropertyName="Visibility" Value="Collapsed" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="Visible" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Visible" /> </i:EventTrigger> </i:Interaction.Triggers> <glyphs:Hamburger Width="10" Height="10" /> </Button> <Button x:Name="optionsBackButton" Visibility="Collapsed"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:SetterAction PropertyName="Visibility" Value="Collapsed" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsButton}" Value="Visible" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Collapsed" /> </i:EventTrigger> </i:Interaction.Triggers> <glyphs:Back Width="12" Height="11" /> </Button></Grid>...<Grid Grid.RowSpan="2" x:Name="optionsPanel" Visibility="Collapsed"></Grid>
You can also specify values this way like in Neutrino's solution:
<Button x:Name="optionsButton"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:SetterAction PropertyName="Visibility" Value="{x:Static Visibility.Collapsed}" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="{x:Static Visibility.Visible}" /> <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="{x:Static Visibility.Visible}" /> </i:EventTrigger> </i:Interaction.Triggers> <glyphs:Hamburger Width="10" Height="10" /></Button>
And here's the code.
using System;using System.ComponentModel;using System.Reflection;using System.Windows;using System.Windows.Interactivity;namespace Mvvm.Actions{ /// <summary> /// Sets a specified property to a value when invoked. /// </summary> public class SetterAction : TargetedTriggerAction<FrameworkElement> { #region Properties #region PropertyName /// <summary> /// Property that is being set by this setter. /// </summary> public string PropertyName { get { return (string)GetValue(PropertyNameProperty); } set { SetValue(PropertyNameProperty, value); } } public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register("PropertyName", typeof(string), typeof(SetterAction), new PropertyMetadata(String.Empty)); #endregion #region Value /// <summary> /// Property value that is being set by this setter. /// </summary> public object Value { get { return (object)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(SetterAction), new PropertyMetadata(null)); #endregion #endregion #region Overrides protected override void Invoke(object parameter) { var target = TargetObject ?? AssociatedObject; var targetType = target.GetType(); var property = targetType.GetProperty(PropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); if (property == null) throw new ArgumentException(String.Format("Property not found: {0}", PropertyName)); if (property.CanWrite == false) throw new ArgumentException(String.Format("Property is not settable: {0}", PropertyName)); object convertedValue; if (Value == null) convertedValue = null; else { var valueType = Value.GetType(); var propertyType = property.PropertyType; if (valueType == propertyType) convertedValue = Value; else { var propertyConverter = TypeDescriptor.GetConverter(propertyType); if (propertyConverter.CanConvertFrom(valueType)) convertedValue = propertyConverter.ConvertFrom(Value); else if (valueType.IsSubclassOf(propertyType)) convertedValue = Value; else throw new ArgumentException(String.Format("Cannot convert type '{0}' to '{1}'.", valueType, propertyType)); } } property.SetValue(target, convertedValue); } #endregion }}