Поделиться через


Маркировка событий маршрутизации как обработанных и классовая обработка

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

Необходимые условия

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

Когда пометить события как обработанные

Если задать для свойства Handled значение true в данных события для перенаправленного события, это называется "пометкой обработанного события". Не существует абсолютного правила, когда следует помечать перенаправленные события как обработанные, будь то в качестве автора приложения или разработчика элемента управления, который отвечает на существующие события или реализует новые. В большинстве случаев концепция "handled", как указано в данных о маршрутизируемом событии, должна использоваться как ограниченный протокол для реакций вашего приложения на различные маршрутизируемые события, предоставляемые API WPF, а также для любых пользовательских маршрутизируемых событий. Другой способ рассмотреть вопрос о "обработке" заключается в том, что обычно следует помечать перенаправленное событие как обработанное, если ваш код отреагировал на него значительным и относительно полным образом. Как правило, не должно быть более одного ощутимого ответа, требующего отдельных реализаций обработчика для каждого вызова маршрутизируемого события. Если требуется больше ответов, необходимо реализовать необходимый код с помощью логики приложения, связанной в одном обработчике, а не использовать систему маршрутизации событий для переадресации. Концепция того, что является "значительным" также субъективным, и зависит от вашего приложения или кода. В качестве общих рекомендаций некоторые примеры "значительного ответа" включают: настройка фокуса, изменение общедоступного состояния, настройка свойств, влияющих на визуальное представление, и создание других новых событий. Примеры незначительных ответов: изменение частного состояния (без визуального влияния или программного представления), ведение журнала событий или просмотр аргументов события и выбор не реагировать на него.

Поведение системы перенаправленных событий усиливает эту модель "значительного ответа" для использования обработанного состояния перенаправленного события, так как обработчики, добавленные в XAML или общую сигнатуру AddHandler, не вызываются в ответ на перенаправленное событие, где данные события уже помечены обработанными. Необходимо приложить дополнительные действия для добавления обработчика с версией параметра handledEventsToo (AddHandler(RoutedEvent, Delegate, Boolean)), чтобы обработать маршрутизированные события, которые были помечены как обработанные предыдущими участниками в маршруте событий.

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

Предварительный просмотр событий (Туннелирование) vs. Всплывающие события и обработка событий

Предварительные события маршрутизации — это события, которые следуют туннельному маршруту через дерево элементов. "Термин 'Предварительный' в соглашении об именовании указывает на общий принцип, что предварительные (туннелируемые) маршрутизируемые события вызываются до эквивалентных распространённых маршрутизируемых событий." Кроме того, входные маршрутивные события, имеющие туннелирование и пару пузырьков, имеют отдельную логику обработки. Если событие туннелирования/предварительное маршрутизируемое событие помечено как обработанное прослушивателем событий, то всплывающее маршрутизируемое событие также будет помечено как обработанное, даже прежде чем любой из его прослушивателей получит его. Туннелирование и всплывающие события являются технически отдельными событиями, но они намеренно совместно используют один и тот же экземпляр данных событий, чтобы обеспечить такое поведение.

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

Дополнительные сведения о том, как реализовать пары маршрутизируемых событий "туннель/пузырь" в пользовательском классе, см. в статье Создание настраиваемого маршрутизируемого события.

Обработчики классов и обработчики экземпляров

Маршрутизированные события различают два типа прослушивателей: классовые и экземплярные. Прослушиватели классов существуют, так как типы вызвали определённый API EventManager,RegisterClassHandlerв статическом конструкторе или переопределили виртуальный метод обработчика класса из базового класса элемента. Прослушиватели экземпляров — это определенные экземпляры или элементы класса, к которым прикреплен один или несколько обработчиков для этого маршрутизированного события вызовом AddHandler. Существующие маршрутизируемые события WPF вызывают AddHandler как часть оболочки событий в среде CLR, добавляют{} и удаляют{} реализации событий, что также обеспечивает простой механизм подключения обработчиков событий XAML через синтаксис атрибута. Поэтому даже простое использование XAML в конечном итоге соответствует вызову AddHandler.

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

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

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

  • Производные классы могут дополнять управление событиями, унаследованное от базового класса, добавляя обработчик, который не отмечает маршрутизируемое событие как обработанное, поскольку обработчик базового класса будет вызван через некоторое время после обработчика производного класса.

  • Производные классы могут заменить обработку классов из базового класса, добавив обработчик класса, который помечает перенаправленное событие как обработанное. Этот подход должен быть осторожным, так как он потенциально изменит предполагаемый базовый элемент управления в таких областях, как внешний вид, логика состояния, обработка входных данных и обработка команд.

Обработка маршрутизируемых событий классами-основами управления

