72

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

1)EventTriggers只支持Actions,所以我必须使用storyBoard来设置我的属性。

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>

我意识到我可以在情节提要完成后使用“已完成”事件将值设置为 False。但是,在这种情况下,我希望将逻辑包含在 XAML 中,因为此逻辑将用于自定义控件并且仅特定于 UI。

4

4 回答 4

38

只需创建自己的操作。

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>

于 2013-11-07T16:45:56.350 回答
24

尽管我很喜欢 XAML,但对于这类任务,我会切换到代码后面。附加行为是一个很好的模式。请记住,Expression Blend 3提供了一种编程和使用行为的标准方法。Expression Community 网站上有一些现有的。

于 2009-06-03T02:18:29.570 回答
10

我修改了 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
    }
}

编辑:Interactivity dll 不再是 Blend 的一部分,现在是“Microsoft.Xaml.Behaviors.Wpf”NuGet 包。此处列出的代码:https ://github.com/microsoft/XamlBehaviorsWpf

请参阅:https ://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/

从旧的 Blend Microsoft.Expression.Interactions.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'.
于 2016-06-25T23:21:51.677 回答
6

停止情节提要可以在后面的代码或 xaml 中完成,具体取决于需要的来源。

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

在这里,我将 Button.Click EventTrigger 移动到周围的 StackPanel 并在 CheckBox.Click 上添加了一个新的 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 的方法。

我们不能调用 myStoryboard.Stop(),因为我们没有通过设置 isControllable 参数的代码开始 Storyboard。相反,我们可以删除 Storyboard。为此,我们需要情节提要所在的 FrameworkElement,在本例中为 StackPanel。删除故事板后,我们可以再次设置 IsChecked 属性,并将其保留在 UI 中。

    private void OnClick(object sender, RoutedEventArgs e)
    {
        myStoryboard.Remove(myStackPanel);
        myCheckBox.IsChecked = true;
    }
于 2009-06-03T02:37:00.283 回答