Freigeben über


Übersicht über Routingereignisse

Dieses Thema beschreibt das Konzept der weitergeleiteten Ereignisse in Windows Presentation Foundation (WPF). Das Thema definiert die Terminologie von Routingereignissen, beschreibt, wie Routingereignisse anhand einer Struktur von Elementen weitergeleitet werden und führt Sie in das Erstellen Ihrer eigenen, benutzerdefinierten Routingereignisse ein.

Voraussetzungen

In diesem Thema wird davon ausgegangen, dass Sie über grundlegende Kenntnisse zu Common Language Runtime (CLR) und objektorientiertem Programmieren sowie zur Konzeptualisierung der Beziehungen zwischen WPF-Elementen als Struktur verfügen. Um den Beispielen in diesem Thema zu folgen, sollten Sie zudem Extensible Application Markup Language (XAML) verstehen und wissen, wie sehr einfache WPF-Anwendungen oder -Seiten geschrieben werden. Weitere Informationen finden Sie unter Walkthrough: My First WPF Desktop Application (Exemplarische Vorgehensweise: Meine erste WPF-Desktop-Anwendung) und XAML in WPF.

Was ist ein Routingereignis?

Stellen Sie sich Routingereignisse entweder aus Sicht der Funktion oder der Implementierung vor. Beide Definitionen werden hier vorgestellt, da Benutzer die Definitionen unterschiedlich hilfreich finden.

Funktionale Definition: Ein Routingereignis ist ein Ereignis, das Ereignishandler auf mehreren Listenern in einer Elementstruktur aufrufen kann, und nicht nur auf dem Objekt, das das Ereignis ausgelöst hat.

Implementierungsdefinition: Ein Routingereignis ist ein CLR-Ereignis, das von einer Instanz der RoutedEvent-Klasse unterstützt wird und vom Windows Presentation Foundation- (WPF)-Ereignissystem verarbeitet wird.

Eine typische WPF-Anwendung enthält viele Elemente. Egal ob im Code erstellt oder in XAML deklariert, stehen diese Elemente in einer Beziehung einer Elementstruktur zueinander. Die Ereignisroute kann sich abhängig von der Ereignisdefinition in eine von zwei Richtungen bewegen; im Allgemeinen bewegt sie sich vom Quellelement aus und „steigt“ in der Elementstruktur auf, bis sie den Stamm der Elementstruktur erreicht (normalerweise eine Seite oder ein Fenster). Dieses Bubblingkonzept kommt Ihnen möglicherweise bekannt vor, wenn Sie schon einmal mit dem DHTML-Objektmodell gearbeitet haben.

Betrachten Sie die folgende einfache Elementstruktur:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

Diese Elementstruktur erzeugt in etwa Folgendes:

Schaltflächen „Ja“, „Nein“ und „Abbrechen“

In diesem vereinfachten Elementbaum ist die Quelle eines Click-Ereignisses eines der Button-Elemente, und das Button, auf das geklickt wurde, ist das erste Element, das die Möglichkeit hat, das Ereignis zu behandeln. Wenn jedoch kein an das Button angehängter Handler auf das Ereignis reagiert, wird das Ereignis nach oben zum übergeordneten Button-Element in der Elementstruktur weitergeleitet, d. h. zu StackPanel. Möglicherweise wird das Ereignis an das Border-Element weitergeleitet, und dann bis zum Seitenstamm der Elementstruktur (nicht dargestellt).

Mit anderen Worten, die Ereignisroute für dieses Click-Ereignis lautet:

Button-->StackPanel-->Border-->...

Szenarios auf oberster Ebene für Routingereignisse

Im folgenden wird eine kurze Zusammenfassung der Szenarios dargestellt, die das Konzept der Routingereignisse motiviert haben; außerdem wird erläutert, warum ein herkömmliches CLR-Ereignis für die folgenden Szenarios nicht ausreichend ist:

Zusammensetzung und Kapselung von Steuerelementen: Verschiedene Steuerelemente in WPF verfügen über ein umfangreiches Inhaltsmodell. Sie können z.B. ein Bild in ein Button-Element einfügen, wodurch die visuelle Struktur der Schaltfläche erweitert wird. Das hinzugefügte Bild darf jedoch nicht das Treffertestverhalten unterbrechen, bei dem eine Schaltfläche auf ein Click-Ereignis seines Inhalts reagiert, selbst wenn der Benutzer auf Pixel klickt, die genau genommen Teil des Bilds sind.

