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, tha
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;
}
}
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).
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.
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;
}
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;
}
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" />