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

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

Необходимые компоненты

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

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

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

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

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

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

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

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

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

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

Перенаправленные события поддерживают два разных типа прослушивателей события: прослушиватели классов и прослушиватели экземпляров. Прослушиватели классов существуют, так как типы вызывают конкретный API EventManager, RegisterClassHandler, в своем статическом конструкторе, или переопределяют виртуальный метод обработчика класса из класса базовых элементов. Прослушиватели экземпляров — это экземпляры или элементы определенного класса, где присоединены один или несколько обработчиков для этого перенаправленного события путем вызова AddHandler. Существующие перенаправленные события WPF вызывают AddHandler в рамках реализаций add{} и remove{} события программы-оболочки событий среды выполнения (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. Обычно это нежелательное поведение.

См. также