Markieren von Routingereignissen als behandelt und Klassenbehandlung
Aktualisiert: November 2007
Handler für ein Routingereignis können das Ereignis innerhalb der Ereignisdaten als behandelt markieren. Durch die Behandlung des Ereignisses wird die Route verkürzt. Bei der Klassenbehandlung handelt es sich um ein Programmierungskonzept, das von Routingereignissen unterstützt wird. Mit einem Klassenhandler kann ein bestimmtes Routingereignis auf Klassenebene behandelt werden. Der Klassenhandler wird vor den Instanzhandlern und Klasseninstanzen aufgerufen.
Dieses Thema enthält folgende Abschnitte.
- Vorbereitungsmaßnahmen
- Wann sollten Ereignisse als behandelt markiert werden?
- "Vorschauereignisse" (Tunneling-Ereignisse) im Vergleich zu Bubbling-Ereignissen und Ereignisbehandlung
- Klassenhandler und Instanzhandler
- Klassenbehandlung von Routingereignissen durch Steuerelement-Basisklassen
- Hinzufügen von Instanzhandlern, die auch dann aufgerufen werden, wenn ein Ereignis als behandelt markiert wurde
- Unterdrücken von Eingabeereignissen für die Steuerelementkomposition
- Verwandte Abschnitte
Vorbereitungsmaßnahmen
In diesem Thema werden die unter Übersicht über Routingereignisse eingeführten Konzepte näher ausgeführt.
Wann sollten Ereignisse als behandelt markiert werden?
Sie "markieren ein Ereignis als behandelt", indem Sie den Wert der Handled-Eigenschaft in den Ereignisdaten für ein Routingereignis auf true festlegen. Es gibt keine absolute Regel dafür, wann Routingereignisse als behandelt markiert werden sollten. Dies gilt sowohl für Anwendungsentwickler als auch für Autoren von Steuerelementen, die auf vorhandene Routingereignisse reagieren oder neue Routingereignisse implementieren. In der Regel sollte der Begriff "handled" in den Ereignisdaten des Routingereignisses als begrenztes Protokoll für die Reaktionen Ihrer Anwendung auf die verschiedenen in WPF APIs verfügbar gemachten Routingereignisse und auf benutzerdefinierte Routingereignisse verwendet werden. Eine andere Methode besteht darin, ein Routingereignis immer dann als behandelt zu markieren, wenn Ihr Code signifikant und relativ vollständig auf das Routingereignis reagiert hat. In der Regel sollte für ein Routingereignis nicht mehr als eine signifikante Antwort erforderlich sein, die separate Handler-Implementierungen erfordert. Wenn mehr Antworten erforderlich sind, muss der notwendige Code über Anwendungslogik implementiert werden, die in einem einzelnen Handler verkettet ist, statt das Routingereignissystem zur Weiterleitung zu verwenden. Die Bedeutung des Begriffs "signifikant" ist ebenfalls subjektiv und hängt von der Anwendung bzw. dem Code ab. Beispiele für "signifikante Antworten": Festlegen des Fokus, Ändern des öffentlichen Zustands, Festlegen von Eigenschaften, die sich auf die visuelle Darstellung auswirken, und Auslösen neuer Ereignisse. Beispiele für nicht signifikante Antworten: Ändern des privaten Zustands (ohne visuelle Auswirkungen oder programmgesteuerte Darstellung), Protokollierung von Ereignissen und Prüfen der Argumente eines Ereignisses und Entscheiden, darauf nicht zu antworten.
Das Verhalten des Routingereignissystems unterstreicht dieses Modell der "signifikanten Antworten" für die Verwendung des behandelten Zustands eines Routingereignisses, da in XAML oder mit der AddHandler-Common-Signatur hinzugefügte Handler nicht als Reaktion auf ein Routingereignis aufgerufen werden, wenn die Ereignisdaten als behandelt markiert sind. Um Routingereignisse zu behandeln, die von vorangehenden Vorkommen in der Ereignisroute als behandelt markiert wurden, müssen Sie einen Handler mit der handledEventsToo-Parameterversion hinzufügen (AddHandler(RoutedEvent, Delegate, Boolean)).
Unter Umständen werden bestimmte Routingereignisse von Steuerelementen selbst als behandelt markiert. Indem der Autor eines Steuerelements in WPF ein Routingereignis als behandelt markiert, gibt er an, dass die Aktionen, mit denen das Steuerelement auf das Routingereignis reagiert, signifikant bzw. vollständig als Teil der Implementierung des Steuerelements sind und das Ereignis nicht weiter behandelt werden muss. Hierzu wird in der Regel ein Klassenhandler für ein Ereignis hinzugefügt, oder eine der virtuellen Methoden des Klassenhandlers, die auf einer Basisklasse basieren, wird überschrieben. Falls erforderlich, können Sie diese Ereignisbehandlung umgehen. Weitere Informationen finden Sie unter Umgehen von Ereignisunterdrückung durch Steuerelemente weiter unten in diesem Thema.
"Vorschauereignisse" (Tunneling-Ereignisse) im Vergleich zu Bubbling-Ereignissen und Ereignisbehandlung
Vorschauereignisse sind Ereignisse, die einer Tunneling-Route durch die Elementstruktur folgen. Hierbei weist das Präfix "Preview" in der Namenskonvention auf das allgemeine Prinzip für Eingabeereignisse hin, dass Vorschau-/Tunneling-Routingereignisse vor den entsprechenden Bubbling-Ereignissen aufgerufen werden. Außerdem verfügen Eingaberoutingereignisse mit einem Tunneling/Bubbling-Paar über eine andere Behandlungslogik. Wenn das Tunneling-Routingereignis von einem Ereignislistener als behandelt markiert wird, wird das Bubbling-Ereignis als behandelt markiert, bevor es von den entsprechenden Listenern empfangen wird. Tunneling- und Bubbling-Ereignisse sind technisch getrennt, nutzen jedoch gemeinsam dieselbe Ereignisdateninstanz, um dieses Verhalten zu ermöglichen.
Die Verbindung zwischen Tunneling- und Bubbling-Ereignissen hängt von der internen Implementierung ab, wie eine gegebene WPF-Klasse die eigenen deklarierten Routingereignisse aufruft. Dies trifft auf die Eingaberoutingereignis-Paare zu. Wenn diese Implementierung auf Klassenebene nicht vorhanden ist, gibt es keine Verbindung zwischen einem Tunneling- und einem Bubbling-Ereignis, die dasselbe Benennungsschema nutzen: Ohne diese Implementierung handelt es sich um zwei vollständig getrennte Routingereignisse, die weder aufeinander folgend aufgerufen werden noch dieselben Ereignisdaten nutzen.
Nähere Informationen dazu, wie Sie Tunneling/Bubbling-Eingabeereignispaare in einer benutzerdefinierten Klasse implementieren, finden Sie unter Gewusst wie: Erstellen eines benutzerdefinierten Routingereignisses.
Klassenhandler und Instanzhandler
Bei Routingereignissen wird zwischen zwei Listenertypen unterschieden: Klassenlistener und Instanzlistener. Klassenlistener sind vorhanden, wenn Typen einen bestimmten EventManager API ,RegisterClassHandler in ihrem statischen Konstruktur aufgerufen haben oder die virtuelle Methode eines Klassenhandlers aus einer Basisklasse überschrieben haben. Bei Instanzlistenern handelt es sich um bestimmte Klasseninstanzen/-elemente, wobei ein oder mehrere Handler für dieses Routingereignis durch einen Aufruf von AddHandler angefügt wurden. Vorhandene WPF-Routingereignisse rufen AddHandler als Teil der add{}- und remove{}-Implementierungen des Ereignisses über den common language runtime (CLR)-Ereigniswrapper auf. Auf diese Weise wird auch der einfache XAML-Mechanismus des Anfügens von Ereignishandlern über eine Attributsyntax aktiviert. Aus diesem Grund entspricht selbst die einfache XAML-Verwendung letztlich einem AddHandler-Aufruf.
Elemente innerhalb der visuellen Struktur werden auf Implementierungen registrierter Handler überprüft. Handlers können innerhalb der Route in der Reihenfolge aufgerufen werden, die im Typ der Routingstrategie für das Routingereignis integriert ist. Bubbling-Routingereignisse rufen z. B. zuerst die Handler auf, die an das Element angefügt sind, mit dem das Routingereignis ausgelöst wurde. Daraufhin wird das Routingereignis an das nächste übergeordnete Element übergeben und so weiter, bis das Stammelement der Anwendung erreicht ist.
Wenn aus der Perspektive des Stammelements in einer Bubbling-Route die Klassenbehandlung oder ein beliebiges Element, das näher an der Quelle des Routingereignisses liegt, Handler aufrufen, die die Ereignisargumente als behandelt markieren, werden die Handler für das Stammelement nicht aufgerufen, und die Ereignisroute wird effektiv verkürzt, bevor das Stammelement erreicht wird. Die Route wird jedoch nicht vollständig angehalten, da Handler mit einer bestimmten Bedingung hinzugefügt werden können, die festlegt, dass die Handler aufgerufen werden müssen, selbst wenn das Routingereignis durch einen Klassen- oder Instanzhandler als behandelt markiert wurde. Dies wird weiter unten in diesem Thema unter Hinzufügen von Instanzhandlern, die auch dann aufgerufen werden, wenn ein Ereignis als behandelt markiert wurde näher erläutert.
Auf einer Ebene unterhalb der Ereignisroute können auch mehrere Klassenhandler jede beliebige Klasseninstanz behandeln. Grund hierfür ist, dass das Klassenbehandlungsmodell für Routingereignisse allen möglichen Klassen in einer Klassenhierarchie ermöglicht, den jeweils eigenen Klassenhandler für jedes Routingereignis zu registrieren. Alle Klassenhandler werden einem internen Speicher hinzugefügt, und bei Erstellung der Ereignisroute für eine Anwendung werden die Klassenhandler alle der Ereignisroute hinzugefügt. Die Klassenhandler werden der Route so hinzugefügt, dass der Klassenhandler mit den meisten Ableitungen zuerst aufgerufen wird und danach die Klassenhandler der einzelnen Folgebasisklassen. In der Regel werden Klassenhandler nicht so registriert, dass sie auch auf Routingereignisse reagieren, die als behandelt markiert wurden. Aus diesem Grund bietet diese Klassenbehandlungsmethode zwei Möglichkeiten:
Abgeleitete Klassen können die von der Basisklasse geerbte Klassenbehandlung ersetzen, indem ein Handler hinzugefügt wird, der das Routingereignis nicht als behandelt markiert, da der Handler der Basisklasse nach dem Handler der abgeleiteten Klasse aufgerufen wird.
Abgeleitete Klassen können die von der Basisklasse geerbte Klassenbehandlung ersetzen, indem ein Klassenhandler hinzugefügt wird, der das Routingereignis als behandelt markiert. Wägen Sie diese Vorgehensweise jedoch genau ab, da hierdurch möglicherweise der beabsichtigte Basissteuerelemententwurf in Bereichen wie visuelles Erscheinungsbild, Zustandslogik, Eingabebehandlung und Befehlsbehandlung geändert wird.
Klassenbehandlung von Routingereignissen durch Steuerelement-Basisklassen
An jedem gegebenen Elementknoten in einer Ereignisroute können Klassenlistener vor den Instanzlistenern des Elements auf das Routingereignis reagieren. Aus diesem Grund werden Klassenhandler in einigen Fällen verwendet, um Routingereignisse zu unterdrücken, die von der Implementierung einer bestimmten Steuerelementklasse nicht weitergegeben werden sollen, oder um das Routingereignis, bei dem es sich um ein Feature der Klasse handelt, gesondert zu behandeln. Beispiel: Eine Klasse kann ein eigenes klassenspezifisches Ereignis auslösen, das mehr Informationen darüber enthält, welche Auswirkungen eine Benutzereingabe im Kontext dieser bestimmten Klasse hat. In diesem Fall kann das allgemeinere Routingereignis durch die Implementierung der Klasse als behandelt markiert werden. Klassenhandler werden in der Regel so hinzugefügt, dass sie für Routingereignisse nicht aufgerufen werden, wenn gemeinsam genutzte Ereignisdaten als behandelt markiert wurden. Für untypische Fälle gibt es jedoch eine RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean)-Signatur, die Klassenhandler so registriert, dass sie aufgerufen werden, auch wenn Routingereignisse als behandelt markiert wurden.
Klassenhandler – Virtuelle Methoden
Einige Elemente, insbesondere Basiselemente wie UIElement, machen die leeren virtuellen Methoden "On*Event" und "OnPreview*Event" verfügbar, die der Liste der öffentlichen Routingereignisse entsprechen. Diese virtuellen Methoden können überschrieben werden, um einen Klassenhandler für das Routingereignis zu implementieren. Die Basiselementklassen registrieren diese virtuellen Methoden mithilfe von RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) als ihre Klassenhandler für diese Routingereignisse (siehe oben). Die virtuellen Methoden vom Typ On*Event vereinfachen die Implementierung der Klassenbehandlung für die relevanten Routingereignisse. Eine spezielle Initialisierung für jeden Typ in statischen Konstruktoren ist nicht erforderlich. Beispiel: Sie können eine Klassenbehandlung für das DragEnter-Ereignis in einer beliebigen von UIElement abgeleiteten Klasse hinzufügen, indem Sie die virtuelle Methode OnDragEnter überschreiben. In der Überschreibung können Sie das Routingereignis behandeln, eine klassenspezifische Logik initialisieren, die Elementeigenschaften von Instanzen ändern kann, oder beides. Rufen Sie die Basisimplementierung in solchen Überschreibungen auf, auch wenn Sie das Ereignis als behandelt markieren. In den meisten Fällen müssen Sie die Basisimplementierung aufrufen, da die virtuelle Methode zur Basisklasse gehört. Das standardmäßige geschützte virtuelle Muster zum Aufrufen der Basisimplementierungen der einzelnen virtuellen Methoden ersetzt einen gleichwertigen Mechanismus der Klassenbehandlung von Routingereignissen, wobei für eine gegebene Instanz Klassenhandler für alle Klassen in einer Klassenhierarchie aufgerufen werden. Auch hier wird mit dem Klassenhandler mit den meisten Ableitungen begonnen und fortgefahren, bis der Basisklassenhandler erreicht ist. Lassen Sie den Aufruf der Basisimplementierung nur weg, wenn bei Ihrer Klasse eine Notwendigkeit zur Änderung der Basisklassen-Behandlungslogik besteht. Ob Sie die Basisimplementierung vor oder nach dem überschreibenden Code aufrufen, hängt von der Art Ihrer Implementierung ab.
Eingabeereignis-Klassenbehandlung
Die virtuellen Methoden der Klassenhandler werden alle so registriert, dass sie nur aufgerufen werden, wenn gemeinsam genutzte Ereignisdaten nicht als behandelt markiert sind. Für Eingabeereignisse werden die Tunneling- und Bubbling-Versionen in der Regel nacheinander ausgelöst und verwenden dieselben Ereignisdaten. Bei einem gegebenen Klassenhandler-Paar für Eingabeereignisse, das aus Tunneling- und Bubbling-Version besteht, sollten Sie das Ereignis also nicht unmittelbar als behandelt markieren. Wenn Sie die virtuelle Methode der Tunneling-Klassenbehandlung implementieren, um das Ereignis als behandelt zu markieren, kann der Bubbling-Klassenhandler nicht aufgerufen werden. (Dasselbe gilt für alle regulär registrierten Instanzhandler für das Tunneling- oder das Bubbling-Ereignis.)
Nach Abschluss der Klassenbehandlung für einen Knoten werden die Instanzlistener berücksichtigt.
Hinzufügen von Instanzhandlern, die auch dann aufgerufen werden, wenn ein Ereignis als behandelt markiert wurde
Die AddHandler-Methode stellt eine bestimmte Überladung bereit, die Ihnen ermöglicht, Handler hinzuzufügen, die vom Ereignissystem stets aufgerufen werden, wenn ein Ereignis das Behandlungselement in der Route erreicht, selbst wenn die Ereignisdaten bereits von einem anderen Handler geändert wurden, um das Ereignis als behandelt zu markieren. Dies ist nicht die Regel. Im Allgemeinen können Handler geschrieben werden, um beliebige Bereiche im Anwendungscode zu ändern, auf die ein Ereignis Auswirkungen haben kann. Dabei spielt es keine Rolle, an welchem Punkt einer Elementstruktur das Ereignis behandelt wurde, selbst wenn mehrere Endergebnisse benötigt werden. Außerdem muss normalerweise nur ein Element auf das Ereignis und die entsprechende Anwendungslogik reagieren. Die handledEventsToo-Überladung steht für Ausnahmefälle zur Verfügung, in denen ein Ereignis bereits durch ein anderes Element in einer Elementstruktur oder einer Steuerelementkomposition als behandelt markiert wurde, es jedoch erforderlich ist, dass die Handler weiterer Elemente mit einem höheren oder einem niedrigeren Rang in der Elementstruktur (je nach Route) aufgerufen werden.
Wann sollten behandelte Ereignisse als nicht behandelt markiert werden?
In der Regel sollten als behandelt markierte Ereignisse nicht als nicht behandelt markiert werden (durch Zurücksetzen von Handled auf false). Dies gilt auch für Handler mit handledEventsToo. Einige Eingabeereignisse weisen jedoch Ereignisse auf höherer und auf niedrigerer Ebene auf, die ü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 dargestellt wird. Beispiel: Ein untergeordnetes Element überwacht ein Tastenereignis auf höherer Ebene wie TextInput, und ein übergeordnetes Element überwacht ein Ereignis auf niedrigerer Ebene wie KeyDown. Wenn das übergeordnete Element das Ereignis auf niedrigerer Ebene behandelt, kann das Ereignis auf höherer Ebene selbst im untergeordneten Element unterdrückt werden, das normalerweise zuerst das Ereignis behandeln müsste.
In einer solchen Situation kann es erforderlich sein, sowohl den übergeordneten als auch den untergeordneten Elementen für das Ereignis auf niedrigerer Ebene Handler hinzuzufügen. Mit der Handlerimplementierung für das untergeordnete Element kann das Ereignis auf niedrigerer Ebene als behandelt markiert werden, diese Markierung kann jedoch mit der Handlerimplementierung für das übergeordnete Element wieder rückgängig gemacht werden, sodass weitere Strukturelemente auf höheren Ebenen (wie auch das Ereignis auf höherer Ebene) die Möglichkeit zur Reaktion haben. Diese Situation tritt jedoch nur äußerst selten ein.
Unterdrücken von Eingabeereignissen für die Steuerelementkomposition
Das Hauptszenario, bei dem die Klassenbehandlung von Routingereignissen für Eingabeereignisse und zusammengesetzte Steuerelemente verwendet wird. Ein zusammengesetztes Steuerelement besteht aus mehreren praktischen Steuerelementen oder Steuerelement-Basisklassen. Der Autor des Steuerelements möchte in vielen Fällen alle möglichen Eingabeereignisse, die von den einzelnen Unterkomponenten ausgelöst werden können, zusammenfassen, um das gesamte Steuerelement als singulare Ereignisquelle anzugeben. In einigen Fällen möchte der Autor des Steuerelements aber auch die von bestimmten Komponenten ausgelösten Ereignisse komplett unterdrücken oder durch ein komponentendefiniertes Ereignis ersetzen, das mehr Informationen enthält oder ein spezifischeres Verhalten impliziert. Ein für jeden Komponentenautor sofort sichtbares Beispiel ist die Behandlung eines Mausereignisses durch Windows Presentation Foundation (WPF) Button, das letztlich in ein intuitives Ereignis aufgelöst wird, das alle Schaltflächen aufweisen: ein Click-Ereignis.
Die Button-Basisklasse (ButtonBase) wird von Control abgeleitet, das wiederum von FrameworkElement und UIElement abgeleitet wird, und ein großer Teil der benötigten Ereignisinfrastruktur für die Verarbeitung der Steuerelementeingabe ist auf der UIElement-Ebene verfügbar. Dabei verarbeitet UIElement allgemeine Mouse-Ereignisse, die die Trefferüberprüfungen für den Mauszeiger innerhalb seiner Begrenzungen behandeln, und stellt verschiedene Ereignisse für die allgemeinsten Schaltflächenaktionen bereit, wie z. B. MouseLeftButtonDown. UIElement stellt außerdem die leere virtuelle Methode OnMouseLeftButtonDown als vorab registrierten Klassenhandler für MouseLeftButtonDown bereit, die von ButtonBase überschrieben wird. Gleichermaßen 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 auf dem Rest der Route an andere Klassenhandler sowie an Instanzhandler und Ereignissetter übergeben. Außerdem löst die OnMouseLeftButtonUp-Überschreibung das Click-Ereignis aus. Das Endergebnis für die meisten Listener besteht darin, dass die Ereignisse MouseLeftButtonDown und MouseLeftButtonUp entfernt und durch Click ersetzt werden, ein Ereignis, das mehr Informationen enthält, da bekannt ist, dass dieses Ereignis von einer Schaltfläche mit dem Wert TRUE und nicht von einem Teil der Zusammensetzung oder von einem ganz anderen Element stammt.
Umgehen von Ereignisunterdrückung durch Steuerelemente
In einigen Fällen kann das Verhalten der Ereignisunterdrückung in einzelnen Steuerelementen mit allgemeinerer Ereignisbehandlungslogik Ihrer Anwendung in Konflikt treten. Beispiel: Wenn die Anwendung über einen Handler für MouseLeftButtonDown im Stammelement der Anwendung verfügt, wird bei einem Mausklick auf eine Schaltfläche nicht der MouseLeftButtonDown-Handler oder der MouseLeftButtonUp-Handler auf Stammebene ausgeführt. Das Ereignis selbst wird tatsächlich weitergegeben (Ereignisrouten werden nicht im eigentlichen Sinne beendet, sondern das Routingereignissystem ändert das Handleraufrufverhalten nach der Markierung als behandelt). Wenn das Routingereignis die Schaltfläche erreicht, markiert die ButtonBase-Klassenbehandlung MouseLeftButtonDown als behandelt, da das Click-Ereignis als Ersatz verwendet werden soll. Aus diesem Grund würde kein standardmäßiger MouseLeftButtonDown-Handler weiter oben in der Route aufgerufen werden. In diesem Fall gibt es zwei Möglichkeiten, mit denen Sie sicherstellen können, dass diese Handler aufgerufen werden.
Sie können den Handler unter Verwendung der handledEventsToo-Signatur von AddHandler(RoutedEvent, Delegate, Boolean) explizit hinzufügen. Diese Möglichkeit weist die Einschränkung auf, dass sie nur mit Code und nicht mit Markup funktioniert. Durch die einfache Angabe des Ereignishandlernamens als Ereignisattributwert über Extensible Application Markup Language (XAML) wird das Verhalten nicht aktiviert.
Die zweite Möglichkeit gilt nur für Eingabeereignisse, bei denen die Tunneling- und Bubbling-Version des Routingereignisses ein Paar bilden. Für diese Routingereignisse können Sie stattdessen dem äquivalenten Vorschau-/Tunneling-Ereignis Handler hinzufügen. Dieses Routingereignis durchläuft die Route beginnend mit dem Stamm, sodass der Schaltflächen-Klassenbehandlungscode nicht damit in Konflikt tritt, vorausgesetzt, Sie haben für den Vorschauhandler eine Vorgängerebene in der Elementstruktur gewählt. Wenn Sie diese Möglichkeit verwenden, sollten Sie Vorschauereignisse in der Regel nicht als behandelt markieren. Wenn Sie in dem Beispiel, in dem PreviewMouseLeftButtonDown im Stammelement behandelt wird, das Ereignis in der Handlerimplementierung als Handled markieren würden, würde das Click-Ereignis unterdrückt. Dieses Verhalten ist in der Regel nicht wünschenswert.
Siehe auch
Aufgaben
Gewusst wie: Erstellen eines benutzerdefinierten Routingereignisses
Konzepte
Übersicht über Routingereignisse