Einzelne Anfügepunkte für Handler: In Windows Forms müssen Sie den gleichen Handler mehrmals zur Verarbeitung der Ereignisse anfügen, die von mehreren Elementen ausgelöst werden können. Routingereignisse ermöglichen es Ihnen, diesen Handler nur einmal anzufügen, wie im vorherigen Beispiel gezeigt wurde, und verwenden Handlerlogik, um ggf. zu bestimmen, woher das Ereignis stammt. Dies kann z.B. der Handler für die zuvor gezeigte XAML sein:

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
  Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
  Select Case feSource.Name
    Case "YesButton"
      ' do something here ...
    Case "NoButton"
      ' do something ...
    Case "CancelButton"
      ' do something ...
  End Select
  e.Handled=True
End Sub

Klassenbehandlung: Routingereignisse akzeptieren einen statischen Handler, der von der Klasse definiert ist. Dieser Klassenhandler hat die Möglichkeit, ein Ereignis vor angefügten Instanzhandlern zu behandeln.

Verweisen auf ein Ereignis ohne Reflektion: Bestimmte Code- und Markuptechniken erfordern eine Möglichkeit, ein bestimmtes Ereignis zu identifizieren. Ein Routingereignis erstellt ein RoutedEvent-Feld als Bezeichner, der eine stabile Ereignisidentifikationstechnik bietet, die weder eine statische noch eine Laufzeitreflektion erfordert.

So werden Routingereignis implementiert

Ein Routingereignis ist ein CLR-Ereignis, das von einer Instanz der RoutedEvent-Klasse unterstützt und im WPF-Ereignissystem registriert wird. Die aus der Ereignisregistrierung abgerufene RoutedEvent-Instanz bleibt in der Regel als ein publicstaticreadonly-Feldmember der Klasse erhalten, die das Routingereignis registriert und somit dessen Besitzer ist. Die Verbindung mit dem identisch benannten CLR-Ereignis (das manchmal auch als „Wrapper“-Ereignis bezeichnet wird) erfolgt durch das Außerkraftsetzen der add- und remove-Implementierungen für das CLR-Ereignis. Normalerweise bleiben add und remove als impliziter Standard bestehen, der die entsprechende sprachspezifische Ereignissyntax zum Hinzufügen und Entfernen von Handlern dieses Ereignisses verwendet. Der Unterstützungs- und Verbindungsmechanismus des Routingereignisses ist vom Konzept her genauso wie eine Abhängigkeitseigenschaft eine CLR-Eigenschaft ist, die von der DependencyProperty-Klasse unterstützt und im WPF-Eigenschaftensystem registriert wird.

Das folgende Beispiel zeigt die Deklaration für ein benutzerdefiniertes Tap-Routingereignis, einschließlich der Registrierung und der Offenlegung des RoutedEvent-Bezeichnerfelds und der add- und remove-Implementierungen für das Tap-Ereignis.

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); }
        remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

Routingereignishandler und XAML

Um einen Handler für ein Ereignis mit XAML hinzuzufügen, deklarieren Sie den Ereignisnamen als Attribut auf dem Element, das ein Ereignislistener ist. Der Wert des Attributs ist der Name Ihrer implementierten Handlermethode, die in der partiellen Klasse der CodeBehind-Datei vorhanden sein muss.

<Button Click="b1SetColor">button</Button>

Die XAML-Syntax zum Hinzufügen von Standard-CLR-Ereignishandlern ist die gleiche wie für das Hinzufügen von Routingereignishandlern, da Sie genauso Handler in den CLR-Ereigniswrapper einfügen, dem eine Routingereignisimplementierung zu Grunde liegt. Weitere Informationen zum Hinzufügen von Ereignishandlern in XAML finden Sie unter Übersicht über XAML (WPF).

Routingstrategien

Routingereignisse verwenden eine von drei Routingstrategien:

  • Bubbling: Ereignishandler werden auf der Ereignisquelle aufgerufen. Das Routingereignis wird dann auf nachfolgenden übergeordnete Elemente weitergeleitet, bis es den Stamm der Elementstruktur erreicht. Die meisten Routingereignisse verwenden die Bubblingroutingstrategie. Bubblingroutingereignisse werden üblicherweise verwendet, um Eingabe- oder Zustandänderungen von unterschiedlichen Steuerelemente oder andere Elementen der Benutzeroberfläche zu melden.

  • Direkt: Nur das Quellelement selbst kann als Reaktion Handler aufrufen. Dies entspricht dem „Routing“, das Windows Forms für Ereignisse verwendet. Im Gegensatz zu einem Standard-CLR-Ereignis unterstützen direkt weitergeleitete Ereignisse jedoch die Behandlung von Klassen (die Behandlung von Klassen wird in einem der nächsten Abschnitte erläutert) und können von EventSetter und EventTrigger verwendet werden.

  • Tunneling: Zunächst werden Ereignishandler am Stamm der Elementstruktur aufgerufen. Das Routingereignis bewegt sich dann über eine Route durch die nachfolgenden untergeordneten Elemente in Richtung des Knotenelements, das die Quelle des Routingereignisses ist (das Element, das das Routingereignis ausgelöst). Tunnelingroutingereignisse werden oft als Teil der Zusammensetzung eines Steuerelements verwendet oder behandelt, sodass Elemente der einzelnen Teile von Ereignissen, die für das vollständige Steuerelement spezifisch sind, bewusst unterdrückt oder ersetzt werden können. Eingabeereignisse aus WPF sind oft als Tunneling/Bubbling-Paar implementiert. Tunnelingereignisse werden manchmal auch als Vorschauereignisse bezeichnet; dies liegt an einer Benennungskonvention für Paare.

