Markieren von Routingereignissen als behandelt und Klassenbehandlung

Handler für ein Routingereignis können das Ereignis innerhalb der Ereignisdaten als behandelt markieren. Das Behandeln des Ereignisses verkürzt die Route. Die Klassenbehandlung ist ein Programmierkonzept, das von Routingereignissen unterstützt wird. Ein Klassenhandler hat die Möglichkeit, ein bestimmtes Routingereignis auf Klassenebene mit einem Ereignishandler zu verarbeiten, der vor jedem Instanzenhandler auf jeder Instanz der Klasse aufgerufen wird.

Voraussetzungen

In diesem Thema werden unter Übersicht über Routingereignisse eingeführte Konzepte näher erläutert.

Markieren von Ereignissen als behandelt

Wenn Sie „ein Ereignis als behandelt markieren“, legen Sie den Wert der Handled-Eigenschaft in den Ereignisdaten für ein Routingereignis auf true fest. Es gibt keine absolute Regel dafür, wann Sie Routingereignisse als Anwendungsentwickler oder als Autor von Steuerelementen, der auf vorhandenen Routingereignisse reagiert oder neue Routingereignisse implementiert, als behandelt markieren sollten. Das Konzept „behandelt“, so wie es in den Ereignisdaten des Routingereignisses ausgeführt wird, sollte als begrenztes Protokoll für die Antworten Ihrer eigenen Anwendung auf verschiedene weitergeleitete Ereignisse, die in WPF-APIs verfügbar gemacht wurden, sowie für alle benutzerdefinierten Routingereignisse verwendet werden. Eine weitere Möglichkeit, das Problem als „behandelt“ zu markieren ist, wenn Code auf eine Weise signifikant und relativ vollständig auf das Routingereignis geantwortet hat. In der Regel sollte es nicht mehr als eine signifikante Antwort geben, die separate Handlerimplementierungen für Routingereignisse erfordern muss. Wenn mehr Antworten erforderlich sind, sollte der erforderliche Code über Anwendungslogik implementiert werden, die in einem einzelnen Handler, nicht mithilfe des Weiterleitungssystems des Routingereignisses verkettet ist. Das Konzept von „erheblich“ ist ebenfalls subjektiv und hängt von der Anwendung oder dem Code ab. „Signifikante Antworten“ sind z.B.: das Festlegen des Fokus, die Änderung des öffentlichen Zustands, das Festlegen von Eigenschaften, die sich auf die visuelle Darstellung auswirken, und das Auslösen neuer Ereignisse. Beispiele für nicht signifikante Antworten sind: die Änderung des privaten Status (mit keiner visuellen Auswirkung oder eine programmgesteuerte Darstellung), das Protokollieren von Ereignissen, oder das Anzeigen eines Ereignisses und die Wahl, nicht darauf zu reagieren.

Das Verhalten des Routingereignissystems verstärkt das Modell der „signifikanten Reaktion“ für die Verwendung des behandelten Zustands eines Routingereignisses, da die in XAML hinzugefügten Handler oder die allgemeine Signatur von AddHandler nicht als Reaktion auf ein Ereignis aufgerufen werden, bei dem die Ereignisdaten bereits als behandelt markiert wurden. Sie müssen sich die Mühe machen, einen Ereignishandler mit der handledEventsToo-Parameterversion hinzuzufügen (AddHandler(RoutedEvent, Delegate, Boolean)), um Routingereignisse zu behandeln, die von früheren Teilnehmern in der Ereignisroute als behandelt gekennzeichnet wurden.

In einigen Fällen markieren Steuerelemente selbst bestimmte Routingereignisse als behandelt. Mit einem behandelten Rountingereignis gibt der Autor eines WPF-Steuerelements zu verstehen, dass die Reaktion des Steuerelements auf das Routingereignis signifikant bzw. im Rahmen der Implementierung vollständig ist und dass das Ereignis keine weitere Behandelung benötigt. Dies erfolgt normalerweise durch Hinzufügen eines Klassenhandlers für ein Ereignis oder durch Überschreiben eines der virtuellen Klassenhandler, die in einer Basisklasse vorhanden sind. Sie können dieses Ereignisbehandlung, wenn notwendig, umgehen. Informationen dazu finden Sie weiter unten im Abschnitt Umgehen der Ereignisunterdrückung von Steuerelementen in diesem Thema.

