Я немного запутался в командах 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
, который их содержит, недоступен, поэтому это может привести к утечкам памяти, например, при привязке к модели представления.