Was ist der Vorteil von Routingereignissen?

Als Anwendungsentwickler müssen Sie nicht immer wissen oder sicherstellen, dass das Ereignis, das Sie behandeln, als Routingereignis implementiert wird. Weitergeleitete Ereignisse weisen ein besonderes Verhalten auf, aber dieses Verhalten ist weitestgehend unsichtbar, wenn Sie ein Ereignisses auf dem Element behandeln, in dem es ausgelöst wurde.

Routingereignisse erweisen sich besonders dort als sehr nützlich, wo Sie eines der folgenden Szenarios verwenden: beim Definieren von gängigen Handlern am gängigen Stamm, beim Zusammensetzen eines eigenen Steuerelements oder beim Definieren Ihrer eigenen, benutzerdefinierten Steuerelementklasse.

Routingereignislistener und Routingereignisquellen müssen kein gemeinsames Ereignis in ihrer Hierarchie aufweisen. Jedes UIElement oder ContentElement kann ein Ereignislistener für jedes weitergeleitete Ereignis sein. Daher können Sie den kompletten Satz an Routingereignissen, die durch die Arbeits-API zur Verfügung stehen, als konzeptionelle „Schnittstelle“ verwenden, über die unterschiedliche Elemente in der Anwendung Ereignisinformationen austauschen können. Dieses „Schnittstellen“-Konzept für Routingereignisse kann besonders auf Eingabeereignisse angewendet werden.

Routingereignisse können auch für die Kommunikation durch die Elementstruktur verwendet werden, da die Ereignisdaten für jedes Element auf der Route aufrechterhalten werden. Ein Element könnte die Ereignisdaten ändern, und diese Änderung wäre für das nächste Element auf der Route verfügbar.

Neben dem Routingaspekt gibt es zwei andere Gründe, aus denen ein beliebiges WPF-Ereignis möglicherweise als Routingereignis statt als CLR-Standardereignis implementiert wird. Wenn Sie Ihr eigenes Ereignis implementieren, können Sie auch folgende Prinzipien berücksichtigen:

  • Bestimmte WPF-Styling- und Schablonenfunktionen wie EventSetter und EventTrigger erfordern, dass das referenzierte Ereignis ein geroutetes Ereignis ist. Dies ist das Szenario mit einem Ereignisbezeichner, das weiter oben erwähnt wurde.

  • Routingereignisse unterstützen einen Mechanismus zur Klassenbehandlung, durch den die Klasse statische Methoden angeben kann, die die Möglichkeit haben, Routingereignisse zu behandeln, bevor registrierte Instanzenhandler darauf zugreifen können. Dies ist beim Entwerfen von Steuerelementen hilfreich, da Ihre Klasse ereignisgesteuertes Klassenverhalten erzwingen kann, das nicht versehentlich durch das Behandeln eines Ereignisses auf einer Instanz unterdrückt werden kann.

Jeder der oben genannten Aspekte wird in einem separaten Abschnitt dieses Themas erläutert.

Hinzufügen und implementieren eines Ereignishandlers für ein Routingereignis

Zum Hinzufügen eines Ereignishandlers in XAML müssen Sie einfach nur den Ereignisnamen als Attribut in ein Element einfügen und den Attributwert auf den Namen des Ereignishandlers festlegen, der einen angemessenen Delegaten implementiert, wie in folgendem Beispiel veranschaulicht.

<Button Click="b1SetColor">button</Button>

b1SetColor ist der Name des implementierten Handlers, der den Code für die Behandlung des Click-Ereignisses enthält. b1SetColor muss die gleiche Signatur wie der RoutedEventHandler-Delegat haben, der der Ereignisbehandlungsdelegat für das Click-Ereignis ist. Der erste Parameter aller Delegaten von Routingereignishandlern gibt das Element an, dem der Ereignishandler hinzugefügt wird, und der zweite Parameter gibt die Daten des Ereignisses an.

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
  'logic to handle the Click event