„Vorschau“ (Tunneling)-Ereignisse vs. Bubbling-Ereignisse und Ereignisbehandlung

Vorschaurountingereignisse sind Ereignisse, die einer Tunnelingroute in der Elementstruktur folgen. Die „Vorschau“ in der Benennungskonvention weist auf das allgemeine Prinzip bei Eingabeereignissen hin, nach dem Vorschau-/ Tunneling-Routingereignisse vor den entsprechenden Bubbling-Rountingereignissen ausgelöst werden. Außerdem haben Eingaberountingereignisse mit einem Tunneling- und einem Bubblingereignis eine andere Behandlungslogik. Wenn das Tunneling-/ Vorschauroutingereignis von einem Ereignislistener als behandelt markiert wurde, dann wird das Bubblingereignis als behandelt markiert, noch bevor alle Listener des Bubblingereignisses es erhalten. Die Tunneling- und Bubblingroutingereignisse sind technisch separate Ereignisse. Sie teilen sich aber absichtlich dieselbe Ereignisdateninstanz, um dieses Verhalten zu ermöglichen.

Die Verbindung zwischen Tunneling- und Bubbling-Rountingereignissen wird mithilfe der internen Implementierung erreicht, die angibt, wie jede gegebene WPF-Klasse die eigenen deklarierten Routingereignisse auslöst. Dies gilt für alle gekoppelten Eingaberountingereignisse. Es besteht keine Verbindung zwischen einem Tunneling- und einem Bubbling-Routnigereignis, die dasselbe Benennungsschema nutzen, wenn diese Implementierung auf Klassenebene nicht vorhanden: Ohne diese Implementierung wären beide Routingereignisse zwei vollständig getrennt und würden weder in der Sequenz ausgelöst werden, noch dieselben Ereignisdaten haben.

Weitere Informationen dazu, wie Sie Tunneling-/Bubbling-Eingabeereignispaare in einer benutzerdefinierten Klasse implementieren, finden Sie unter Erstellen eines benutzerdefinierten Routingereignisses.

Klassenhandler und Instanzhandler

Rountingereignisse sollten Sie zwei verschiedene Arten von Ereignislistenern beachten: Klassenlistener und Instanzlistener. Klassenlistener werden erstellt, wenn bestimmte Typen eine besondere EventManager-API, RegisterClassHandler, in ihrem statischen Konstruktor aufrufen, oder eine virtuelle Klassenhandlermethode in der Basisklasse eines Elements überschrieben haben. Instanzlistener sind bestimmte Klasseninstanzen/-elemente, in denen ein oder mehrere Handler durch einen Aufruf von AddHandler für dieses Routingereignis angefügt wurde. Vorhandene WPF-Routingereignisse rufen im Rahmen der Common Language Runtime (CLR)-Ereigniswrapper add{} and remove{}-Implementierungen des Ereignisses AddHandler auf. So werden auch einfache XAML-Mechanismen für das Anfügen von Ereignishandlern über eine Attributsyntax aktiviert. Daher ist selbst die einfache XAML-Verwendung letztlich gleichbedeutend mit einem AddHandler-Aufruf.

Elemente innerhalb der visuellen Struktur werden auf registrierte Handlerimplementierungen überprüft. Handler werden möglicherweise in der gesamten Route aufgerufen, und zwar in der Reihenfolge, die im Typ der Routingstrategie für das Routingereignis inhärent ist. Bubbling-Routingereignisse rufen z.B. zuerst die Handler auf, die dem gleichen Element zugeordnet sind, das das Routingereignis ausgelöst hat. Das Routingereignis bubblet dann zu dem nächsten übergeordneten Element und so weiter, bis das Stammelement der Anwendung erreicht ist.

