Маркировка перенаправленных событий как обработанных и обработка классов

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

Предварительные условия

В этом разделе описываются основные понятия, представленные в разделе Общие сведения о перенаправленных событиях.

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

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

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

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

События предварительной версии (туннелирование) и события восходящей маршрутизации и обработка событий

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

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

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

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

Перенаправленные события поддерживают два разных типа прослушивателей события: прослушиватели классов и прослушиватели экземпляров. Прослушиватели классов существуют, так как типы вызвали определенный EventManager API, 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 . Если родительский элемент обрабатывает событие нижнего уровня, событие верхнего уровня может подавляться даже в дочернем элементе, который интуитивно должен иметь возможность первым обработать это событие.

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

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

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

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

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

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

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

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

См. также раздел