Я немного запутался в командах WPF

Команды в WPF кажутся довольно мощными, и я понял, как их использовать (более или менее), несмотря на высокоуровневый жаргон в документации, бессвязные эссе и блоги с разбрызгиванием кода, которые всегда, кажется, тратят нездоровое количество времени на то, чтобы сосредоточиться на том, как он работает с шаблоном MVVM. Но я запутался в общей картине.

Я пытаюсь выяснить, кому принадлежит команда, и если это ICommand или RoutedCommand, и под командой я подразумеваю любую команду.

Основные игроки

  • ICommand
    Интерфейс, который должен реализовать класс команды, чтобы его можно было использовать в свойстве Command в элементе управления, реализующем ICommandSource.
  • RoutedCommand
    Класс, реализующий ICommand и который при выполнении вызывает пару перенаправленных событий на своей цели, чтобы уведомить визуальное и логическое дерево о том, что команда была выполнена. Событиями являются PreviewCanExecute и CanExecute, со стратегией маршрутизации, установленной на Tunnelling и Bubbling соответственно, и те же два варианта для PreviewExecuted и Executed. Состояние CanExecute автоматически обновляется при возникновении события CommandManager.QuerySuggested.
  • RoutedUICommand
    Производится от RoutedCommand, добавляя одно свойство с именем Text, которое устанавливается конструктором для пользовательских команд или путем запроса владельцев встроенных команд.
  • ICommandSource
    Интерфейс, который должен быть реализован объектом, который может вызывать команду.
  • Элементы управления, реализующие ICommandSource
    ButtonBase, MenuItem, Hyperlink, InputBinding и ThumbButtonInfo.
  • IInputElement
    Интерфейс, реализуемый объектами пользовательского интерфейса WPF, и требуемый тип для указания ICommand Target.
  • CommandBinding
    Связующий объект, который связывает маршрутизируемую команду с ее поведением, сохраняя ссылку на маршрутизируемую команду и делегируя методы, которые управляют ее поведением. Командная система будет искать привязку, регистрирующую конкретную маршрутизируемую команду, когда команда выполняется.
  • UIElement
    Класс, который также реализует IInputElement, который поддерживает размещение перенаправленных событий и перенаправленных команд через зависимости от классов EventManager и CommandManager соответственно.
    Он также может содержать коллекцию объектов CommandBinding, которые позволяют это чтобы обеспечить собственное поведение при перенаправлении команды.
  • CommandManager
    Класс, который определяет события Commanding Execute / CanExecute, а также его RoutedEventHandlers, который делегирует их соответствующим _17 _ / _ 18_ обработчикам событий.

В общем, вот что я понял из исходного кода ...

Это началось с щелчка

Например, для всего, что происходит от ButtonBase, отправной точкой для выполнения команды является обработчик событий OnClick.

Этот метод передает родительский элемент (в данном случае кнопку) методу ExecuteCommandSource класса CommandHelper в качестве ICommandSource ссылки.

Тогда есть два пути кода, в зависимости от типа свойства команды: один для простых команд и один для перенаправленных команд.

Простая команда

Если команда не является производным от RoutedCommand, то это простая команда. В этом случае командный объект будет содержать определяемую пользователем реализацию интерфейса ICommand, который обычно будет включать прямые ссылки на целевой объект. Через этот интерфейс CommandHelper вызовет метод commandCanExecute и, если он вернет true, вызовет метод Execute команды, передав свойство CommandParameter из источника. Для простых команд свойство CommandTarget игнорируется, если оно присутствует.

Маршрутная команда

Если свойство Command в источнике команды является производным от RoutedCommand, тогда класс CommandHelpers будет следовать аналогичному пути, но он не будет использовать явно реализованный интерфейсICommand в маршрутизируемой команде. Класс RoutedComand имеет альтернативные методы с расширенной сигнатурой, которая включает источник CommandTarget. Вместо того, чтобы вызывать определяемое пользователем поведение, он будет составлять и вызывать перенаправленные события для целевого объекта. Класс события, идентифицируемый составленными аргументами событий, как CommandManager.CanExecute или CommandManager.Executed, или их «предварительные» версии. Затем система перенаправленных событий построит маршрут, создав резервную копию визуального и логического деревьев (если нет визуального родителя, он будет следовать за логическим деревом), а затем, используя стратегию маршрутизации аргументов событий для определения порядка, вызовет обработчики, которые соответствует типу перенаправленного события.

Конструктор по умолчанию для класса UIElement регистрируется как обработчик класса для перенаправленных событий, связанных с командами, и связывает их с соответствующей статической службой в CommandManager (CommandManager.OnExecuted и CommandManager.OnCanExecute). Затем CommandManager выполнит поиск квалифицированной привязки и выполнит ее, если она будет найдена. Если не удается найти привязку, он проверит, находится ли целевой элемент в области фокуса, и, если есть, попытается выполнить команду на родительском элементе фокуса.

В этом случае в ICommand интерфейс не подключено другое поведение, которое могло бы взаимодействовать с системой перенаправленных событий. Интерфейс ICommand реализован явно, но, например, в случае с кнопкой этот интерфейс игнорируется при выполнении команды. Если команда выполняется с использованием явного интерфейса ICommand, в качестве цели будет установлен фокусированный элемент.

Привязки команд

Учитывая, что маршрутизируемая команда не обеспечивает фактического требуемого поведения, требуется некоторый механизм для привязки этого к команде, и для этого предназначен класс CommandBinding.

Это тип элемента для CommandBindingCollection, который можно найти в типах UIElement, ContentElement и UIElement3D и обеспечивает недостающую связь между маршрутизируемыми командами и реализацией их поведения.

Маршрутизируемая часть

Когда направленная команда выполняется на целевом элементе, командная система будет искать в логическом дереве - используя перенаправленные события - до тех пор, пока не найдет первую привязку команды, которая зарегистрировала конкретную команду, и «выполнит» привязку. Внутренний метод OnExecuted экземпляра CommandBinding выполнит соответствующие обработчики событий, а затем установит для свойства Handled аргументов события значение true. Если Handled уже имеет значение true, привязка не будет выполнена.

Привязки команд класса

Вместо того, чтобы полагаться на логическую древовидную структуру для поиска привязки, ее также можно зарегистрировать для типа с помощью метода CommandManager RegisterClassCommandBinding, и эти привязки будут выполняться перед привязками экземпляров. Единственная проблема с этим заключается в том, что нет метода отмены регистрации и CommandBindingCollection, который их содержит, недоступен, поэтому это может привести к утечкам памяти, например, при привязке к модели представления.