Wenn aus der Perspektive des Stammelements in einer Bubblingroute die Klassenbehandlung oder ein beliebiges Element, das sich näher an der Quelle des Routingereignisses befindet, Handler aufrufen, die die Ereignisargumente als behandelt markieren, werden Handler in den Stammelementen nicht aufgerufen, und die Ereignisroute wird effektiv verkürzt, bevor das Stammelement erreicht wird. Die Route wird jedoch nicht vollständig angehalten, da Handler mithilfe der bestimmten Bedingung hinzugefügt werden können, dass sie trotzdem noch aufgerufen werden sollen, auch wenn ein Klassen- oder Instanzhandler das Routingereignis als behandelt markiert hat. Informationen hierzu finden Sie im Abschnitt Hinzufügen von Instanzhandlern, die ausgelöst werden, obwohl Ereignisse als behandelt markiert wurden weiter unten in diesem Thema.

Auf einer tieferen Ebene als die Ereignisroute operieren möglicherweise mehrere Klassenhandler auf jeder gegebenen Instanz einer Klasse. Das liegt daran, dass das Klassenbehandlungsmodell für Routingereignisse es allen möglichen Klassen in einer Klassenhierarchie ermöglicht, einen eigenen Klassenhandler für jedes Routingereignis zu registrieren. Jeder Klassenhandler wird einem internen Speicher hinzugefügt, und wenn die Ereignisroute für eine Anwendung erstellt wird, werden die Klassenhandler werden alle der Ereignisroute hinzugefügt. Klassenhandler werden der Route so hinzugefügt, dass der am stärksten abgeleitete Klassenhandler zuerst und die Klassenhandler aus jeder nachfolgenden Basisklasse im Anschluss aufgerufen werden. Klassenhandler werden im Allgemeinen nicht registriert, sodass sie auch auf Routingereignisse reagieren, die bereits als behandelt markiert wurden. Dieser Mechanismus zur Klassenbehandlung ermöglicht eine dieser zwei Optionen:

  • Abgeleitete Klassen können die Klassenbehandlung ergänzen, die von der Basisklasse geerbt wird, indem ein Handler hinzugefügt wird, der das Routingereignis nicht als behandelt markiert, da der Basisklassenhandler nach dem Handler der abgeleiteten Klasse aufgerufen wird.

  • Abgeleitete Klassen können die Klassenbehandlung der Basisklasse ersetzen, indem ein Klassenhandler hinzugefügt wird, der das Routingereignis als behandelt markiert. Bei diesem Ansatz sollten Sie sorgfältig vorgehen, da er möglicherweise das vorgesehene Design des Basissteuerelements in Bereichen wie der visuelle Darstellung, der Zustandslogik, der Eingabeverarbeitung und der Befehlsbehandlung ändert.

Klassenbehandlung von Routingereignissen durch Steuerelementbasisklassen

Auf jedem gegebene Elementknoten in einer Ereignisroute haben Klassenlistener die Möglichkeit, vor jedem Instanzlistener auf dem Element auf das Routingereignis zu reagieren. Aus diesem Grund werden Klassenhandler manchmal verwendet, um Routingereignisse zu unterdrücken, die von der Implementierung einer bestimmten Steuerelementklasse nicht weitergegeben werden sollen, oder um eine besondere Behandlung des Routingereignisses bereitzustellen, das eine Funktion der Klasse ist. Eine Klasse kann z.B. ein eigenes klassenspezifisches Ereignis auslösen, das weitere Details über die Bedeutung einer Benutzereingabebedingung im Kontext der jeweiligen Klasse enthält. Die Implementierung der Klasse kann das allgemeinere Routingereignis dann als behandelt markieren. Klassenhandler werden in der Regel so hinzugefügt, dass sie für Routingereignisse nicht aufgerufen werden, bei denen gemeinsam genutzte Ereignisdaten bereits als behandelt markiert wurden. Für atypische Fälle gibt es aber eine RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean)-Signatur, die aufzurufende Klassenhandler registriert, selbst wenn Routingereignisse als behandelt markiert wurden.

Virtuelle Klassenhandler