End Sub

RoutedEventHandler ist der grundlegende Delegat für geroutete Ereignishandler. Für Routingereignisse, die auf bestimmte Steuerelemente oder Szenarien ausgelegt sind, können die für die Routingereignishandler zu verwendenden Delegaten möglicherweise so spezialisiert werden, dass sie spezialisierte Ereignisdaten übertragen können. In einem üblichen Eingabeszenario könnten Sie beispielsweise ein DragEnter-Routingereignis verarbeiten. Ihr Handler sollte den DragEventHandler-Delegaten implementieren. Indem Sie den spezifischsten Delegaten verwenden, können Sie DragEventArgs im Handler verarbeiten und die Data-Eigenschaft lesen, die die Nutzlast der Zwischenablage des Ziehvorgangs enthält.

Ein vollständiges Beispiel zum Hinzufügen eines Ereignishandlers in ein Element mit XAML finden Sie unter Behandeln eines Routingereignisses.

Das Hinzufügen eines Handlers für ein Routingereignis in einer Anwendung, die im Code erstellt wird, ist einfach. Routingereignishandler können immer über eine Hilfsmethode hinzugefügt werden AddHandler (dies ist die gleiche Methode, die die vorhandenen Sicherungsaufrufe für add.) Vorhandene WPF-Routingereignisse verfügen jedoch in der Regel über Unterstützungsimplementierungen von add und remove-Logik, die es den Handlern ermöglichen, durch eine sprachspezifische Ereignissyntax hinzugefügt zu werden, was eine intuitivere Syntax als die Hilfsmethode ist. Im Folgenden wird die Hilfsmethode in einem Beispiel verwendet:

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event
 }
Private Sub MakeButton()
     Dim b2 As New Button()
     b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
 Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
     'logic to handle the Click event     
 End Sub

Das folgende Beispiel zeigt die C#-Operatorsyntax (Visual Basic hat eine geringfügig andere Operatorsyntax aufgrund seiner Behandlung von Dereferenzierung):

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event
}
Private Sub MakeButton2()
  Dim b2 As New Button()
  AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
  'logic to handle the Click event     
End Sub

Ein Beispiel für das Hinzufügen eines Ereignishandlers in Code finden Sie unter Hinzufügen eines Ereignishandlers mithilfe von Code.

Wenn Sie Visual Basic verwenden, können Sie auch das Schlüsselwort Handles verwenden, um Handler als Teil der Handlerdeklarationen hinzufügen. Weitere Informationen finden Sie unter Visual Basic- und WPF-Ereignisbehandlung.

Das Handled-Konzept

Alle Routingereignisse verwenden eine gemeinsame Ereignisdaten-Basisklasse, RoutedEventArgs. RoutedEventArgs definiert die Handled-Eigenschaft, die einen booleschen Wert annimmt. Der Zweck dieser Handled-Eigenschaft ist es, jedem Ereignisbehandler entlang der Route zu ermöglichen, das Routingereignis als behandelt zu markieren, indem der Wert von Handled auf true gesetzt wird. Nachdem die freigegebenen Ereignisdaten vom Handler an einem Element auf der Route verarbeitet wurden, werden die Daten wieder jedem Listener auf der Route gemeldet.

Der Wert von Handled wirkt sich darauf aus, wie ein Ereignis gemeldet oder verarbeitet wird, während es die Route durchläuft. Wenn Handled in den Ereignisdaten eines Routingereignisses true ist, werden Handler, die nach diesem Routingereignis auf anderen Elementen lauschen, im Allgemeinen für diese bestimmte Ereignisinstanz nicht mehr aufgerufen. Dies gilt sowohl für in XAML angefügte Handler als auch für Handler, die von anderen sprachspezifischen Anfügesyntaxen für Ereignishandlern, wie z. B. += oder Handles, hinzugefügt wurden. Durch das Festlegen vonHandled auf true markieren Sie ein Ereignis als „handled“; in den meisten gängigen Handlerszenarios wird dadurch das Routing für eine Tunneling- oder Bubbling-Route sowie für jedes andere Ereignis, das an einer Stelle auf der Route von einem Klassenhandler behandelt wird, „gestoppt“.

