Setting a property with an EventTrigger

前端 未结 4 2035
你的背包
你的背包 2020-11-27 02:35

I want to be able to set a property with an EventTrigger, there\'s a number of problems with this.

1) EventTriggers only support Actions, so I must use a storyBoard

相关标签:
4条回答
  • 2020-11-27 03:21

    As much as I love XAML, for this kinds of tasks I switch to code behind. Attached behaviors are a good pattern for this. Keep in mind, Expression Blend 3 provides a standard way to program and use behaviors. There are a few existing ones on the Expression Community Site.

    0 讨论(0)
  • 2020-11-27 03:24

    Stopping the Storyboard can be done in the code behind, or the xaml, depending on where the need comes from.

    If the EventTrigger is moved outside of the button, then we can go ahead and target it with another EventTrigger that will tell the storyboard to stop. When the storyboard is stopped in this manner it will not revert to the previous value.

    Here I've moved the Button.Click EventTrigger to a surrounding StackPanel and added a new EventTrigger on the the CheckBox.Click to stop the Button's storyboard when the CheckBox is clicked. This lets us check and uncheck the CheckBox when it is clicked on and gives us the desired unchecking behavior from the button as well.

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

    To stop the storyboard in the code behind, we will have to do something slightly different. The third button provides the method where we will stop the storyboard and set the IsChecked property back to true through code.

    We can't call myStoryboard.Stop() because we did not begin the Storyboard through the code setting the isControllable parameter. Instead, we can remove the Storyboard. To do this we need the FrameworkElement that the storyboard exists on, in this case our StackPanel. Once the storyboard is removed, we can once again set the IsChecked property with it persisting to the UI.

        private void OnClick(object sender, RoutedEventArgs e)
        {
            myStoryboard.Remove(myStackPanel);
            myCheckBox.IsChecked = true;
        }
    
    0 讨论(0)
  • 2020-11-27 03:29

    I modified Neutrino's solution to make the xaml look less verbose when specifying the value:

    Sorry for no pictures of the rendered xaml, just imagine a [=] hamburger button that you click and it turns into [<-] a back button and also toggles the visibility of a Grid.

    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>
    

    You can also specify values this way like in Neutrino's solution:

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

    And here's the code.

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-27 03:35

    Just create your own action.

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

    In this case I'm binding to a property called DialogResult on my viewmodel.

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

    0 讨论(0)
提交回复
热议问题