Question

Je veux invoquer une commande lorsque vous appuyez sur ENTRÉE dans un TextBox. Considérons le XAML suivant:

<UserControl
     ...
     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
     ...>    
     ...    
     <TextBox>
          <i:Interaction.Triggers>
               <i:EventTrigger EventName="KeyUp">
                    <i:InvokeCommandAction Command="{Binding MyCommand}"
                                           CommandParameter="{Binding Text}" />
               </i:EventTrigger>
          </i:Interaction.Triggers>
     </TextBox>    
     ...    
</UserControl>

et que MyCommand est la suivante:

public ICommand MyCommand {
     get { return new DelegateCommand<string>(MyCommandExecute); }
}

private void MyCommandExecute(string s) { ... }

Avec ce qui précède, ma commande est invoquée pour chaque pression de touche. Comment puis-je limiter la commande seulement Invoke lors de la touche ENTER est pressée?

Je comprends que, avec Expression Blend je peux utiliser, mais les conditions semblent se limiter aux éléments et ne peut pas examiner les arguments de l'événement.

Je suis venu aussi à travers SLEX qui offre sa propre implémentation de InvokeCommandAction qui est construit sur la mise en œuvre de Systems.Windows.Interactivity et peut faire ce que je avoir besoin. Une autre considération est d'écrire mon propre déclencheur, mais j'espère qu'il ya un moyen de le faire sans utiliser des boîtes à outils externes.

Était-ce utile?

La solution 2

Je aime l'approche de scottrudy (auquel j'ai donné un +1) avec les éléments déclencheurs personnalisés approche car elle reste fidèle à mon approche initiale. Je suis notamment une version modifiée de celui-ci ci-dessous pour utiliser les propriétés de dépendance au lieu de l'info de réflexion afin qu'il soit possible de lier directement au ICommand. Je suis aussi, y compris une approche en utilisant les propriétés attachées à éviter d'utiliser System.Windows.Interactivity si on le souhaite. La mise en garde à cette dernière approche est que vous perdez la fonctionnalité de plusieurs invocations d'un événement, mais vous pouvez l'appliquer de façon plus générale.


Approche personnalisée Déclencheurs

ExecuteCommandAction.cs

public class ExecuteCommandAction : TriggerAction<DependencyObject> {
    #region Properties
    public ICommand Command {
        get { return (ICommand)base.GetValue(CommandProperty); }
        set { base.SetValue(CommandProperty, value); }
    }

    public static ICommand GetCommand(DependencyObject obj) {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value) {
        obj.SetValue(CommandProperty, value);
    }

    // We use a DependencyProperty so we can bind commands directly rather
    // than have to use reflection info to find them
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
    #endregion Properties

    protected override void Invoke(object parameter) {
        ICommand command = Command ?? GetCommand(AssociatedObject);
        if (command != null && command.CanExecute(parameter)) {
            command.Execute(parameter);
        }
    }
}

TextBoxEnterKeyTrigger.cs

public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
    protected override void OnAttached() {
        base.OnAttached();
        TextBox textBox = this.AssociatedObject as TextBox;

        if (textBox != null) {
            this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
        }
        else {
            throw new InvalidOperationException("This behavior only works with TextBoxes");
        }
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
    }

    private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
        if (e.Key == Key.Enter) {
            TextBox textBox = AssociatedObject as TextBox;

            //This checks for an mvvm style binding and updates the source before invoking the actions.
            BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
            if (expression != null)
                expression.UpdateSource();

            InvokeActions(textBox.Text);
        }
    }
}

MyUserControl.xaml

<UserControl
    ...
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:b="clr-namespace:MyNameSpace.Interactivity"
    ...
    <TextBox>
        <i:Interaction.Triggers>
            <b:TextBoxEnterKeyTrigger>
                <b:ExecuteCommandAction Command="{Binding MyCommand}" />
            </b:TextBoxEnterKeyTrigger>
        </i:Interaction.Triggers>
    </TextBox>
    ...
</UserControl>

Approche des propriétés jointes

EnterKeyDown.cs

public sealed class EnterKeyDown {

    #region Properties

    #region Command

    public static ICommand GetCommand(DependencyObject obj) {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value) {
        obj.SetValue(CommandProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
            new PropertyMetadata(null, OnCommandChanged));

    #endregion Command

    #region CommandArgument

    public static object GetCommandArgument(DependencyObject obj) {
        return (object)obj.GetValue(CommandArgumentProperty);
    }

    public static void SetCommandArgument(DependencyObject obj, object value) {
        obj.SetValue(CommandArgumentProperty, value);
    }

    public static readonly DependencyProperty CommandArgumentProperty =
        DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
            new PropertyMetadata(null, OnCommandArgumentChanged));

    #endregion CommandArgument

    #region HasCommandArgument


    private static bool GetHasCommandArgument(DependencyObject obj) {
        return (bool)obj.GetValue(HasCommandArgumentProperty);
    }

