使用 EventTrigger 设置属性

我希望能够用 EventTrigger 设置一个属性,这里有很多问题。

1) EventTriggers 只支持操作,所以我必须使用故事板来设置我的属性。

2)一旦我使用故事板,我有两个选择:

  • 停止: 一旦动画已经停止,值恢复到动画开始之前
  • HoldEnd: 这会锁定属性,因此代码或用户交互都不能更改动画所持有的属性。

在下面的例子中,我希望在单击按钮时将 IsChecked 属性设置为 False,并且希望用户能够更改 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。

129175 次浏览

尽管我非常喜欢 XAML,但是对于这种类型的任务,我会切换到后台代码。附加行为是一个很好的模式。请记住,表达式混合3 提供了一个标准的方法编程和使用行为。在 Expression 社区站点上有 现存的一些

停止故事板可以在后面的代码中完成,也可以在 xaml 中完成,这取决于需求来自何处。

如果 EventTrigger 被移出按钮,那么我们可以继续并用另一个 EventTrigger 来定位它,这个 EventTrigger 将告诉故事板停止。当故事板以这种方式停止时,它将不会恢复到先前的值。

在这里,我已经移动了按钮。在周围的 StackPanel 中单击 EventTrigger,并在 CheckBox 上添加一个新的 EventTrigger。单击此处可在单击 CheckBox 时停止 Button 的故事板。这使我们可以在单击 CheckBox 时检查和取消检查,并从按钮中给出所需的取消检查行为。

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

为了停止后面代码中的故事板,我们必须做一些稍微不同的事情。第三个按钮提供了一个方法,我们将在这个方法中停止故事板,并通过代码将 IsChecked 属性设置回 true。

我们不能叫我的故事板。Stop () ,因为我们没有通过设置 isControlable 参数的代码启动 Storyboard。相反,我们可以移除故事板。为此,我们需要故事板所在的 FrameworkElement,在本例中是我们的 StackPanel。一旦情节串连板被删除,我们可以再次设置 IsChecked 属性,并将其保留到 UI 中。

    private void OnClick(object sender, RoutedEventArgs e)
{
myStoryboard.Remove(myStackPanel);
myCheckBox.IsChecked = true;
}

只要创造你自己的行动。

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);
}
}
}

在本例中,我绑定到视图模型上名为 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>

我修改了 Neutrino 的解决方案,使 xaml 在指定值时看起来不那么冗长:

很抱歉没有呈现 xaml 的图片,只要想象一下你点击一个[ = ]汉堡包按钮,它就会变成[ <-]后退按钮,并切换网格的可见性。

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 的解决方案一样:

<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
}
}

编辑: 交互 dll 不再是 Blend 的一部分,现在是“微软”。赛姆。行为。NuGet 软件包。代码列在这里: https://github.com/microsoft/XamlBehaviorsWpf

见: https://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/

从旧版混合 Microsoft 迁移的步骤。表情。Dll 到新的开源互动 dll (希望我的旧笔记是正确的; p) :

1. Install the "Microsoft.Xaml.Behaviors.Wpf" NuGet package.


2. Edit xaml files:
Replace 'http://schemas.microsoft.com/expression/2010/interactivity' and
'http://schemas.microsoft.com/expression/2010/interactions'


with 'http://schemas.microsoft.com/xaml/behaviors'.


Replace 'xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"' and
'xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"'


with 'xmlns:i="http://schemas.microsoft.com/xaml/behaviors"'.


3. Edit C# files:
Replace usings in c# files 'Microsoft.Xaml.Interactivity' and
'Microsoft.Xaml.Interactions'


with 'Microsoft.Xaml.Behaviors'.


Remove references to 'Microsoft.Expression.Interactions' and
'System.Windows.Interactivity'.