使用EventTrigger设置属性
我希望能够使用EventTrigger来设置一个属性,这里有一些问题。
1)EventTriggers只支持Actions,所以我必须使用storyBoard来设置我的属性。
2)一旦我使用故事板,我有两个select:
- 停止:一旦animation停止,该值将恢复到animation开始之前
- HoldEnd:这将locking属性,以便代码和用户交互都不能改变animation所保存的属性。
在下面的例子中,我想设置IsChecked属性为False,当button被点击时,我希望用户能够更改IsChecked和/或我想要能够更改代码中的属性。
例:
<EventTrigger SourceName="myButton" RoutedEvent="Button.Click"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <BooleanAnimationUsingKeyFrames Storyboard.TargetName="myCheckBox" Storyboard.TargetProperty="IsChecked" FillBehavior="Stop"> <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False" /> </BooleanAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger>
我意识到我可以在故事板完成后使用“Completed”事件来将值设置为False。 然而,在这个例子中,我想包含XAML中的逻辑,因为这个逻辑将被用在一个自定义的控件上,并且只针对UI。
就像我喜欢XAML一样,为了这种types的任务,我切换到后面的代码。 附加的行为是一个很好的模式。 请记住,Expression Blend 3 提供了编程和使用行为的标准方法 。 Expression Community网站上有一些现有的 。
只要创build你自己的行动。
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); } } }
在这种情况下,我绑定到我的viewmodel上名为DialogResult的属性。
<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>
停止Storyboard可以在后面的代码或xaml中完成,具体取决于需求来自哪里。
如果EventTrigger被移出button之外,那么我们可以继续使用另一个EventTrigger作为目标,这个EventTrigger会告诉故事板停止。 当故事板以这种方式停止时,它不会恢复到以前的值。
在这里,我将Button.Click EventTrigger移动到了一个周围的StackPanel,并在CheckBox上添加了一个新的EventTrigger。单击CheckBox时停止Button的Storyboard。 这让我们检查并取消选中CheckBox,并从button给我们所需的取消选中行为。
<StackPanel x:Name="myStackPanel"> <CheckBox x:Name="myCheckBox" Content="My CheckBox" /> <Button Content="Click to Uncheck" x:Name="myUncheckButton" /> <Button Content="Click to check the box in code." Click="OnClick" /> <StackPanel.Triggers> <EventTrigger RoutedEvent="Button.Click" SourceName="myUncheckButton"> <EventTrigger.Actions> <BeginStoryboard x:Name="myBeginStoryboard"> <Storyboard x:Name="myStoryboard"> <BooleanAnimationUsingKeyFrames Storyboard.TargetName="myCheckBox" Storyboard.TargetProperty="IsChecked"> <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False" /> </BooleanAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="CheckBox.Click" SourceName="myCheckBox"> <EventTrigger.Actions> <StopStoryboard BeginStoryboardName="myBeginStoryboard" /> </EventTrigger.Actions> </EventTrigger> </StackPanel.Triggers> </StackPanel>
要停止代码背后的故事板,我们将不得不做一些稍微不同的事情。 第三个button提供了我们将停止故事板并通过代码将IsChecked属性重新设置为true的方法。
我们不能调用myStoryboard.Stop(),因为我们没有通过设置isControllable参数的代码来开始故事板。 相反,我们可以删除故事板。 为此,我们需要Storyboard存在的FrameworkElement,在这里是我们的StackPanel。 一旦故事板被移除,我们可以再一次设置IsChecked属性,并保存到UI中。
private void OnClick(object sender, RoutedEventArgs e) { myStoryboard.Remove(myStackPanel); myCheckBox.IsChecked = true; }
我修改了Neutrino的解决scheme,使xaml在指定值时看起来不那么冗长:
对不起,没有图片的XAML渲染,只要想象一个[=]汉堡包button,你点击,它变成了一个后退button,并切换网格的可见性。
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>
您也可以像Neutrino解决scheme中那样指定值:
<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>
这是代码。
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 } }