Allerdings gibt es den Mechanismus „handledEventsToo“, mit dem Listener Handler als Reaktion auf ein Routingereignis, in dem Handled in den Ereignisdaten true ist, immer noch ausführen können. Das heißt, ist die Ereignisroute nicht wirklich durch Markieren der Ereignisdaten als behandelt beendet. Sie können den HandledEventsToo-Mechanismus nur in Code oder in einer EventSetter verwenden:

  • Anstatt eine sprachspezifische Ereignissyntax zu verwenden, die für allgemeine CLR-Ereignisse funktioniert, rufen Sie im Code die WPF-Methode AddHandler(RoutedEvent, Delegate, Boolean) auf, um Ihren Handler hinzuzufügen. Legen Sie den Wert von handledEventsToo auf true fest.

  • In einem EventSetter, setzen Sie das HandledEventsToo-Attribut auf true.

Zusätzlich zu dem vom Handled-Zustand ausgelösten Verhalten in Routingereignissen hat das Konzept von Handled Auswirkungen darauf, wie Sie Ihre Anwendung entwerfen und den Ereignishandlercode schreiben sollten. Sie können sich das Handled als ein einfaches Protokoll vorstellen, das durch Routingereignisse offengelegt wird. Wie Sie dieses Protokoll genau verwenden, ist Ihnen überlassen, aber der konzeptuelle Entwurf, wie der Wert von Handled verwendet werden soll, ist wie folgt:

  • Wenn ein Routingereignis als behandelt markiert ist, muss es nicht von anderen Elementen auf dieser Route erneut verarbeitet werden.

  • Wenn ein Routingereignis nicht als behandelt markiert ist, haben andere Listener, die am Beginn der Route lagen, entweder beschlossen, einen Handler nicht zu registrieren, oder die Handler, die registriert wurden, haben beschlossen, die Ereignisdaten nicht zu manipulieren und Handled auf true festzulegen. (Oder es ist natürlich möglich, dass der aktuelle Listener der erste Punkt in der Route ist.) Handler für den aktuellen Listener verfügen jetzt über drei mögliche Handlungswege:

    • Führen Sie keine Aktion durch; das Ereignis bleibt unbehandelt und wird an den nächsten Listener weitergeleitet.

    • Führen Sie Code als Reaktion auf das Ereignis aus, aber seien Sie sich im Klaren, dass die Aktion nicht umfangreich genug war, um ein Markieren des Ereignisses als „handled“ zu rechtfertigen. Das Ereignis wird an den nächsten Listener weitergeleitet.

    • Führen Sie Code als Reaktion auf das Ereignis aus. Markieren Sie das Ereignis in den an den Handler übergebenen Ereignisdaten als „handled“, weil die ausgeführte Aktion umfangreich genug war, um das Markieren als „handled“ zu rechtfertigen. Das Ereignis wird immer noch an den nächsten Listener weitergeleitet, allerdings mit Handled=true in seinen Ereignisdaten, sodass nur handledEventsToo-Listener die Möglichkeit haben, weitere Handler aufzurufen.

Dieser konzeptuelle Entwurf wird vom oben genannten Routingverhalten unterstützt: Es ist schwieriger (aber immer noch in Code und Formaten möglich), Handler für Routingereignisse anzufügen, die aufgerufen werden, auch wenn ein vorheriger Handler auf der Route Handled bereits auf true festgelegt wurde.

Weitere Informationen über Handled, die Behandlung von gerouteten Ereignissen durch die Klasse und Empfehlungen dazu, wann es angebracht ist, ein geroutetes Ereignis als Handled, zu markieren, finden Sie unter Markieren von gerouteten Ereignissen als behandelt und Klassenbehandlung.

In Anwendungen ist es üblich, lediglich ein Bubblingroutingereignis auf dem Objekt zu behandeln, das es ausgelöst hat, ohne die Routingmerkmale des Ereignisses zu berücksichtigen. Allerdings empfiehlt es sich immer noch, das Routingereignis in den Ereignisdaten als „handled“ zu markieren, um unvorhergesehene Nebeneffekte zu verhindern, nur für den Fall, dass ein Element, das sich weiter oben in der Elementstruktur befindet, auch einen angefügten Handler für das gleiche Routingereignis aufweist.

Klassenhandler

Wenn Sie eine Klasse definieren, die sich in irgendeiner Art von DependencyObject ableitet, können Sie ebenfalls einen Klassenhandler für ein Routingereignis definieren und anfügen, das ein deklarierter oder geerbter Ereignismember Ihrer Klasse ist. Klassenhandler werden vor Handler für Instanzlistener, die an eine Instanz dieser Klasse angefügt sind, aufgerufen, immer dann, wenn ein Routingevent eine Elementinstanz auf seiner Route erreicht.