Einige Elemente, insbesondere Basiselemente wie UIElement, machen virtuelle „On*Event“- und „OnPreview*Event“-Methoden verfügbar, die ihrer Liste der öffentlichen Routingereignisse entsprechen. Diese virtuellen Methoden können überschrieben werden, um einen Klassenhandler für dieses Routingereignis zu implementieren. Die Basiselementklassen registrieren diese virtuellen Methoden als ihre Klassenhandler für jedes solche Routingereignis unter Verwendung von RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), wie zuvor beschrieben. Die virtuellen On*Event-Methoden vereinfachen die Implementierung der Klassenbehandlung für die relevanten Routingereignisse, ohne eine spezielle Initialisierung in statischen Konstruktoren für jeden Typ zu erfordern. Sie können beispielsweise einen Klassenhandler für das DragEnter-Ereignis in einer abgeleiteten UIElement-Klasse hinzufügen, indem Sie die virtuelle Methode OnDragEnter überschreiben. In der Überschreibung können Sie das Routingereignis behandeln, andere Ereignisse auslösen, klassenspezifische Logik initialisieren, die Elementeigenschaften für Instanzen ändern kann, oder jede beliebige Kombination dieser Aktionen ausführen. Sie sollten die Basisimplementierung solcher Überschreibungen generell aufrufen, selbst wenn Sie das Ereignis als behandelt markieren. Das Aufrufen der Basisimplementierung wird dringend empfohlen, da sich die virtuelle Methode in der Basisklasse befindet. Das geschützte virtuelle Standardmuster, bei dem die Basisimplementierungen aus jeder virtuellen Methode aufgerufen werden, ersetzt und entspricht einem ähnlichen Mechanismus, der nativ in der Klassenbehandlung von Routingereignissen vorkommt. Dabei werden Klassenhandler für alle Klassen in einer Klassenhierarchie auf jeder Instanz aufgerufen, beginnend mit dem Handler der am stärksten abgeleiteten Klasse und anschließend mit den Handlern der Basisklasse. Sie sollten den Aufruf der Basisimplementierung nur weglassen, wenn Ihre Klasse eine explizite Anforderung enthält, die Logik der Basisklassenbehandlung zu ändern. Es hängt von der Art Ihrer Implementierung ab, ob Sie die Basisimplementierung vor oder nach dem überschreibenden Code aufrufen.

Klassenbehandlung von Eingabeereignissen

Virtuelle Methoden des Klassenhandlers werden alle so registriert, dass sie nur dann aufgerufen werden, wenn gemeinsam genutzte Ereignisdaten nicht bereits als behandelt markiert wurden. Außerdem werden nur bei Eingabeereignissen die Tunneling- und Bubblingversionen in der Regel nacheinander ausgelöst und verwenden dieselben Ereignisdaten. Dazu sollten Sie ein Ereignis für ein bestimmtes Paar von Eingabeereignis-Klassenhandlern, von denen einer die Tunneling-und der andere die Bubblingversion darstellt, nicht sofort als behandelt markieren. Wenn Sie die virtuelle Methode der Tunneling-Klassenbehandlung implementieren, um das Ereignis als behandelt zu markieren, wird der Bubbling-Klassenhandler daran gehindert, aufgerufen zu werden (außerdem wird verhindert, dass alle normal registrierten Instanzhandler für das Tunneling- oder das Bubblingereignis aufgerufen werden).

Nach Abschluss der Klassenbehandlung für einen Knoten werden die Instanzlistener berücksichtigt.

Hinzufügen von Instanzhandlern, die ausgelöst werden, obwohl Ereignisse als behandelt markiert wurden

Die AddHandler-Methode stellt eine bestimmte Überladung bereit, mit dem Sie Handler hinzufügen können. Diese Handler werden vom Ereignissystem aufgerufen, wenn ein Ereignis das behandelnde Element in der Route erreicht, selbst wenn die Ereignisdaten bereits von einem anderen Handler angepasst wurden, um das Ereignis als behandelt zu markieren. Dies wird in der Regel nicht ausgeführt. Ereignishandler können generell geschrieben werden, um alle Bereiche des Anwendungscodes anzupassen, die von einem Ereignis beeinflusst werden können, unabhängig davon, wo es in einer Elementstruktur behandelt wurde, auch wenn mehrere Ergebnisse gewünscht sind. Darüber hinaus gibt es in der Regel nur ein Element, das auf dieses Ereignis reagieren muss, und die entsprechende Anwendungslogik ist bereits geschehen. Für Ausnahmefälle steht die handledEventsToo-Überladung zur Verfügung, in der ein anderes Element in einer Elementstruktur oder Zusammensetzung von Steuerelementen ein Ereignis bereits als behandelt markiert hat, während andere, in der Elementstruktur höhere oder niedrigere Elemente (je nach Route) ihre eigenen Handler aber aufrufen möchten.

