it-swarm.com.de

Festlegen einer Eigenschaft mit einem EventTrigger

Ich möchte in der Lage sein, eine Eigenschaft mit einem EventTrigger festzulegen, da gibt es eine Reihe von Problemen.

1) EventTrigger unterstützen nur Aktionen, daher muss ich ein StoryBoard verwenden, um meine Eigenschaften festzulegen.

2) Wenn ich ein Storyboard verwende, habe ich zwei Möglichkeiten:

  • Stop: Sobald die Animation gestoppt wurde, kehrt der Wert vor dem Start der Animation zurück
  • HoldEnd: Dadurch wird die Eigenschaft gesperrt, sodass weder Code noch Benutzerinteraktion die Eigenschaft ändern können, die die Animation enthält.

Im folgenden Beispiel möchte ich die IsChecked-Eigenschaft auf False setzen, wenn auf die Schaltfläche geklickt wird, und möchte, dass der Benutzer die IsChecked-Eigenschaft ändern kann und/oder die Eigenschaft im Code ändern kann.

Beispiel:

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

Mir ist klar, dass ich das Ereignis "Abgeschlossen" verwenden kann, nachdem das Storyboard abgeschlossen wurde, um den Wert auf "Falsch" zu setzen. In diesem Fall möchte ich jedoch die Logik in der XAML enthalten, da diese Logik für ein benutzerdefiniertes Steuerelement verwendet wird und nur für die Benutzeroberfläche spezifisch ist.

67
Chris Nicol

So sehr ich XAML auch liebe, für diese Art von Aufgaben wechsle ich zu Code Behind. Angehängte Verhaltensweisen sind hierfür ein gutes Muster. Denken Sie daran, Expression Blend 3 bietet eine Standardmethode um Verhalten zu programmieren und zu verwenden. Es gibt ein paar existierende auf der Expression Community Site.

21

Erstellen Sie einfach Ihre eigene Aktion.

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 diesem Fall bin ich in meinem Ansichtsmodell an eine Eigenschaft namens DialogResult gebunden.

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

Ich habe die Neutrino-Lösung so geändert, dass die XAML-Datei bei der Angabe des Werts weniger ausführlich aussieht:

Es tut uns leid, dass Sie keine Bilder von der gerenderten XAML haben. Stellen Sie sich einfach einen [=] Hamburger-Button vor, auf den Sie klicken, und er verwandelt sich in einen [<-] Zurück-Button. Außerdem wird die Sichtbarkeit eines Gitters umgeschaltet.

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>

Sie können Werte auch folgendermaßen wie in der Neutrino-Lösung angeben:

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

Und hier ist der 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
    }
}
7
FocusedWolf

Das Stoppen des Storyboards kann im Code dahinter oder in der XAML erfolgen, je nachdem, woher der Bedarf stammt.

Wenn der EventTrigger außerhalb der Schaltfläche verschoben wird, können wir ihn mit einem anderen EventTrigger als Ziel festlegen, der das Storyboard anweist, anzuhalten. Wenn das Storyboard auf diese Weise angehalten wird, wird der vorherige Wert nicht wiederhergestellt.

Hier habe ich den Button.Click EventTrigger auf ein umliegendes StackPanel verschoben und der CheckBox einen neuen EventTrigger hinzugefügt. Klicken Sie, um das Storyboard des Buttons zu stoppen, wenn Sie auf die CheckBox klicken. Auf diese Weise können wir die CheckBox aktivieren und deaktivieren, wenn sie angeklickt wird, und das gewünschte Deaktivierungsverhalten für die Schaltfläche festlegen.

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

Um das Storyboard im Code dahinter zu stoppen, müssen wir etwas anderes tun. Die dritte Schaltfläche enthält die Methode, mit der das Storyboard angehalten und die IsChecked-Eigenschaft durch Code auf true zurückgesetzt wird.

Wir können myStoryboard.Stop () nicht aufrufen, da wir das Storyboard nicht über den Code gestartet haben, der den isControllable-Parameter festlegt. Stattdessen können wir das Storyboard entfernen. Dazu benötigen wir das FrameworkElement, auf dem sich das Storyboard befindet, in diesem Fall unser StackPanel. Sobald das Storyboard entfernt wurde, können wir die IsChecked-Eigenschaft erneut auf die Benutzeroberfläche setzen.

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