Установка свойства с помощью EventTrigger

Я хочу иметь возможность установить свойство с помощью EventTrigger, с этим есть ряд проблем.

1) EventTrigger поддерживает только действия, поэтому я должен использовать 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, поскольку эта логика будет использоваться в настраиваемом элементе управления и относится только к пользовательскому интерфейсу.


person Chris Nicol    schedule 03.06.2009    source источник


Ответы (4)


Насколько я люблю XAML, для такого рода задач я переключаюсь на программный код. Прикрепленное поведение - хороший образец для этого. Имейте в виду, что Expression Blend 3 предоставляет стандартный способ программирования и использования поведения. На сайте сообщества Expression есть несколько уже существующих.

person Sergey Aldoukhov    schedule 03.06.2009

Просто создайте свое собственное действие.

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>

person Neutrino    schedule 07.11.2013

Я изменил решение 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
    }
}
person FocusedWolf    schedule 25.06.2016
comment
TargetObject всегда имеет значение null при использовании <local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="{x:Static Visibility.Visible}" /> - person huoxudong125; 08.11.2016
comment
@Neutrino У меня вопрос о TargetedTriggerAction stackoverflow.com/questions/40478172/ - person huoxudong125; 08.11.2016

Остановка раскадровки может быть выполнена в исходном коде или в xaml, в зависимости от того, откуда возникла необходимость.

Если EventTrigger перемещается за пределы кнопки, мы можем продолжить и нацелить его с помощью другого EventTrigger, который скажет раскадровке остановиться. Когда раскадровка остановлена ​​таким образом, она не вернется к предыдущему значению.

Здесь я переместил Button.Click EventTrigger в окружающую StackPanel и добавил новый EventTrigger в CheckBox.Click, чтобы остановить раскадровку Button при нажатии CheckBox. Это позволяет нам проверять и снимать отметку с 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. Для этого нам понадобится FrameworkElement, на котором существует раскадровка, в данном случае наш StackPanel. После удаления раскадровки мы снова можем установить свойство IsChecked, сохраняя его в пользовательском интерфейсе.

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