Маркировка перенаправленных событий как обработанных и обработка классов
Обработчики для перенаправленных событий могут помечать событие как обработанное в данных этого события. Обработка событий эффективно сокращает маршрут. Обработка класса — это концепция программирования, поддерживаемая перенаправленными событиями. Обработчик класса может обрабатывать отдельное перенаправленное событие на уровне класса с помощью обработчика, который вызывается перед любым обработчиком экземпляра в любом экземпляре класса.
Предварительные требования
В этом разделе описываются основные понятия, представленные в разделе Общие сведения о перенаправленных событиях.
Когда следует помечать события как обработанные
Если задано значение Handled свойства true
событий данные для перенаправленного события, это называется «маркировка события как обработанного». Нет абсолютного правила, кто должен помечать перенаправленные события как обработанные — разработчик приложения или разработчик элемента управления, который реагирует на существующие или реализует новые перенаправленные события. По большей части концепция «обработанный», вносимая в данные перенаправленного события, должна использоваться в качестве ограниченного протокола для ответов собственного приложения на различные перенаправленные события, предоставленные в API WPF, а также на любые пользовательские перенаправленные события. С другой стороны, главным образом следует помечать перенаправленное событие как обработанное, если код отвечал на перенаправленное событие значительным и относительно законченным образом. Как правило, не должно быть более одного значительного ответа, которому требуется реализация отдельных обработчиков для каждого отдельного перенаправленного события. Если требуются дополнительные ответы, необходимый код должен быть реализован посредством логики приложения, связанной с простым обработчиком, а не с помощью системы перенаправленных событий для переадресации. Понятие того, что является «значительным», также субъективно и зависит от приложения или кода. В качестве общих рекомендаций можно привести следующие примеры «значительного ответа»: установка фокуса, изменение общедоступного состояния, установка свойств, влияющих на визуальное представление, и создание других новых событий. Примеры незначительных ответов: изменение закрытого состояния (без визуального воздействия или программного представления), ведение журнала событий, просмотр аргументов события и выбор не отвечать на него.
Поведение системы перенаправленных событий усиливает эту модель «значительного ответа» для использования обработанного состояния перенаправленного события, поскольку обработчики, добавленные в XAML или в общую подпись AddHandler не вызываются в ответ на перенаправленное событие где события данные уже помечено как обработанное. Вы должны проходить через дополнительные усилия, добавить обработчик с handledEventsToo
версией параметра (AddHandler(RoutedEvent, Delegate, Boolean)) для обработки перенаправленных событий, которые помечены как обработанные, более ранними участниками маршрута события.
В некоторых случаях элементы управления сами помечают некоторые перенаправленные события как обработанные. Обработанное перенаправленное событие представляет решение разработчиков элемента управления WPF, что действия этого элемента управления в ответ на перенаправленное событие являются значительными или завершенными в рамках реализации элемента управления, и событие не требует дальнейшей обработки. Обычно это делается путем добавления обработчика класса для события или путем переопределения одного из виртуальных методов обработчика класса, который существует в базовом классе. При необходимости вы по-прежнему можете обойти обработку этого события; см. раздел Обход подавления событий элементами управления далее в этой статье.
События предварительного просмотра по сравнению с событиями восходящей маршрутизации и обработка событий
Перенаправленные события предварительного просмотра — это события нисходящей маршрутизации через дерево элементов. Выражение «Предварительный просмотр» в соглашении об именовании указывает на общий принцип для событий ввода, согласно которому перенаправленные события предварительного просмотра (нисходящей маршрутизации) вызываются до эквивалентных перенаправленных событий восходящей маршрутизации. Кроме того, перенаправленные события, имеющие пару нисходящей и восходящей маршрутизации, имеют другую логику обработки. Если перенаправленное событие нисходящей маршрутизации (событие предварительного просмотра) помечается прослушивателем событий как обработанное, то перенаправленное событие восходящей маршрутизации будет помечено как обработанное даже до того, как это событие будет получено каким-либо прослушивателем событий восходящей маршрутизации. Перенаправленные события нисходящей и восходящей маршрутизации технически являются отдельными событиями, но они специально совместно используют один и тот же экземпляр данных события, чтобы такое поведение стало возможным.
Связь между перенаправленными событиями нисходящей и восходящей маршрутизации обеспечивается внутренней реализацией того, как любой конкретный класс WPF вызывает свои собственные объявленные перенаправленные события, и это справедливо для пары перенаправленных событий ввода. Но если данная реализация на уровне класса отсутствует, то между перенаправленными событиями нисходящей и восходящей маршрутизации, совместно использующими эту схему именования, нет связи: без такой реализации это будут два абсолютно раздельных перенаправленных события, которые не будут вызываться в последовательности и не будут совместно использовать данные события.
Дополнительные сведения о реализации пары перенаправленных событий нисходящей и восходящей маршрутизации в пользовательском классе см. в разделе Создание пользовательских перенаправленных событий.
Обработчики классов и обработчики экземпляров
Перенаправленные события поддерживают два разных типа прослушивателей события: прослушиватели классов и прослушиватели экземпляров. Прослушиватели классов существуют, так как типы определенный EventManager API ,RegisterClassHandler, в своем статическом конструкторе, или переопределяют виртуальный метод обработчика класса из элемента базового класса. Прослушиватели экземпляров — экземпляры определенного класса или элементы, где один или несколько привязаны обработчики для этого перенаправленного события путем вызова AddHandler. Существующие WPF вызывают перенаправленные события AddHandler как часть CLR Добавление программы-оболочки событий{} и удалить{} реализации событий, который также является как простой XAML механизм присоединения обработчики событий через синтаксис атрибутов включен. Таким образом, даже простое XAML использования конечном счете приравнивается к AddHandler вызова.
Элементы в пределах визуального дерева проверяются на наличие реализаций зарегистрированных обработчиков. Обработчики потенциально вызываются на всем маршруте в порядке, соответствующем типу стратегии маршрутизации для этого перенаправленного события. Например, перенаправленные события восходящей маршрутизации сначала будут вызывать обработчики, присоединенные к элементу, который вызвал это перенаправленное событие. Затем перенаправленное событие «поднимается» к следующему родительскому элементу и так далее, пока не будет достигнут корневой элемент приложения.
С точки зрения корневого элемента в восходящем маршруте, если обработка класса или любой элемент, ближайший к источнику перенаправленного события, вызывает обработчики, которые помечают аргументы события как обработанные, то обработчики в корневых элементах не вызываются и маршрут события эффективно укорачивается до достижения этого корневого элемента. Однако маршрут не полностью останавливается, так как обработчики могут быть добавлены с использованием специального условия, что они должны по-прежнему вызываться, даже если обработчик класса или обработчик экземпляра пометил перенаправленное событие как обработанное. Это объясняется в разделе Добавление обработчиков экземпляра, которые вызываются, даже когда события помечены как обработанные далее в этой статье.
На более глубоком уровне, чем маршрут события, также потенциально имеется несколько обработчиков класса, действующих в любом экземпляре класса. Это связано с тем, что модель обработки класса для перенаправленных событий позволяет каждому из возможных классов в иерархии классов зарегистрировать свой собственный обработчик класса для каждого перенаправленного события. Каждый обработчик класса добавляется во внутреннее хранилище, и, когда формируется маршрут события для приложения, все обработчики классов добавляются в этот маршрут события. Обработчики классов добавляются в маршрут таким образом, что сначала вызывается обработчик класса, находящегося на самом низком уровне иерархии, а затем вызываются обработчики классов из каждого последующего базового класса. Как правило, обработчики классов не регистрируются, так что они также отвечают на перенаправленные события, которые уже были помечены как обработанные. Таким образом, этот механизм обработки класса позволяет выбрать один из двух следующих вариантов.
Производные классы могут дополнять обработку класса, наследуемую от базового класса, путем добавления обработчика, который не помечает перенаправленное событие как обработанное, поскольку обработчик базового класса будет иногда вызываться после обработчика производного класса.
Производные классы могут заменять обработку класса из базового класса путем добавления обработчика класса, который помечает перенаправленное событие как обработанное. Следует соблюдать осторожность при таком подходе, потому что в результате возможно изменение планируемой конструкции базового элемента управления в таких областях, как внешний вид, логика состояний, обработки ввода и обработка команд.
Обработка перенаправленных событий базовыми классами элементов управления
В каждом узле элементов в маршруте события прослушиватели классов имеют возможность ответить на перенаправленное событие прежде, чем это сможет сделать любой прослушиватель экземпляра в элементе. По этой причине обработчики классов иногда используются для подавления перенаправленных событий, которые определенная реализация класса элементов управления не желает распространять дальше, или для предоставления специальной обработки этого перенаправленного события, являющейся функцией класса. Например, класс может вызывать собственное событие класса, содержащее дополнительные сведения о том, что означает некоторое пользовательское условие ввода в контексте данного класса. Затем эта реализация класса может пометить более общее перенаправленное событие как обработанное. Обработчики класса обычно добавляются таким образом, чтобы они не вызываются для перенаправленных событий, где общие данные события уже помечены как обработанные, однако для нетипичных случаев имеется также RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) подпись, которая регистрирует обработчики классов для вызова, даже если перенаправленные события имеют помечено как обработанное.
Виртуальные функции обработчиков классов
Некоторые элементы, в частности базовые элементы, такие как UIElement, предоставляют пустые «на * событий» и «OnPreview*событий» виртуальные методы, которые соответствуют списку общих перенаправленных событий. Эти виртуальные методы можно переопределить, чтобы реализовать обработчик класса для перенаправленного события. Классы базовых элементов регистрируют эти виртуальные методы как свои обработчики классов для каждого такого перенаправленного события с помощью 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, событие, которое имеет больше смысла, так как известно, поступившее от настоящей кнопки, а не некоторые составной фигуру полностью кнопки или из другой элемент.
Обход скрытия события элементами управления
Иногда это поведение скрытия события внутри отдельных элементов управления может конфликтовать с некоторыми более общими целями логики обработки событий в приложении. Например если для какой-либо причине приложение имеет обработчик для MouseLeftButtonDown находится в корневом элементе приложения, то можно заметить, что любой щелчок мышью по кнопке не вызывает MouseLeftButtonDown или MouseLeftButtonUp обработчики на корневом уровне. Само событие действительно передается вверх (еще раз, маршруты событий на самом деле не завершены, но система перенаправления событий изменяет поведение вызова их обработчика после пометки их как обработанных). Когда перенаправленное событие достигает кнопки, ButtonBase обработку класса помечен MouseLeftButtonDown как обработанное, поскольку пытается заменить Click событием с большим смыслом. Таким образом, любой стандарт MouseLeftButtonDown обработчик далее по маршруту вызываться не будет. Существует два способа гарантировать, что в таких обстоятельствах ваши обработчики будут вызываться.
Первый способ состоит в том, чтобы намеренно добавить обработчик с помощью handledEventsToo
подпись AddHandler(RoutedEvent, Delegate, Boolean). Этот подход ограничен тем, что такой способ присоединения обработчика событий возможен только из кода, но не из разметки. Простой синтаксис указания имени обработчика событий в качестве значения атрибута события посредством XAML не позволяет такое поведение.
Второй способ работает только для событий ввода, где версии нисходящей и восходящей маршрутизации перенаправленного события объединены в пару. Для этих перенаправленных событий можно добавлять обработчики в версию перенаправленного события нисходящей маршрутизации. Это перенаправленное событие будет спускаться по маршруту, начиная от корня, поэтому код обработки класса кнопки не будет его перехватывать, при условии что вы присоединили обработчик предварительного просмотра на уровне предшествующего элемента в дереве элементов приложения. При использовании этого подхода будьте внимательны при пометке любого события предварительного просмотра как обработанного. В примере с PreviewMouseLeftButtonDown обрабатывается в корневом элементе, если вы пометили событие как Handled в реализации обработчика, фактически будет подавлено Click событий. Обычно это нежелательное поведение.