На каждом заданном узле элемента в маршруте событий прослушиватели классов могут реагировать на маршрутизированное событие, прежде чем любой экземплярный прослушиватель на элементе сможет. По этой причине обработчики классов иногда используются для подавления маршрутизируемых событий, которые не требуется распространять дальше в определенном классе элемента управления, или для предоставления специальной обработки этого маршрутизируемого события, что является особенностью класса. Например, класс может вызвать собственное событие для конкретного класса, содержащее более конкретные сведения о том, что означает определенное условие ввода пользователя в контексте этого конкретного класса. Затем реализация класса может пометить более общее маршрутизированное событие как обработанное. Как правило, обработчики классов добавляются таким образом, чтобы они не вызывались для маршрутизируемых событий, где общие данные событий уже помечены как обработанные. Однако для нетипичных случаев существует сигнатура RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), которая регистрирует обработчики классов для вызова, даже если маршрутизируемые события помечены как обработанные.

Виртуальные обработчики классов

Некоторые элементы, особенно базовые элементы, такие как UIElement, предоставляют пустые виртуальные методы On*Event и OnPreview*Event, соответствующие их списку общедоступных перенаправленных событий. Эти виртуальные методы можно переопределить для реализации обработчика класса для этого маршрутизированного события. Классы базовых элементов регистрируют эти виртуальные методы в качестве обработчика классов для каждого такого перенаправленного события, используя RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), как описано ранее. Виртуальные методы On*Event упрощают реализацию обработки классов для соответствующих маршрутизируемых событий, не требуя специальной инициализации в статических конструкторах для каждого типа. Например, можно добавить обработку классов для события DragEnter в любом производном классе UIElement путем переопределения виртуального метода OnDragEnter. В переопределении вы можете обрабатывать перенаправленное событие, вызывать другие события, инициировать логику, специфичную для классов, которая может изменять свойства элементов на экземплярах, или выполнить любое сочетание этих действий. Обычно следует вызывать базовую реализацию в таких переопределениях, даже если вы помечаете событие как обработанное. Настоятельно рекомендуется вызывать базовую реализацию, так как виртуальный метод находится в базовом классе. Стандартный защищенный виртуальный шаблон вызова базовых реализаций из каждой виртуальной сущности заменяет и параллелизирует аналогичный механизм, который является собственным для обработки перенаправленного класса событий, в котором обработчики классов для всех классов иерархии классов вызываются для любого конкретного экземпляра, начиная с обработчика наиболее производных классов и продолжая обработчик базового класса. Не следует пропускать вызов базовой реализации, если класс имеет преднамеренное требование изменить логику обработки базового класса. Вызывается ли базовая реализация до или после переопределения кода, зависит от характера реализации.

Обработка входного класса событий

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

После завершения обработки классов на узле учитываются прослушиватели экземпляров.

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

Метод AddHandler предоставляет определенную перегрузку, которая позволяет добавлять обработчики, которые будут вызываться системой событий всякий раз, когда событие достигает элемента обработки в маршруте, даже если некоторые другие обработчики уже отрегулировали данные события, чтобы пометить это событие как обработанное. Обычно это не делается. Как правило, обработчики могут быть записаны для настройки всех областей кода приложения, которые могут влиять на событие, независимо от того, где он был обработан в дереве элементов, даже если требуется несколько конечных результатов. Кроме того, обычно существует только один элемент, который должен реагировать на это событие, и соответствующая логика приложения уже была выполнена. Но вариант перегрузки handledEventsToo доступен для исключительных случаев, когда другой элемент в дереве элементов или состав элемента управления уже пометил событие как обработанное, но другие элементы как выше, так и ниже в дереве элементов (в зависимости от маршрута) всё же стремятся вызвать свои собственные обработчики.

Когда следует пометить обработанные события как необработанные

Как правило, маршрутизируемые события, которые помечены как обработанные, не должны помечаться как необработанные (переводить состояниеHandled обратно на false) даже обработчиками, которые влияют на handledEventsToo. Однако некоторые события ввода имеют высокоуровневые и низкоуровневые представления событий, которые могут перекрываться, когда событие высокого уровня отображается на одной позиции в дереве и низкоуровневом событии на другой позиции. Например, рассмотрим случай, когда дочерний элемент прослушивает высокоуровневое ключевое событие, например TextInput в то время как родительский элемент прослушивает событие низкого уровня, например KeyDown. Если родительский элемент обрабатывает событие низкого уровня, событие более высокого уровня можно подавлять даже в дочернем элементе, который интуитивно должен иметь первую возможность обрабатывать событие.

