programing

EventTrigger를 사용하여 속성 설정

abcjava 2023. 4. 11. 21:36
반응형

EventTrigger를 사용하여 속성 설정

Event Trigger로 속성을 설정할 수 있으면 좋겠는데, 여기에는 몇 가지 문제가 있습니다.

1) EventTrigger는 액션만 지원하므로 StoryBoard를 사용하여 속성을 설정해야 합니다.

2) 스토리보드를 사용하면 다음 두 가지 옵션이 있습니다.

  • 중지: 애니메이션이 중지되면 값이 애니메이션 시작 이전으로 되돌아갑니다.
  • 홀드 엔드:이렇게 하면 속성이 잠기므로 코드나 사용자 상호 작용이 애니메이션이 보유하고 있는 속성을 변경할 수 없습니다.

다음 예에서는 버튼을 클릭했을 때 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에만 한정되기 때문에 이 로직을 포함시키고 싶습니다.

자신만의 동작을 만들어보세요.

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>

XAML을 매우 좋아하지만, 이러한 작업에는 코드 배후에 전환합니다.부가된 행동은 이를 위한 좋은 패턴입니다.Expression Blend 3은 동작을 프로그래밍하고 사용하는 표준 방법을 제공합니다.Expression Community Site에는 몇 가지 기존 항목이 있습니다.

값을 지정할 때 xaml이 덜 장황하게 보이도록 Neutrino의 솔루션을 수정했습니다.

렌더링된 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"가 되었습니다.젬, 행동가들Wpf" NuGet 패키지.코드 리스트는 https://github.com/microsoft/XamlBehaviorsWpf 입니다.

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

이전 Blend Microsoft에서 마이그레이션하는 단계입니다.표현.interactions.dll to new opensource Interactivity 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'.

Storyboard를 정지하는 방법은 필요에 따라 뒤에 있는 코드 또는 xaml로 할 수 있습니다.

EventTrigger가 버튼 밖으로 이동하면 스토리보드에 중지하라는 메시지가 표시되는 다른 EventTrigger를 통해 EventTrigger를 대상으로 지정할 수 있습니다.이 방법으로 스토리보드를 정지해도 이전 값으로 돌아가지 않습니다.

여기 버튼을 옮겼습니다.EventTrigger를 클릭하여 주변의 StackPanel에 새 EventTrigger를 CheckBox에 추가합니다.CheckBox를 클릭하면 버튼의 스토리보드가 정지됩니다.그러면 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>

뒤에 있는 코드의 스토리보드를 멈추려면 조금 다른 작업을 해야 합니다.세 번째 버튼은 스토리보드를 정지하고 코드를 통해 Is Checked 속성을 true로 설정하는 방법을 제공합니다.

myStoryboard를 호출할 수 없습니다.stop()은 isControlable 파라미터 설정 코드를 사용하여 스토리보드를 시작하지 않았기 때문입니다.대신 스토리보드를 제거할 수 있습니다.그러기 위해서는 스토리보드가 존재하는 FrameworkElement(이 경우는 StackPanel)가 필요합니다.스토리보드가 삭제되면 IsChecked 속성을 다시 설정하여 UI로 유지할 수 있습니다.

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

언급URL : https://stackoverflow.com/questions/942548/setting-a-property-with-an-eventtrigger

반응형