Einige WPF-Steuerelemente verfügen über inhärente Klassenbehandlung für bestimmte Routingereignisse. Dies kann nach außen den Anschein erwecken, als dass das Routingereignis nicht einmal ausgelöst wird, aber in Wirklichkeit wird es von einer Klasse behandelt, und das Routingereignis kann immer noch von Ihren Instanzhandlern behandelt werden, wenn Sie bestimmte Techniken verwenden. Außerdem machen viele Basisklassen und Steuerelemente virtuelle Methoden verfügbar, die zum Außerkraftsetzen von Klassenbehandlungsverhalten verwendet werden können. Weitere Informationen sowohl zum Umgehen unerwünschter Klassenbehandlung als auch zum Definieren Ihres eigenen Klassenverhaltens in einer benutzerdefinierten Klasse finden Sie unter Markieren von Routingereignissen als behandelt und Klassenbehandlung.

Angefügte Ereignisse in WPF

Die XAML-Sprache definiert auch eine besondere Art von Ereignissen, die als angefügte Ereignisse bezeichnet werden. Mit einem angefügten Ereignis können Sie einen Ereignishandler für ein bestimmtes Ereignis zu einem beliebigen Element hinzuzufügen. Das Element, das das Ereignis behandelt, muss das angefügte Ereignis weder definieren noch erben; zudem muss weder das Objekt, dass das Ereignis möglicherweise auslöst, noch die dafür zuständige Instanz dieses Ereignis als Klassenmember definieren oder „besitzen“.

Das WPF-Eingabesystem verwendet angefügte Ereignisse in großem Umfang. Fast alle dieser angefügten Ereignisse werden jedoch über Basiselemente weitergeleitet. Die Eingabeereignisse erscheinen dann als äquivalente nicht angefügte Routingereignisse, die Member der Basiselementklasse sind. Zum Beispiel kann das zugrundeliegende angehängte Mouse.MouseDownEreignis einfacher durch UIElement unter Verwendung von MouseDown bei diesem UIElement gehandhabt werden, anstatt sich mit der Syntax für angehängte Ereignisse entweder in XAML oder im Code zu beschäftigen.

Weitere Informationen zu angefügten Ereignissen in WPF finden Sie unter Übersicht über angefügte Ereignisse.

Qualifizierte Ereignisnamen in XAML

Wenn Sie Handler für Routingereignisse anfügen, die von untergeordneten Elementen ausgelöst werden, ist dies so ähnlich wie der Gebrauch einer typename.eventname angefügten Ereignissyntax, obwohl es sich dabei genau genommen nicht um den Gebrauch eines angefügten Ereignisses handelt. Fügen Sie die Handler an ein gemeinsames übergeordnetes Element an, um von Ereignisrouting zu profitieren, auch wenn das gemeinsame übergeordnete Element möglicherweise das relevante Routingereignis nicht als Member enthält. Schauen Sie sich dieses Beispiel erneut an:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

Hier ist der Listener des übergeordneten Elements, in dem der Handler hinzugefügt wird, eine StackPanel-Klasse. Es fügt jedoch einen Handler für ein geroutetes Ereignis hinzu, das von der Button-Klasse deklariert wurde und ausgelöst wird (eigentlich ButtonBase, aber durch Vererbung für Button verfügbar). Button „besitzt“ das Ereignis, aber das geroutete Ereignissystem erlaubt es, dass Handler für jedes geroutete Ereignis an jeden UIElement- oder ContentElement-Instanzlistener angehängt werden können, die ansonsten Listener für ein CLR-Ereignis (Common Language Runtime) anhängen könnten. Der Standard-xmlns-Namespace für diese qualifizierten Attributnamen ist in der Regel der Standard-WPF-xmlns-Namespace, aber Sie können auch Namespaces mit Präfix für benutzerdefinierte Routingereignisse angeben. Weitere Informationen zu xmlns finden Sie unter XAML-Namespaces und Namespacezuordnung für WPF-XAML.

Eingabeereignisse in WPF

Routingereignisse werden auf der WPF-Plattform häufig für Eingabeereignisse verwendet. In WPF erhalten Namen von Tunnelingroutingereignissen per Konvention das Wort „Preview“ (Vorschau) als Präfix. Eingabeereignisse treten oft paarweise auf – wobei das eine ein Bubblingereignis und das andere ein Tunnelingereignis ist. Zum Beispiel haben das KeyDown-Ereignis und das PreviewKeyDown-Ereignis die gleiche Signatur, wobei ersteres das sprudelnde Eingabeereignis und letzteres das tunnelnde Eingabeereignis ist. Manchmal haben Eingabeereignisse nur eine Bubblingversion oder nur eine direkte Routingversion. In der Dokumentation verweisen die Themen zu Routingereignissen auf ähnliche Routingereignisse mit alternativen Routingstrategien, falls derartige Routingereignisse vorhanden sind; Abschnitte auf den Seiten zu verwalteten Referenzen erläutern die Routingstrategien jedes Routingelements.