Markieren von behandelten Ereignisse als nicht behandelt

Rountingereignisse, die als behandelt markiert wurden, sollten generell nicht als unbehandelt markiert werden (Handled auf false zurückgesetzt), selbst mit Handlern, die auf handledEventsToo reagieren. Allerdings haben einige Ereignisse Ereignisdarstellungen auf hoher und niedriger Ebene, die sich überlappen können, wenn das Ereignis auf höherer Ebene an einer Position in der Struktur und das Ereignis auf niedrigerer Ebene an einer anderen Position angezeigt wird. Dies geschieht z. B., wenn ein untergeordnetes Element ein wichtiges Ereignis auf höherer Ebene belauscht, z. B. TextInput, während ein übergeordnetes Element ein Ereignis auf niedrigerer Ebene belauscht, z. B. KeyDown. Wenn das übergeordnete Element das Ereignis auf niedrigerer Ebene behandelt, kann das Ereignis auf höherer Ebene sogar im untergeordneten Element unterdrückt werden, das intuitiv als Erstes die Möglichkeit haben sollte, das Ereignis zu behandeln.

In diesen Situationen kann es erforderlich sein, für das Ereignis auf niedrigerer Ebene sowohl dem übergeordneten als auch dem untergeordneten Element Handler hinzuzufügen. Die Handlerimplementierung für das untergeordnete Element kann das Ereignis auf niedrigerer Ebene als behandelt markieren, aber die Handlerimplementierung des übergeordneten Elements würde dies wieder rückgängig machen, damit andere Elemente weiter oben in der Struktur (und das Ereignis auf höherer Ebene) die Möglichkeit haben, zu reagieren. Diese Situation ist aber relativ selten.

Beabsichtigtes Unterdrücken von Eingabeereignissen für die Zusammensetzung von Steuerelementen

Das Hauptszenario, in dem die Klassenbehandlung von Routingereignissen dazu verwendet wird, sind Eingabeereignisse und zusammengesetzte Steuerelemente. Ein zusammengesetztes Steuerelement besteht per definitionem aus mehreren praktischen Steuerelementen oder Steuerelement-Basisklassen. Häufig möchte der Autor des Steuerelements alle möglichen Eingabeereignisse verbinden, die von den einzelnen Unterkomponenten ausgelöst werden können, um das gesamte Steuerelement als Ereignisquelle zu melden. In einigen Fällen möchte der Autor des Steuerelements die Ereignisse von Komponenten möglicherweise vollständig unterdrücken oder ein komponentendefiniertes Ereignis ersetzen, das weitere Informationen enthält oder ein spezifischeres Verhalten impliziert. Das kanonische, für jeden Komponentenautor sofort sichtbare Beispiel zeigt, wie eine Windows Presentation Foundation (WPF)-Button ein Mausereignis behandelt, das letztlich in ein intuitives Ereignis aufgelöst wird, das alle Schaltflächen aufweisen: ein Click-Ereignis.

