Поддержка команд

38

Многие элементы управления обладают встроенной поддержкой команд. Добавить такую поддержку к разрабатываемому элементу управления можно двумя способами:

В следующем примере используется первый подход для добавления поддержки команды ApplicationCommands.Undo.

Чтобы поддержать средство Undo в указателе цвета, необходимо отслеживать предыдущий цвет в поле-члене класса:

private Color? previousColor;

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

colorPicker.previousColor = (Color)е.OldValue;

Теперь имеется инфраструктура, необходимая для поддержки команды Undo. Все, что остается — это создать привязку команды, которая подключит элемент управления к команде и обработает события CanExecute и Executed.

Лучшее место для создания привязки команды — момент первоначального создания элемента управления. Например, в следующем коде для добавления привязки команды ApplicationCommands.Undo используется конструктор указателя цвета:

public ColorPickerUserControl()
        {
            InitializeComponent();
            SetUpCommands();
        }

        private void SetUpCommands()
        {
            CommandBinding binding = new CommandBinding(ApplicationCommands.Undo, 
               UndoCommand_Executed, UndoCommand_CanExecute);

            this.CommandBindings.Add(binding);
        }

Чтобы сделать команду функциональной, понадобится обработать событие CanExecute и разрешить команду, если имеется предшествующее значение:

private void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
   e.CanExecute = previousColor.HasValue;
}

И, наконец, когда команда выполняется, можно заменить цвет предыдущим значением:

private void UndoCommand_Executed (object sender, ExecutedRoutedEventArgs e)
{
   this.Color = (Color)previousColor;
}

Команда Undo инициируется двумя другими способами. Можно применить стандартную клавиатурную привязку <Ctrl+Z>, когда соответствующий элемент в пользовательском элементе управления имеет фокус, или же добавить кнопку в клиентскую панель, которая будет инициировать команду.

В любом случае текущее значение цвета отбрасывается и применяется предыдущее. В данном примере сохраняется только один уровень информации отмены. Однако легко создать стек отмены, который будет хранить последовательность старых значений. Для этого понадобится лишь сохранять значения Color в коллекции соответствующего типа. Коллекция Stack<T> из пространства имен System.Collections.Generic — подходящий выбор, поскольку реализует алгоритм "последний вошел — первый вышел", который как раз подходит для получения наиболее свежего предыдущего объекта Color при выполнении операции отмены.

Надежные команды

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

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

Подобное невозможно в случае применения метода RegisterClassCommandBinding(). И именно такой подход используют элементы управления WPF. Например, если посмотреть на коллекцию CommandBindings из TextBox, то в ней не будет никаких привязок для жестко закодированных команд Undo, Redo, Cut, Сору и Paste, поскольку они зарегистрированы как привязки классов. Техника довольно проста. Вместо создания привязок команд в конструкторе экземпляра, их понадобится создавать в статическом конструкторе с помощью примерно такого кода:

CommandManager.RegisterClassCommandBinding(typeof(ColorPickerUserControl),
      new CommandBinding(ApplicationCommands.Undo,
      UndoCommand_Executed, UndoCommand_CanExecute));

Хотя этот код изменен незначительно, все же он демонстрирует существенный сдвиг. Поскольку ссылки на методы UndoCommand_Executed() и UndoCommand_CanExecute() присутствуют в конструкторе, оба они должны быть статическими. Чтобы извлечь данные экземпляра (такие как информацию о текущем и предшествующем цветах), нужно привести отправителя события к объекту ColorPicker и использовать его.

Ниже приведен пересмотренный код обработки команд:

private static void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            ColorPickerUserControl colorPicker = (ColorPickerUserControl)sender;
            e.CanExecute = colorPicker.previousColor.HasValue;
        }
        private static void UndoCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            ColorPickerUserControl colorPicker = (ColorPickerUserControl)sender;            
            colorPicker.Color = (Color)colorPicker.previousColor;
        }

Кстати, этот подход не ограничивается командами. Если хотите привязать логику обработки событий к элементу управления, можете воспользоваться обработчиком событий класса с методом EventManager.RegisterClassHandler(). Обработчики событий класса всегда вызываются перед обработчиками событий экземпляра, позволяя легко подавлять события.

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