WPF-Eingabeereignisse, die paarweise auftreten, werden implementiert, damit eine einzelne Benutzereingabeaktion, wie z. B. das Drücken einer Maustaste, beide Routingereignisse des Paars in der Sequenz auslöst. Zunächst wird das Tunnelingereignis ausgelöst und durchläuft seine Route. Anschließend wird das Bubblingereignis ausgelöst und durchläuft seine Route. Beide Ereignisse haben die gleiche Instanz von Ereignisdaten, da der RaiseEvent-Methodenaufruf in der implementierenden Klasse, das Bubblingereignis auslöst, lauscht auf die Ereignisdaten des Tunnelingereignisses und verwendet sie im neuen, ausgelösten Ereignis wieder. Listener mit Handlern für das Tunnelingereignis haben als Erstes die Möglichkeit, das Routingereignis als „handled“ zu markieren (zuerst Klassenhandler, anschließend Instanzhandler). Wenn ein Element entlang der Tunnelingroute das Routingereignis als „handled“ markiert, werden die bereits behandelten Daten des Bubblingereignisses gesendet, und typische angefügte Handler für das entsprechende Bubblingeingabeereignis werden nicht aufgerufen. Nach außen wirkt dies so, als sei das behandelte Bubblingereignis nicht einmal ausgelöst worden. Dieses Behandlungsverhalten ist beim Zusammensetzen von Steuerelementen nützlich; hier möchten Sie möglicherweise, dass alle treffertestbasierten oder fokusbasierten Eingabeereignisse von Ihrem endgültigen Steuerelement gemeldet werden, und nicht von dessen einzelnen Komponenten. Das endgültige Steuerelement ist in der Zusammensetzung näher am Stamm, weshalb es das Tunnelingereignis zunächst in einer Klasse behandeln und eventuell sogar dieses Routingereignis durch ein steuerelementspezifischeres Ereignis „ersetzen“ kann – als Teil des Codes, der die Steuerelementklasse unterstützt.

Das folgende Beispiel für ein Eingabeereignis veranschaulicht das Verarbeiten von Eingabeereignissen. Im folgenden Baumdiagramm ist leaf element #2 sowohl die Quelle eines PreviewMouseDown- als auch eines MouseDown-Ereignisses.

Ereignisrouting-Diagramm

