wpf xaml mvvm focus inputbinding

wpf - KeyBinding en UserControl no funciona cuando TextBox tiene el foco



xaml mvvm (4)

La siguiente situación. Tengo un UserControl con cinco combinaciones de teclas. Cuando el TextBox tiene el foco, las combinaciones de teclas de UserControl dejan de disparar.

¿Hay alguna manera de solucionar este "problema"?

<UserControl.InputBindings> <KeyBinding Key="PageDown" Modifiers="Control" Command="{Binding NextCommand}"></KeyBinding> <KeyBinding Key="PageUp" Modifiers="Control" Command="{Binding PreviousCommand}"></KeyBinding> <KeyBinding Key="End" Modifiers="Control" Command="{Binding LastCommand}"></KeyBinding> <KeyBinding Key="Home" Modifiers="Control" Command="{Binding FirstCommand}"></KeyBinding> <KeyBinding Key="F" Modifiers="Control" Command="{Binding SetFocusCommand}"></KeyBinding> </UserControl.InputBindings> <TextBox Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"> <TextBox.InputBindings> <KeyBinding Gesture="Enter" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl }}, Path=DataContext.FilterCommand}"></KeyBinding> </TextBox.InputBindings> </TextBox>

Parece que las teclas de función ( F1, etc.) y ALT + [tecla] funcionan. Supongo que los modificadores CTRL y SHIFT están de alguna manera ''bloqueando'' el evento desde el burbujeo hasta el UserControl.


La solución de Adi Lester funciona bien. Aquí hay una solución similar que usa Comportamiento. El código C #:

public class AcceptKeyBinding : Behavior<UIElement> { private TextBox _textBox; /// <summary> /// Subscribes to the PreviewKeyDown event of the <see cref="TextBox"/>. /// </summary> protected override void OnAttached() { base.OnAttached(); _textBox = AssociatedObject as TextBox; if (_textBox == null) { return; } _textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown; } private void TextBoxOnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs) { var uielement = (UIElement)sender; var foundBinding = uielement.InputBindings .OfType<KeyBinding>() .FirstOrDefault(kb => kb.Key == keyEventArgs.Key && kb.Modifiers == keyEventArgs.KeyboardDevice.Modifiers); if (foundBinding != null) { keyEventArgs.Handled = true; if (foundBinding.Command.CanExecute(foundBinding.CommandParameter)) { foundBinding.Command.Execute(foundBinding.CommandParameter); } } } /// <summary> /// Unsubscribes to the PreviewKeyDown event of the <see cref="TextBox"/>. /// </summary> protected override void OnDetaching() { if (_textBox == null) { return; } _textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown; base.OnDetaching(); } }

Y el XAML:

<TextBox> <TextBox.InputBindings> <KeyBinding Key="Enter" Modifiers="Shift" Command="{Binding CommandManager[ExecuteCommand]}" CommandParameter="{Binding ExecuteText}" /> </TextBox.InputBindings> <i:Interaction.Behaviors> <behaviours:AcceptKeyBinding /> </i:Interaction.Behaviors> </TextBox>


La razón por la cual algunos enlaces de entrada funcionan y otros no, es que el control TextBox detecta y maneja algunos enlaces de teclas. Por ejemplo, maneja CTRL + V para pegar, CTRL + Inicio para ir al principio del texto, etc. Otras combinaciones de teclas, como CTRL + F3, por otro lado, no son manejadas por el TextBox, por lo que burbujearán arriba.

Si solo quisiera deshabilitar el enlace de entrada del TextBox, sería simple: podría usar el comando ApplicationCommands.NotACommand , que inhabilitaría el comportamiento predeterminado. Por ejemplo, en el siguiente caso, se desactivará pegar con CTRL + V :

<TextBox> <TextBox.InputBindings> <KeyBinding Key="V" Modifiers="Control" Command="ApplicationCommands.NotACommand" /> </TextBox.InputBindings> </TextBox>

Sin embargo, hacerlo burbujear hasta el control del usuario es un poco más complicado. Mi sugerencia es crear un comportamiento adjunto que se aplicará al UserControl, registrarse en su evento PreviewKeyDown y ejecutar sus enlaces de entrada según sea necesario antes de que lleguen al TextBox. Esto dará prioridad al UserControl cuando se ejecutan los enlaces de entrada.

Escribí un comportamiento básico que logra esta funcionalidad para que comiences:

public class InputBindingsBehavior { public static readonly DependencyProperty TakesInputBindingPrecedenceProperty = DependencyProperty.RegisterAttached("TakesInputBindingPrecedence", typeof(bool), typeof(InputBindingsBehavior), new UIPropertyMetadata(false, OnTakesInputBindingPrecedenceChanged)); public static bool GetTakesInputBindingPrecedence(UIElement obj) { return (bool)obj.GetValue(TakesInputBindingPrecedenceProperty); } public static void SetTakesInputBindingPrecedence(UIElement obj, bool value) { obj.SetValue(TakesInputBindingPrecedenceProperty, value); } private static void OnTakesInputBindingPrecedenceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((UIElement)d).PreviewKeyDown += new KeyEventHandler(InputBindingsBehavior_PreviewKeyDown); } private static void InputBindingsBehavior_PreviewKeyDown(object sender, KeyEventArgs e) { var uielement = (UIElement)sender; var foundBinding = uielement.InputBindings .OfType<KeyBinding>() .FirstOrDefault(kb => kb.Key == e.Key && kb.Modifiers == e.KeyboardDevice.Modifiers); if (foundBinding != null) { e.Handled = true; if (foundBinding.Command.CanExecute(foundBinding.CommandParameter)) { foundBinding.Command.Execute(foundBinding.CommandParameter); } } } }

