WPF TextBox перехватывает RoutedUICommands

Я пытаюсь получить быстрые клавиши Undo/Redo, работающие в моем приложении WPF (у меня есть собственные пользовательские функции, реализованные с помощью Command Pattern). Однако кажется, что элемент управления TextBox перехватывает мою команду "Отменить" RoutedUICommand.

Каков самый простой способ отключить это, чтобы я мог поймать Ctrl + Z в корне моего дерева пользовательского интерфейса? Я хотел бы избегать ввода тонны кода /XAML в каждый TextBox в моем приложении, если это возможно.

Ниже кратко демонстрируется проблема:

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

Ответ 1

Нет простого способа подавить все привязки, не устанавливайте IsUndoEnabled в false, поскольку он будет только ловушкой и флешем привязку клавиш Ctrl + Z. Вам нужно перенаправить CanUndo, CanRedo, Undo и Redo. Вот как я это делаю с помощью 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;
}

Ответ 2

Если вы хотите реализовать собственное Undo/Redo и предотвратить перехват TextBox, присоединитесь к событиям предварительного просмотра команды через CommandManager.AddPreviewCanExecuteHandler и CommandManager.AddPreviewExecutedHandler и установите флаг event.Handled в значение 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;
    }
}

Ответ 3

TextBoxBase (и, следовательно, TextBox и RichTextBox) имеют свойство IsUndoEnabled, по умолчанию true. Если вы установите его на false (и вы можете сделать это для всех текстовых полей в вашем окне с помощью стиля и сеттера, как обычно), то они не будут перехватывать Ctrl + Z.

Ответ 4

Элемент TextBox предоставляет свойство IsUndoEnabled, которое вы может быть установлен на false, чтобы предотвратить функцию отмены. (Если IsUndoEnabled есть true, нажатие клавиши Ctrl + Z вызывает его.)

Также для элемента управления, который не предоставляет специального свойства, вы можете добавить новое связывание для команды, которую вы хотите отключить. Эта привязка может затем предоставить новый обработчик события CanExecute, который всегда отвечает на false. Имеет пример, который использует этот метод для удаления поддержки функции Cut в текстовом поле:

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

и heres обработчик события, который устанавливает состояние CanExecute:

private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e)
{
  e.CanExecute = false;
  e.Handled = true;
}

Ответ 5

По умолчанию целью RoutedUICommand является элемент с фокусом клавиатуры. Однако вы можете установить CommandTarget на элемент управления, испускающий команду, чтобы изменить корневой элемент, который получает команду.

<MenuItem Command="ApplicationCommands.Open"
          CommandTarget="{Binding ElementName=UIRoot}"
          Header="_Open" />

Ответ 6

Событие Executed запускается, поэтому событие Window Executed всегда будет ударяться после события TextBox Executed. Попробуйте изменить его на PreviewExecuted, и это должно иметь огромное значение. Кроме того, вам может понадобиться подключить CanExecute для вашего окна. то есть:

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

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

Конечно, вам, вероятно, понадобится какая-то логика, чтобы определить, когда CanExecute должен быть установлен в true. Вам, вероятно, не нужно использовать пользовательскую команду (просто используйте встроенное Undo).