WPF TextBox Intercepting RoutedUICommands
Asked Answered
B

6

11

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!"); }
    }
}
Boatel answered 3/9, 2009 at 19:36 Comment(0)
H
11

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;
}
Harilda answered 23/5, 2010 at 5:42 Comment(2)
Really seems the only way...I find this bat-shit insane, as if it would be that weird to want to have an undo stack that spans many input controls...Ivetteivetts
I got this to work. You have to ensure you use the applicationcommands and not custom ones. You also have to set IsUndoEnabled to false, or it will still perform the undo that the textbox implements.Livia
M
5

If you want to implement your own Undo/Redo and prevent the TextBox from intercepting, attach to the command preview events through CommandManager.AddPreviewCanExecuteHandler and CommandManager.AddPreviewExecutedHandler and set the event.Handled flag to true:

MySomething()
{
    CommandManager.AddPreviewCanExecuteHandler(
        this,
        new CanExecuteRoutedEventHandler(OnPreviewCanExecuteHandler));
    CommandManager.AddPreviewExecutedHandler(
        this,
        new ExecutedRoutedEventHandler(OnPreviewExecutedEvent));
}
void OnPreviewCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e)
{
    if (e.Command == ApplicationCommands.Undo)
    {
        e.CanExecute = true;
        e.Handled = true;
    }
    else if (e.Command == ApplicationCommands.Redo)
    {
        e.CanExecute = true;
        e.Handled = true;
    }
}
void OnPreviewExecutedEvent(object sender, ExecutedRoutedEventArgs e)
{
    if (e.Command == ApplicationCommands.Undo)
    {
        // DO YOUR UNDO HERE
        e.Handled = true;
    }
    else if (e.Command == ApplicationCommands.Redo)
    {
        // DO YOUR REDO HERE
        e.Handled = true;
    }
}
Mutt answered 28/6, 2010 at 22:6 Comment(1)
This solution seems preferable when there are multiple controls (of varying types) at play.Dael
L
1

By default the target of the RoutedUICommand is the element with keyboard focus. However, you can set CommandTarget on the control emitting the command in order to change the root element that receives the command.

<MenuItem Command="ApplicationCommands.Open"
          CommandTarget="{Binding ElementName=UIRoot}"
          Header="_Open" />
Lira answered 3/9, 2009 at 19:53 Comment(0)
C
1

The Executed event bubbles up, so the Window Executed event will always be hit after the TextBox Executed event. Try changing it to PreviewExecuted and it should make a huge difference. Also, you might need to hook up a CanExecute for your window as well. ie:

<CommandBinding Command="Undo" PreviewExecuted="MyUndo_Executed" CanExecute="SomeOtherFunction"/>

private void SomeOtherFunction(object sender, ExecutedRoutedEventArgs e) { e.CanExecute=true; }

Of course you'll probably want some logic in there to determine when CanExecute should be set to true. You probably don't need to use a custom command either (just use the built-in Undo).

Collette answered 3/9, 2009 at 20:13 Comment(0)
D
0

TextBoxBase (and thus TextBox and RichTextBox) have IsUndoEnabled property, defaulting to true. If you set it to false (and you can event do it for all textboxes on your window via a style and a setter, as usual), then they will not intercept Ctrl+Z.

Disengagement answered 3/9, 2009 at 19:43 Comment(1)
Strange. Adding IsUndoEnabled="False" to the TextBox still does not give the expected result (showing the MessageBox). Is there something else I'm missing?Boatel
B
0

The TextBox control provides an IsUndoEnabled property that you can set to false to prevent the Undo feature. (If IsUndoEnabled is true, the Ctrl + Z keystroke triggers it.)

Also for a control that does not provide a special property you can add a new binding for the command you want to disable. This binding can then supply a new CanExecute event handler that always responds false. Here’s an example that uses this technique to remove support for the Cut feature of the text box:

CommandBinding commandBinding = new CommandBinding(
ApplicationCommands.Cut, null, SuppressCommand);
txt.CommandBindings.Add(commandBinding);

and here’s the event handler that sets the CanExecute state:

private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e)
{
  e.CanExecute = false;
  e.Handled = true;
}
Barbados answered 3/9, 2009 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.