Die Button-Basisklasse (ButtonBase) leitet sich von Control ab, was wiederum von FrameworkElementund UIElement abgeleitet wird, und vieles der für die Verarbeitung von Steuerelementeingaben benötigten Ereignisinfrastruktur ist auf der Ebene UIElement verfügbar. Insbesondere verarbeitet UIElement die allgemeinen Mouse-Ereignisse, die die Trefferprüfung für den Mauszeiger innerhalb seiner Grenzen behandeln, und stellt eigene Ereignisse für die häufigsten Schaltflächenaktionen, wie MouseLeftButtonDown, bereit. UIElement stellt auch einen leeren virtuellen OnMouseLeftButtonDown als vorregistrierten Klassenhandler für MouseLeftButtonDown bereit, und ButtonBase überschreibt es. In ähnlicher Weise verwendet ButtonBase Klassenhandler für MouseLeftButtonUp. In den Überschreibungen, denen die Ereignisdaten übergeben werden, markieren die Implementierungen die RoutedEventArgs-Instanz als behandelt, indem Handled auf true festgelegt wird. Dieselben Ereignisdaten werden in der verbleibenden Route an die anderen Klassenhandler und auch an die Instanzhandler oder Ereignissetter weitergegeben. Außerdem löst das Überschreiben vonOnMouseLeftButtonUp als nächstes das Ereignis Click aus. Das Endergebnis für die meisten Listener ist, dass die Ereignisse MouseLeftButtonDown und MouseLeftButtonUp „verschwinden“ und durch Click ersetzt werden. Dies ist ein Ereignis mit mehr Aussagekraft, weil man dadurch weiß, dass dieses Ereignis einer echten Schaltfläche und keinem zusammengesetzten Teil der Schaltfläche oder einem völlig anderen Element entstammt.

Umgehen der Ereignisunterdrückung von Steuerelementen

Manchmal kann dieses Ereignis unterdrückende Verhalten in einzelnen Steuerelementen einige allgemeinere Absichten der Ereignisbehandlungslogik der Anwendung behindern. Wenn Ihre Anwendung beispielsweise einen Handler für MouseLeftButtonDown im Stammelement der Anwendung hätte, würden Sie feststellen, dass ein Mausklick auf eine Schaltfläche nicht die Handler für MouseLeftButtonDown oder MouseLeftButtonUp auf der Stammebene aufrufen würde. Das Ereignis selbst würde aufbubblen (wie gesagt, Ereignisroutes werden nicht tatsächlich beendet, das Routingereignissystem ändert nur deren Aufrufverhalten für Ereignishandler, nachdem sie als behandelt markiert wurden). Als das Routingereignis die Schaltfläche erreichte, hat das ButtonBase-Klassen-Handling MouseLeftButtonDown als behandelt markiert, weil es das Click-Ereignis durch eines mit größerer Aussagekraft ersetzen wollte. Daher würde ein Standard-MouseLeftButtonDown-Handler weiter oben auf der Route nicht aufgerufen werden. Es gibt zwei Verfahren, mit denen Sie sicherzustellen können, dass die Handler unter diesen Umständen aufgerufen werden würden.

Die erste Technik besteht darin, den Handler absichtlich unter Verwendung der handledEventsToo-Signatur von AddHandler(RoutedEvent, Delegate, Boolean) hinzuzufügen. Dieses Verfahren zum Anhängen eines Ereignishandlers ist aber nur im Code und nicht in Markup möglich. Die einfache Syntax für die Angabe des Ereignishandlernamens als Ereignisattributwert über Extensible Application Markup Language (XAML) ermöglicht dieses Verhalten nicht.

Das zweite Verfahren funktioniert nur für Eingabeereignisse, in denen die Tunneling- und Bubblingversionen des Routingereignisses kombiniert werden. Für diese Routingereignisse können Sie stattdessen der Vorschau-/Tunnelingversion des Routingereignisses Handler hinzufügen. Dieses Routingereignis tunnelt ausgehend vom Stamm durch die Route, damit der Code für die Behandlung der Schaltflächenklasse es nicht abfängt, vorausgesetzt, dass Sie den Vorschauhandler in einer Vorgängerebene in der Anwendungsstruktur angefügt haben. Wenn Sie diesen Ansatz verwenden, sollten Sie beim Markieren von Vorschauereignissen als behandelt vorsichtig vorgehen. Wenn Sie in dem Beispiel, in dem PreviewMouseLeftButtonDown am Stammelement behandelt wird, das Ereignis in der Handler-Implementierung als Handled markieren, würden Sie das Ereignis Click tatsächlich unterdrücken. Dies ist in der Regel kein erwünschtes Verhalten.

Siehe auch