Ein Ereignis wird in folgender Reihenfolge verarbeitet:

  1. PreviewMouseDown (Tunnel) auf dem Stammelement (root element)

  2. PreviewMouseDown (Tunnel) auf dem ersten Zwischenelement (intermediate element #1)

  3. PreviewMouseDown (Tunnel) auf dem zweiten Zwischenelement (intermediate element #2)

  4. MouseDown (Bubble) auf dem zweiten Quellelement (source element #2)

  5. MouseDown (Bubble) auf dem ersten Zwischenelement (intermediate element #1)

  6. MouseDown (Bubble) auf dem Stammelement (root element)

Ein Delegat eines Routingereignishandlers enthält Verweise auf zwei Objekte: auf das Objekt, das das Ereignis ausgelöst hat und das Objekt, auf dem der Handler aufgerufen wurde. Das Objekt, auf dem der Handler aufgerufen wurde, ist das Objekt, das vom sender-Parameter gemeldet wurde. Das Objekt, auf dem das Ereignis zuerst ausgelöst wurde, wird von der Source-Eigenschaft in den Ereignisdaten gemeldet. Ein Ereignis kann dennoch vom gleichen Objekt ausgelöst und verarbeitet werden; in diesem Fall sind sender und Source identisch (dies gilt für Schritt 3 und 4 auf der Liste der Beispiele zur Ereignisverarbeitung).

Aufgrund von Tunneling und Bubbling erhalten übergeordnete Elemente Eingabeereignisse, in denen die Source-Eigenschaft eines ihrer untergeordneten Elemente ist. Wenn Sie wissen müssen, was das Quellelement ist, können Sie das Quellelement durch Zugriff auf die Source-Eigenschaft identifizieren.

Sobald das Eingabeereignis als Handled markiert ist, werden normalerweise keine weiteren Handler aufgerufen. In der Regel sollten Sie Eingabeereignisse als „handled“ markieren, sobald ein Handler aufgerufen wird, der Ihre anwendungsspezifische logische Behandlung der Bedeutung des Eingabeereignisses angeht.

Ausgenommen von dieser allgemeinen Aussage zum Handled-Zustand sind Eingabeereignishandler, die registriert werden, um den Handled-Zustand der Ereignisdaten bewusst zu ignorieren; diese werden trotzdem noch entlang jeder der beiden Routen aufgerufen. Weitere Informationen finden Sie unter Vorschauereignisse oder Markieren von Routingereignissen als behandelt und Klassenbehandlung.

Das freigegebene Ereignisdatenmodell von Tunneling- und Bubblingereignissen und das sequenzielle Auslösen zunächst der Tunneling-und dann der Bubblingereignisse ist kein allgemein gültiges Konzept für alle Routingereignisse. Dieses Verhalten wird insbesondere davon implementiert, wie WPF-Eingabegeräte Eingabeereignispaare auslösen und verbinden. Das Implementieren Ihres eigenen Eingabeereignisses ist ein erweitertes Szenario; möglicherweise entscheiden Sie sich aber dazu, dieses Modell auch für Ihre eigenen Eingabeereignisse zu nutzen.

Gewisse Klassen behandeln gewisse Eingabeereignisse in einer Klasse, meist mit dem Ziel, die Bedeutung eines bestimmten benutzergesteuerten Eingabeereignisses innerhalb dieses Steuerelements neu zu definieren und ein neues Ereignis auszulösen. Weitere Informationen finden Sie unter Markieren von Routingereignissen als behandelt und Klassenbehandlung.

Weitere Informationen zur Eingabe und zur Interaktion zwischen Eingabe und Ereignissen in normalen Anwendungsszenarien finden Sie unter Übersicht über die Eingabe.

EventSetters und EventTriggers

Sie können vordeklarierte XAML-Ereignisbehandlungssyntax in Formate im Markup mithilfe von EventSetter einfügen. Wenn das Format angewendet wird, wird der verwiesene Handler zur formatierten Instanz hinzugefügt. Sie können ein EventSetter nur für ein Routingereignis deklarieren. Im Folgenden finden Sie ein Beispiel. Beachten Sie, dass die b1SetColor-Methode, auf die hier verwiesen wird, sich in einer CodeBehind-Datei befindet.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

Der Vorteil hierbei ist, dass das Format wahrscheinlich viele weitere Informationen enthält, die auf eine beliebige Schaltfläche in der Anwendung angewendet werden können, und dass EventSetter als Teil dieses Formats die Wiederverwendung von Code fördert – sogar auf Markupebene. Darüber hinaus abstrahiert EventSetter Methodennamen für Handler, die sich einen Schritt von der allgemeinen Anwendung und dem Seitenmarkup entfernt befinden.

EventTrigger ist eine weitere spezialisierte Syntax, die das Routingereignis mit Animationsfunktionen von WPF kombiniert. Wie bei EventSetter, können nur Routingereignisse für ein EventTrigger verwendet werden. Normalerweise wird EventTrigger als Teil eines Stils deklariert, aber EventTrigger kann auch auf Seitenebene als Teil der Triggers-Sammlung oder in einer ControlTemplate deklariert werden. Ein EventTrigger ermöglicht es Ihnen, ein Storyboard anzugeben, das immer dann ausgeführt wird, wenn ein weitergeleitetes Ereignis ein Element in seiner Route erreicht, das ein EventTrigger für dieses Ereignis deklariert. Der Vorteil von EventTrigger gegenüber der einfachen Verarbeitung des Ereignisses und dem Starten eines vorhandenen Storyboards ist, dass EventTrigger eine bessere Kontrolle über das Storyboard und sein Laufzeitverhalten bietet. Weitere Informationen finden Sie unter Verwenden von Ereignistriggern zum Steuern eines Storyboards nach dessen Start.

Weitere Informationen zu Routingereignissen

In diesem Thema werden hauptsächlich die grundlegenden Konzepte von Routingereignissen erläutert; zudem bietet er Ihnen eine Orientierung, wann Sie auf Routingereignisse reagieren sollten, die bereits in den verschieden Basiselementen und -steuerelementen vorhanden sind. Allerdings können Sie eigene Routingereignis für Ihre benutzerdefinierte Klasse mit den notwendigen Hilfsmitteln, wie z. B. spezialisierte Ereignisdatenklassen und Delegaten, erstellen. Jede Klasse kann Eigentümer des Routingereignisses sein, aber die Routingereignisse müssen von abgeleiteten Klassen von UIElement oder ContentElement ausgelöst und behandelt werden, um von Nutzen zu sein. Weitere Informationen zu benutzerdefinierten Ereignissen finden Sie unter Erstellen eines benutzerdefinierten Routingereignisses.

Siehe auch