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


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

Обновлен: Ноябрь 2007

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

В этом разделе содержатся следующие подразделы.

  • Необходимые компоненты
  • Когда следует помечать события как обрабатываемые
  • События «Preview» (нисходящей маршрутизации) и события восходящей маршрутизации. Обработка событий
  • Обработчики классов и обработчики экземпляров
  • Обработка класса перенаправленных событий с помощью базовых классов элементов управления
  • Добавление обработчиков экземпляров, которые вызываются, даже если события помечены как обработанные
  • Намеренное подавление событий ввода для композиции элементов управления
  • Связанные разделы

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

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

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

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

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

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

События «Preview» (нисходящей маршрутизации) и события восходящей маршрутизации. Обработка событий

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

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

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

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

Перенаправленные события поддерживают два различных типа прослушивателей события: прослушиватели классов и прослушиватели экземпляров. Прослушиватели классов существуют, так как типы вызывают особенный EventManagerAPI-интерфейс, RegisterClassHandler в их статическом конструкторе, или переопределяют виртуальный метод обработчика класса из элемента базового класса. Прослушиватели экземпляров являются особенными экземплярами/элементами класса, в которых один или несколько обработчиков присоединены к этому перенаправленному событию путем вызова вызова AddHandler. Существующие перенаправленные события WPF выполняют вызов AddHandler как части оболочки события среда CLR (common language runtime) add{} и remove{} реализаций события, которая определяет, каким образом включен простой механизм 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. Если родительский элемент обрабатывает событие нижнего уровня, то событие высокого уровня может быть подавлено даже в дочернем элементе, который интуитивно должен иметь возможность первым обработать это событие.

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

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

Основной сценарий, в котором используется обработка перенаправленных событий, предназначен для событий ввода и составных элементов управления. Составной элемент управления по определению состоит из нескольких практических элементов управления или базовых классов элементов управления. Часто автор элемента управления хочет объединить все возможные события ввода, которые каждый из субкомпонентов может вызывать, чтобы полный элемент управления был единственным источником событий. В некоторых случаях автору элемента управления может потребоваться полностью подавить события от компонентов или заменить событием, заданным для компонента, которое содержит дополнительные сведения или подразумевает особое поведение. Канонический пример, очевидный для любого автора компонента, это способ, которым ButtonWindows 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 (Extensible Application Markup Language) не включает это поведение.

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

См. также

Задачи

Практическое руководство. Создание пользовательских перенаправленных событий

Основные понятия

События предварительного просмотра

Общие сведения о перенаправленных событиях

Ссылки

EventManager