Search code examples
wpfkeyboard-shortcutsundorouted-commands

WPF TextBox Intercepting RoutedUICommands


I am trying to get Undo/Redo keyboard shortcuts working in my WPF application (I have my own custom functionality implemented using the Command Pattern). It seems, however, that the TextBox control is intercepting my "Undo" RoutedUICommand.

What is the simplest way to disable this so that I can catch Ctrl+Z at the root of my UI tree? I would like to avoid putting a ton of code/XAML into each TextBox in my application if possible.

The following briefly demonstrates the problem:

<Window x:Class="InputBindingSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:loc="clr-namespace:InputBindingSample"
    Title="Window1" Height="300" Width="300">
    <Window.CommandBindings>
        <CommandBinding Command="loc:Window1.MyUndo" Executed="MyUndo_Executed" />
    </Window.CommandBindings>
    <DockPanel LastChildFill="True">
        <StackPanel>
            <Button Content="Ctrl+Z Works If Focus Is Here" />
            <TextBox Text="Ctrl+Z Doesn't Work If Focus Is Here" />
        </StackPanel>
    </DockPanel>
</Window>

using System.Windows;
using System.Windows.Input;

namespace InputBindingSample
{
    public partial class Window1
    {
        public static readonly RoutedUICommand MyUndo = new RoutedUICommand("MyUndo", "MyUndo", typeof(Window1),
            new InputGestureCollection(new[] { new KeyGesture(Key.Z, ModifierKeys.Control) }));

        public Window1() { InitializeComponent(); }

        private void MyUndo_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("MyUndo!"); }
    }
}

Solution

  • There is no straightforward way to supress all bindings, do not set IsUndoEnabled to false as it will only trap and flush Ctrl + Z key binding. You need to redirect CanUndo, CanRedo, Undo and Redo. Here is how I do it with my UndoServiceActions singleton.

    textBox.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo,
                                                   UndoCommand, CanUndoCommand));
    textBox.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo,
                                                   RedoCommand, CanRedoCommand));
    
    private void CanRedoCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = UndoServiceActions.obj.UndoService.CanRedo;
        e.Handled = true;
    }
    
    private void CanUndoCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = UndoServiceActions.obj.UndoService.CanUndo;
        e.Handled = true;
    }
    
    private void RedoCommand(object sender, ExecutedRoutedEventArgs e)
    {
        UndoServiceActions.obj.UndoService.Redo();
        e.Handled = true;
    }
    
    private void UndoCommand(object sender, ExecutedRoutedEventArgs e)
    {
        UndoServiceActions.obj.UndoService.Undo();
        e.Handled = true;
    }