Настройка команд

70

Использование множества источников команд

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

<Menu>
    <MenuItem Header="Файл">
         <MenuItem Command="New"></MenuItem>
    </MenuItem>
</Menu>

Обратите внимание, что данный объект MenuItem не устанавливает свойство Header для команды New. Элемент управления MenuItem достаточно интеллектуален, чтобы самостоятельно извлечь текст из команды в случае, если свойство Header не задано. (Элемент управления Button такой способностью не обладает). Может показаться, что это довольно незначительное удобство, однако оно играет очень важную роль при локализации приложения для разных языков. В таком случае гораздо удобнее изменять текст в одном месте (за счет установки в командах свойства Text), чем отслеживать его во всех окнах:

Элемент меню использующий команду

Класс MenuItem обладает еще одной полезной особенностью. Он автоматически выбирает первую клавишу быстрого вызова команды, которая содержится в коллекции Command.InputBindings (если таковая имеется).

В случае объекта ApplicationsCommands.New это означает, что в меню рядом с текстом будет появляться клавиатурная комбинация <Ctrl+N>.

Чего у MenuItem нет, так это возможности автоматического отображения подчеркнутой клавиши доступа. Среда WPF не имеет средств, позволяющих узнать, какие команды могут размещаться в меню вместе, и, следовательно, определить, какие клавиши доступа лучше всего использовать. Например, для использования <N> в качестве клавиши быстрого доступа (она будет отображаться подчеркнутой при открытии меню с помощью клавиатуры, а ее нажатие позволит инициировать команду New) текст меню должен быть установлен вручную, а перед клавишей доступа должен находиться символ подчеркивания. То же самое необходимо сделать и при желании использовать клавишу быстрого доступа для кнопки.

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

Точная настройка текста команды

Способность меню автоматически извлекать текст элемента команды может вызвать вопрос о том, а можно ли такое же делать с другими классами ICommandSource, например, с элементом управления Button? Можно, но это требует дополнительных усилий.

В частности, для многократного использования текста команды предусмотрены два способа. Первый подразумевает извлечение текста прямо из статического объекта команды. В XAML-разметке это же можно делать с помощью расширения Static. Ниже показан пример кода, который извлекает имя команды New и использует его в качестве текста для кнопки:

<Button Command="New" Padding="5" Content="{x:Static ApplicationCommands.New}"></Button>

Проблема такого подхода в том, что он предполагает просто вызов в отношении объекта команды метода ToString(), который позволяет получить имя команды, но не ее текст. (В случае многословных команд лучше использовать текст, а не имя команды, потому что текст включает пробелы.) От данной проблемы можно избавиться, но это потребует гораздо больших усилий. Другая проблема связана со способом, которым одна кнопка дважды использует одну и ту же команду, что увеличивает вероятность случайного извлечения текста не из той команды.

Поэтому более предпочтительным решением считается применение выражения привязки данных. Эта привязка данных выглядит несколько необычно, поскольку подразумевает выполнение привязки к текущему элементу, захват используемого объекта Command и извлечение его свойства Text. Весь необходимый (и довольно длинный синтаксис) показан ниже:

<Button Command="New" Padding="5" 
        Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Command.Text}"></Button>

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

Очевидно, что такой подход многословнее простого размещения текста команды непосредственно в коде разметки. Тем не менее, его стоит рассматривать как вариант, если планируется локализация приложения для разных языков. Текст для всех команд должен устанавливаться при запуске приложения (изменение текста после привязки команд не будет давать никакого эффекта из-за того, что свойство Text не является свойством зависимости, а раз так, то никакого автоматического уведомления об изменениях, требующих обновления пользовательского интерфейса, происходить не будет).

Вызов команды напрямую

При запуске команды вы не ограничены использованием только классов, реализующих ICommandSource. Команду можно также и просто вызывать напрямую из любого обработчика событий с помощью метода Execute(). В таком случае необходимо всего лишь передавать значение параметра (или null) и ссылаться на целевой элемент:

ApplicationCommands.New.Execute(null, targetElement);

targetElement — это просто элемент, в котором WPF начинает искать привязку команды. В качестве такого элемента можно применять как содержащее окно (имеющее привязку команды), так и вложенный элемент (вроде фактического элемента, инициировавшего данное событие).

Проход через метод Execute() также можно осуществлять и в ассоциируемом объекте CommandBinding. В таком случае указывать целевой элемент не требуется, потому что им автоматически становится элемент, который предоставляет используемую коллекцию CommandBindings:

this.CommandBindings[0].Command.Execute(null);

При таком подходе модель команд задействуется лишь наполовину. Подход позволяет инициировать команду, но не предоставляет возможности для реагирования на изменение ее состояния. При желании иметь такую функцию придется также обрабатывать событие RoutedCommand.CanExecuteChanged для обеспечения соответствующей реакции на изменение состояния команды с активного на неактивное и наоборот. При инициализации события CanExecuteChanged понадобится вызвать метод RoutedCommand.CanExecute() для проверки того, находятся ли команды в пригодном для использования состоянии, и если нет, то отключать или изменять содержимое в соответствующей части пользовательского интерфейса.

Поддержка команд в специальных элементах управления

В состав WPF входит набор элементов управления, которые реализуют интерфейс ICommandSource и имеют возможность вызывать команды. (В WPF также включены элементы управления, которые имеют возможность обрабатывать команды). Несмотря на такую поддержку, может встретиться элемент управления, который нужно использовать с моделью команд, даже если он и не реализует ICommandSource. В такой ситуации самым простым вариантом будет обработка одного из событий этого элемента управления и запуск соответствующей команды из кода. Однако есть и другой вариант — создать новый специальный элемент управления со встроенной логикой для выполнения команды.

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