Встроенные команды

55

Некоторые элементы управления вводом умеют обрабатывать события команд самостоятельно. Например, класс TextBox обрабатывает команды Cut, Сору и Paste (а также команды Undo и Redo и часть команд класса EditingCommands, которые позволяют выделять текст и перемещать курсор в разные позиции).

Когда элемент управления имеет собственную жестко закодированную логику обработки команд, для обеспечения работы команд ничего делать не требуется. Например, добавление в простой текстовый редактор, следующих кнопок панели инструментов автоматически приводит к появлению возможности вырезания, копирования и вставки текста:

<ToolВаr>
   <Button Command="Cut">Cut</Button>
   <Button Command="Copy">Copy</Button>
   <Button Command="Paste">Paste</Button>
</ToolBar>

После этого можно будет щелкать на этих кнопках (когда фокус находится на элементе управления TextBox) и копировать, вырезать или вставлять текст из буфера обмена. Интересно, что элемент управления TextBox также обрабатывает событие CanExecute. Если в текстовом поле в текущий момент ничего не выделено, команды Cut и Сору не будут доступны. Если фокус установлен на элементе управления, который не поддерживает команды Copy, Cut и Paste (если только к нему не был специально присоединен собственный обработчик события CanExecute, включающий их), то все три команды сразу же автоматически отключаются.

С этим примером связана одна интересная деталь. Команды Copy, Cut и Paste обрабатываются тем элементом управления TextBox, на котором находится фокус. Однако в действие они приводятся соответствующей кнопкой в панели инструментов, которая представляет собой совершенно отдельный элемент. В данном примере этот процесс протекает гладко, потому что каждая кнопка размещается в панели инструментов, а класс ToolBar включает в себя встроенную логику, которая обеспечивает динамическую установку свойства CommandTarget его потомков в элемент управления, в текущий момент имеющий фокус. (Формально элемент ToolBar просматривает родительский элемент, которым является окно, и в этом контексте находит элемент управления, недавно имевший фокус, в данном случае — текстовое поле. Элемент управления ToolBar имеет отдельную область действия фокуса, и в этом контексте фокус находится на кнопке.)

Если кнопки размещены в другом контейнере (отличном от ToolBar и Menu), такого преимущества не будет. То есть кнопки не будут работать до тех пор, пока для них вручную не будет установлено свойство CommandTarget. Это требует использования выражения привязки с именем целевого элемента. Например, в случае, если именем текстового поля является txtDocument, кнопки должны быть определены следующим образом:

<Button Command="Cut"
 CommandTarget="{Binding ElementName=txtDocument}">Cut</Button>
<Button Command="Copy"
 CommandTarget="{Binding ElementName=txtDocument}">Copy</Button>
<Button Command="Paste"
 CommandTarget="(Binding ElementName=txtDocument}">Paste</Button>

Другой, более простой вариант предусматривает создание новой области действия фокуса с использованием присоединенного свойства FocusManager.IsFocusScope. Это заставляет WPF при срабатывании команды искать элемент в области действия фокуса родительского элемента:

<StackPanel FocusManager.IsFocusScope="True">
   <Button Command="Cut">Cut</Button>
   <Button Command="Copy">Copy</Button>
   <Button Command="Paste">Paste</Button>
</StackPanel>

Такой подход обладает дополнительным преимуществом, поскольку позволяет применять одни и те же команды к множеству элементов управления в отличие от предыдущего примера, где свойство CommandTarget кодировалось жестким образом. Кстати, в элементах Menu и ToolBar свойство FocusManager.IsFocusScope по умолчанию установлено в true, но если нужно иметь более простое поведение при маршрутизации команд, не предусматривающее поиск находящегося в фокусе элемента в контексте родителя, FocusManager.IsFocusScope следует установить в false.

В некоторых редких случаях оказывается, что элемент управления имеет встроенную поддержку команд, которая не должна быть включена. Для отключения этой поддержки на выбор доступны три способа.

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

Если такой вариант не доступен, можно добавить новую привязку для команды, которую требуется отключить. Затем эта привязка определяет обработчик события CanExecute, который всегда возвращает false. Ниже приведен пример применения такого приема для отключения поддержки команды Cut в элементе управления TextBox:

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

А вот код необходимого обработчика событий, устанавливающего состояние CanExecute:

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

Обратите внимание на установку флага Handled, что предотвращает выполнение элементом TextBox собственной обработки, при которой CanExecute может быть установлено в true.

Этот подход не идеален. Он успешно отключает клавиатурную комбинацию <Ctrl+X> и команду Cut в контекстном меню элемента управления TextBox. Однако пункт Cut продолжает отображаться в контекстном меню в отключенном состоянии. Последним вариантом является удаление ввода, который приводит к инициированию команды, с помощью коллекции InputBindings.

Например, ниже показан код, который служит для отключения клавиатурной комбинации <Ctrl+C>, приводящей к инициированию команды Сору в TextBox:

KeyBinding keyBinding = new KeyBinding(
   ApplicationCommands.NotACommand, Key.C, ModifierKeys.Control) ;
txt.InputBindings.Add(keyBinding);

Трюк здесь связан с применением специального значения ApplicationCommands.NotACommand, представляющего собой команду, которая ничего не делает. Оно предназначено специально для отключения привязок ввода.

При таком подходе команда Сору остается включенной. Ее по-прежнему можно вызывать через специально созданные кнопки (или контекстное меню элемента TextBox, если только не удалить его, установив свойство ContextMenu в null).

Для отключения функций всегда необходимо добавлять новые привязки команд или привязки ввода. Удалять существующие привязки не допускается. Причина в том, что существующие привязки в общедоступных коллекциях CommandBindings и InputBindings не появляются. Вместо этого они определяются с помощью отдельного механизма, который называется привязками классов.

Пройди тесты
Лучший чат для C# программистов