    private static void SetHasCommandArgument(DependencyObject obj, bool value) {
        obj.SetValue(HasCommandArgumentProperty, value);
    }

    private static readonly DependencyProperty HasCommandArgumentProperty =
        DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
            new PropertyMetadata(false));


    #endregion HasCommandArgument

    #endregion Propreties

    #region Event Handling

    private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
        SetHasCommandArgument(o, true);
    }

    private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
        FrameworkElement element = o as FrameworkElement;
        if (element != null) {
            if (e.NewValue == null) {
                element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
            }
            else if (e.OldValue == null) {
                element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
            }
        }
    }

    private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
        if (e.Key == Key.Enter) {
            DependencyObject o = sender as DependencyObject;
            ICommand command = GetCommand(sender as DependencyObject);

            FrameworkElement element = e.OriginalSource as FrameworkElement;
            if (element != null) {
                // If the command argument has been explicitly set (even to NULL)
                if (GetHasCommandArgument(o)) {
                    object commandArgument = GetCommandArgument(o);

                    // Execute the command
                    if (command.CanExecute(commandArgument)) {
                        command.Execute(commandArgument);
                    }
                }
                else if (command.CanExecute(element.DataContext)) {
                    command.Execute(element.DataContext);
                }
            }
        }
    }

    #endregion
}

MyUserControl.xaml

<UserControl
    ...
    xmlns:b="clr-namespace:MyNameSpace.Interactivity"
    ...
    <TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
             b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
    ...
</UserControl>

Autres conseils

Il y a KeyTrigger dans le mélange d'expression.

<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;
         assembly=System.Windows.Interactivity"
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input;
           assembly=Microsoft.Expression.Interactions" ...>    
     <TextBox>
         <i:Interaction.Triggers>
            <iex:KeyTrigger Key="Enter">
               <i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" />
            </iex:KeyTrigger>
         </i:Interaction.Triggers>
     </TextBox>    
</UserControl>

System.Windows.Interactivity et assemblées Microsoft.Expression.Interactions sont disponibles pour WPF dans le package officiel Nuget .

je suis tombé sur ce même problème hier et résolu à l'aide de déclencheurs personnalisés. Il peut sembler un peu beaucoup au début, mais je trouve ce schéma général est utilisable pour faire beaucoup de choses que je l'habitude d'accomplir à l'aide des gestionnaires d'événements directement dans une vue (comme les événements double clic). La première étape consiste à créer une action de déclenchement qui peut accepter un paramètre, car nous en aurons besoin plus tard.

public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
    public string Command { get; set; }

    protected override void Invoke(object o)
    {
        if (Command != null)
        {
            object ctx = AssociatedObject.DataContext;
            if (ctx != null)
            {
                var cmd = ctx.GetType().GetProperty(Command)
                    .GetValue(ctx, null) as ICommand;
                if (cmd != null && cmd.CanExecute(o))
                {
                    cmd.Execute(o);
                }
            }
        }
    }
}

L'étape suivante consiste à créer le déclencheur. Vous pouvez faire des choses intéressantes avec des classes de base pour le rendre plus générique pour capturer différents types de touches, mais nous le garder simple.

public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.KeyUp += AssociatedObject_KeyUp;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
    }

    void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            TextBox textBox = AssociatedObject as TextBox;
            object o = textBox == null ? null : textBox.Text;
            if (o != null)
            {
                InvokeActions(o);
            }
        }
    }
}

Gardez à l'esprit que même si vous pouvez avoir une liaison de données en place à votre valeur TextBox, l'événement change de propriété ne sera pas le feu parce que votre zone de texte n'a pas perdu le focus. Pour cette raison, je passe la valeur de la propriété TextBox.Text à la commande. La dernière étape consiste à utiliser cette fonctionnalité dans votre XAML. Vous devez être sûr d'inclure l'espace de noms Interactivité ainsi que l'espace de noms qui contient votre code d'en haut.

<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
    <TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
        <i:Interaction.Triggers>
            <common:TextBoxEnterKeyTrigger>
                <common:ExecuteCommandAction Command=MyCommand" />
            </common:TextBoxEnterKeyTrigger>
        </i:Interaction.Triggers>
    </TextBox>
</UserControl>

J'utilise le code de scottrudy dans ma demande cependant, mon texte textbox est lié à une propriété dans la classe viewmodel et cette propriété ne reçoit pas mis à jour par la commande du temps est invoquée après pressiong ENTER parce que ma zone de texte n'a pas encore perdu le focus. Donc, pour résoudre cela, j'ai ajouté les extraits de code ci-dessous juste au-dessus InvokeActions (o) dans la méthode AssociatedObject_KeyUp et propriété de texte mis à jour est mis à jour en classe se viewmodel.

                    BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
                bindingExpression.UpdateSource();

En plus de mon esprit .. Vous pouvez passer args événement à commander et que dans le contrôle ViewModel si e.KeyPress = Keys.Enter .. ce n'est pas vraiment le code :) Je n'ai pas mon VS sur cet ordinateur .. ce est plutôt une idée:)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top