Pregunta

Quiero invocar un comando cuando Enter se presiona en un TextBox. Considere el siguiente XAML:

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

Y ese myCommand es el siguiente:

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

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

Con lo anterior, mi comando se invoca para cada tecla Presione. ¿Cómo puedo restringir el comando para invocar solo cuando se presiona la tecla ENTER?

Entiendo que con Expression Blend puedo usar condiciones, pero parecen estar restringidas a elementos y no pueden considerar los argumentos de eventos.

También he encontrado Aguacera que ofrece su propio InvokeCommandAction implementación que se basa en la parte superior del Systems.Windows.Interactivity implementación y puede hacer lo que necesito. Otra consideración es escribir mi propio disparador, pero espero que haya una manera de hacerlo sin usar kits de herramientas externas.

¿Fue útil?

Solución 2

Me gusta el enfoque de Scottrudy (al que le he dado un +1) con el enfoque de desencadenantes personalizados, ya que se mantiene fiel a mi enfoque inicial. Incluyo una versión modificada a continuación para usar propiedades de dependencia en lugar de información de reflexión para que sea posible vincularse directamente al iCommand. También incluyo un enfoque que utiliza propiedades adjuntas para evitar usar System.Windows.Interactivity Si es deseado. La advertencia a este último enfoque es que pierde la característica de múltiples invocaciones de un evento, pero puede aplicarla de manera más general.


Enfoque de desencadenantes personalizados

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

Enfoque de propiedades adjuntas

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>

Otros consejos

Hay KeyTrigger en la mezcla de expresión.

<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 y Microsoft.Expression.Interactions Los ensamblajes están disponibles para WPF en el oficial Paquete nuget.

Ayer me encontré con este mismo problema y lo resolví usando desencadenantes personalizados. Al principio puede parecer un poco mucho, pero descubrí que este patrón general se puede usar para hacer muchas de las cosas que solía lograr usando los manejadores de eventos directamente en una vista (como eventos de doble clic). El primer paso es crear una acción de activación que pueda aceptar un parámetro ya que lo necesitaremos más tarde.

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

El siguiente paso es crear el disparador. Podrías hacer algunas cosas interesantes con clases base para que sea más genérico para capturar diferentes tipos de prensas de teclas, pero lo mantendremos 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);
            }
        }
    }
}

Tenga en cuenta que a pesar de que puede tener un enlace de datos en su lugar para el valor de su cuadro de texto, la propiedad cambió el evento no disparará porque su cuadro de texto no ha perdido el enfoque. Por esta razón, estoy pasando el valor de la propiedad TextBox.Text al comando. El último paso es usar esta función en su XAML. Debe asegurarse de incluir el espacio de nombres de interactividad, así como el espacio de nombres que contiene su código desde arriba.

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

Utilicé el código de ScotTrudy en mi aplicación, sin embargo, mi texto en el cuadro de texto está vinculado a alguna propiedad en la clase ViewModel y esta propiedad no se actualiza por el comando de la hora se invoca después de la clave de ingresar a Pressiong porque mi cuadro de texto aún no ha perdido el enfoque. Entonces, para resolver esto, agregué los siguientes fragmentos de código justo arriba de InvokeActions (o) en el método AssociatedObject_Keyup y la propiedad de texto actualizada se está actualizando en la clase ViewModel.

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

Además de mi mente ... puedes pasar los argumentos de eventos al mando y que en ViewModel verifique si E.Keypress = Keys.enter ... esto no es realmente código :) No tengo mi VS en esta computadora ... esto es más bien un ocurrencia :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top