web-dev-qa-db-fra.com

Définir une propriété avec un EventTrigger

Je veux pouvoir définir une propriété avec un EventTrigger, cela pose un certain nombre de problèmes.

1) EventTriggers ne supportant que les actions, je dois donc utiliser un storyBoard pour définir mes propriétés.

2) Une fois que j'utilise un storyboard, j'ai deux options:

  • Arrêter: une fois l'animation arrêtée, la valeur redevient antérieure à l'animation
  • HoldEnd: Ceci verrouille la propriété, de sorte que ni le code ni l'interaction de l'utilisateur ne puissent modifier la propriété que l'animation contient.

Dans l'exemple ci-dessous, je souhaite définir la propriété IsChecked sur False lorsque l'utilisateur clique sur le bouton et je souhaite que l'utilisateur puisse modifier IsChecked et/ou je souhaite modifier la propriété dans le code.

Exemple:

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

Je réalise que je peux utiliser l'événement "Completed" une fois le storyboard terminé pour définir la valeur sur False. Cependant, dans ce cas, je souhaite contenir la logique dans le code XAML, car cette logique sera utilisée sur un contrôle personnalisé et est uniquement spécifique à l'interface utilisateur.

67
Chris Nicol

Autant que j'aime XAML, je passe au code derrière pour ce type de tâches. Les comportements attachés sont un bon modèle pour cela. N'oubliez pas qu'Expression Blend 3 fournit une méthode standard pour programmer et utiliser des comportements. Il y a quelques-uns existants sur le site de la communauté d'expression.

21
Sergey Aldoukhov

Créez simplement votre propre 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);
        }
    }
}

Dans ce cas, je lie une propriété appelée DialogResult sur mon modèle de vue.

<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

J'ai modifié la solution de Neutrino pour rendre le xaml moins verbeux lors de la spécification de la valeur:

Désolé de ne pas avoir d'images du rendu du xaml, imaginez un bouton de hamburger [=] sur lequel vous cliquez et qui se transforme en bouton de retour [<-] et active/désactive la visibilité d'une grille.

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>

Vous pouvez également spécifier des valeurs de cette manière, comme dans la solution de 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>

Et voici le 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

L'arrêt du Storyboard peut être effectué dans le code situé derrière, ou dans le xaml, en fonction de l'origine des besoins.

Si EventTrigger est déplacé en dehors du bouton, nous pouvons alors le cibler avec un autre EventTrigger qui indiquera au storyboard de s’arrêter. Lorsque le storyboard est arrêté de cette manière, il ne reviendra pas à la valeur précédente.

Ici, j'ai déplacé le Button.Click EventTrigger vers un StackPanel environnant et ajouté un nouvel EventTrigger sur la CheckBox.Cliquez pour arrêter le story-board du bouton lorsque vous cliquez sur la CheckBox. Cela nous permet de cocher et décocher la case à cocher CheckBox lorsque nous cliquons dessus et nous donne également le comportement de décochage souhaité du bouton.

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

Pour arrêter le storyboard dans le code, nous devrons faire quelque chose de légèrement différent. Le troisième bouton indique la méthode permettant d'arrêter le storyboard et de définir la propriété IsChecked sur true via le code.

Nous ne pouvons pas appeler myStoryboard.Stop () car nous n’avons pas commencé le Storyboard par le biais du code définissant le paramètre isControllable. Au lieu de cela, nous pouvons supprimer le Storyboard. Pour ce faire, nous avons besoin du FrameworkElement sur lequel le storyboard existe, dans ce cas notre StackPanel. Une fois le storyboard supprimé, nous pouvons à nouveau définir la propriété IsChecked avec sa persistance dans l'interface utilisateur.

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