Uso:

<UserControl local:InputBindingsBehavior.TakesInputBindingPrecedence="True"> <UserControl.InputBindings> <KeyBinding Key="Home" Modifiers="Control" Command="{Binding MyCommand}" /> </UserControl.InputBindings> <TextBox ... /> </UserControl>

Espero que esto ayude.


<UserControl.Style> <Style TargetType="UserControl"> <Style.Triggers> <Trigger Property="IsKeyboardFocusWithin" Value="True"> <Setter Property="FocusManager.FocusedElement" Value=" {Binding ElementName=keyPressPlaceHoler}" /> </Trigger> </Style.Triggers> </Style> </UserControl.Style>

keyPressPlaceHoler es el nombre del contenedor de su elemento objetivo

recuerde configurar Focusable="True" en usercontrol


Además de Adi Lester su (muy útil) respuesta, me gustaría sugerir algunas mejoras / extensiones que me ayudaron con mi implementación.

Gesto.Matches

El foundBinding también se puede hacer llamando a Gesture.Matches. Cambie la consulta foundBinding Linq a lo siguiente:

KeyBinding foundBinding = ((UIElement)this).InputBindings .OfType<KeyBinding>() .FirstOrDefault(inputBinding => inputBinding.Gesture.Matches(sender, eventArgs));

Enganche de mouse

Además, también puedes definir MouseBindings.

<MouseBinding Command="{Binding DataContext.AddInputValueCommand, ElementName=root}" CommandParameter="{Binding}" Gesture="Shift+MiddleClick" />

También necesita suscribirse a PreviewMouseEvents, por ejemplo, PreviewMouseUp y PreviewMouseDoubleClick. La implementación es casi la misma que para KeyBindings.

private void OnTextBoxPreviewMouseUp(object sender, MouseButtonEventArgs eventArgs) { MouseBinding foundBinding = ((UIElement)this).InputBindings .OfType<MouseBinding>() .FirstOrDefault(inputBinding => inputBinding.Gesture.Matches(sender, eventArgs)); if (foundBinding != null) { eventArgs.Handled = true; if (foundBinding.Command.CanExecute(foundBinding.CommandParameter)) { foundBinding.Command.Execute(foundBinding.CommandParameter); } } }