В таких ситуациях может потребоваться добавить обработчики как в родительские элементы, так и в дочерние элементы для события низкого уровня. Реализация обработчика дочерних элементов может пометить событие низкого уровня как обработанное, но обработчик родительского элемента снова отметит его как необработанное, чтобы дополнительные элементы вверх по дереву (а также событие высокого уровня) могли иметь возможность отреагировать. Эта ситуация должна быть довольно редкой.

Намеренное подавление событий ввода для компоновки элементов управления

Основной сценарий, в котором используется обработка маршрутизируемых событий, заключается во входных событиях и составных элементах управления. Составной элемент управления, по определению, состоит из нескольких функциональных элементов управления или базовых классов. Часто автор элемента управления хочет объединить все возможные входные события, которые могут возникнуть у каждой из подкомпонентов, чтобы представлять весь элемент управления в виде единого источника события. В некоторых случаях автор элемента управления может полностью отключить события от компонентов или заменить определенное компонентом событие, которое содержит больше информации или подразумевает более конкретное поведение. Канонический пример, который сразу виден любому автору компонента, показывает, как Button Windows Presentation Foundation (WPF) обрабатывает любое событие мыши, которое в конечном итоге преобразуется в интуитивно понятное событие, имеющееся у всех кнопок: событие Click.

Базовый класс Button (ButtonBase) является производным от Control, который, в свою очередь, является производным от FrameworkElement и UIElement, а большая часть инфраструктуры событий, необходимой для обработки входных данных управления, доступна на уровне UIElement. В частности, UIElement обрабатывает общие Mouse события, связанные с определением попадания курсора мыши в пределах его границ, и предоставляет отдельные события для наиболее распространенных действий кнопки, таких как MouseLeftButtonDown. UIElement также предоставляет пустую виртуальную OnMouseLeftButtonDown в качестве предварительно зарегистрированного обработчика классов для MouseLeftButtonDownи ButtonBase переопределяет его. Аналогичным образом ButtonBase использует обработчики классов для MouseLeftButtonUp. В переопределениях, куда передаются данные события, реализации отмечают экземпляр RoutedEventArgs как обработанный путем установки Handled на true. Эти же данные события продолжают движение по остальной части маршрута к другим обработчикам классов, а также к обработчикам экземпляров или установщикам событий. Кроме того, перекрытие OnMouseLeftButtonUp вызовет следующее событие Click. Конечным результатом для большинства слушателей станет то, что события MouseLeftButtonDown и MouseLeftButtonUp "исчезнут" и будут заменены событием Click, которое имеет большее значение, поскольку известно, что это событие произошло от настоящей кнопки, а не из какого-либо составного элемента кнопки или вовсе из другого элемента.

Работа вокруг подавления событий элементами управления

Иногда это поведение подавления событий в отдельных элементах управления может повлиять на некоторые более общие намерения логики обработки событий для приложения. Например, если по какой-то причине у приложения был обработчик MouseLeftButtonDown, расположенный в корневом элементе приложения, вы заметите, что любой щелчк мыши на кнопке не вызовет MouseLeftButtonDown или MouseLeftButtonUp обработчиков на корневом уровне. Само событие на самом деле всплывало (опять же, маршруты событий действительно не заканчиваются, но система маршрутизируемых событий изменяет поведение вызова обработчиков после того, как они помечены как обработанные). Когда перенаправленное событие достигло кнопки, обработка класса ButtonBase помечает обработку MouseLeftButtonDown, так как она хотела заменить событие Click более значимым. Таким образом, любой стандартный обработчик MouseLeftButtonDown дальше по маршруту не будет вызван. Существует два метода, которые можно использовать, чтобы убедиться, что обработчики будут вызваны в этом случае.

Первым способом является намеренное добавление обработчика с помощью handledEventsToo сигнатуры AddHandler(RoutedEvent, Delegate, Boolean). Ограничение этого подхода заключается в том, что этот метод присоединения обработчика событий возможен только из кода, а не из разметки. Простой синтаксис указания имени обработчика событий в качестве значения атрибута события с помощью языка разметки расширяемого приложения (XAML) не включает это поведение.

Второй метод работает только для входных событий, где парируются туннелирование и бублинг версий перенаправленного события. Для этих маршрутизируемых событий можно добавить обработчики в эквивалентное предварительное или туннелирующее маршрутизируемое событие. Это маршрутизированное событие будет туннелировано через маршрут, начиная с корня, поэтому обработчик класса кнопки не перехватит его, при условии, что вы подключили обработчик события предварительного просмотра на уровне одного из элементов-предков в дереве элементов приложения. Если вы используете этот подход, будьте осторожны с тем, чтобы отмечать любое событие предварительного просмотра как обработанное. В примере, когда PreviewMouseLeftButtonDown обрабатывается в корневом элементе, если вы помечаете событие как Handled в реализации обработчика, вы фактически подавляете событие Click. Обычно это не желательно.

См. также