Übersicht über angefügte Ereignisse (WPF .NET)

Extensible Application Markup Language (XAML) definiert eine Sprachkomponente und einen Ereignistyp, der als angefügtes Ereignis bezeichnet wird. Angefügte Ereignisse können verwendet werden, um ein neues Routingereignis in einer Nicht-Elementklasse zu definieren und dieses Ereignis für jedes Element in der Struktur auszulösen. Dazu müssen Sie das angefügte Ereignis als Routingereignis registrieren und spezifischen unterstützenden Code bereitstellen, die angefügte Ereignisfunktionen unterstützen. Da angefügte Ereignisse als Routingereignisse registriert werden, wenn sie für ein Element ausgelöst werden, erfolgt die Verteilung über die Elementstruktur.

Wichtig

Der Desktopleitfaden zu .NET 7 und .NET 6 ist in Bearbeitung.

Voraussetzungen

In diesem Artikel wird davon ausgegangen, dass Sie über grundlegende Kenntnisse über Routingereignisse von Windows Presentation Foundation (WPF) verfügen und die Artikel Übersicht über Routingereignisse und XAML in WPF gelesen haben. Um den Beispielen in diesem Artikel zu folgen, ist es hilfreich, wenn Sie mit XAML vertraut sind und wissen, wie WPF-Anwendungen geschrieben werden.

Syntax der angefügten Ereignisse

In der XAML-Syntax wird ein angefügtes Ereignis durch den Ereignisnamen und seinen Besitzertyp in Form von <owner type>.<event name> angegeben. Da der Ereignisname mit dem Namen des Besitzertyps qualifiziert ist, ermöglicht die Syntax, dass das Ereignis an jedes Element angefügt werden kann, für das eine Instanziierung möglich ist. Diese Syntax ist auch auf Handler für reguläre Routingereignisse anwendbar, die an ein beliebiges Element entlang der Ereignisroute angefügt werden.

Die folgende XAML-Attributsyntax fügt den AquariumFilter_Clean-Handler für das angefügte Ereignis AquariumFilter.Clean an das aquarium1-Element an:

<aqua:Aquarium x:Name="aquarium1" Height="300" Width="400" aqua:AquariumFilter.Clean="AquariumFilter_Clean"/>

In diesem Beispiel ist das Präfix aqua: erforderlich, da die Klassen AquariumFilter und Aquarium in einem anderen CLR-Namespace (Common Language Runtime) und einer anderen Assembly vorhanden sind.

Sie können auch Handler für angefügte Ereignisse in CodeBehind anfügen. Rufen Sie dazu die Methode AddHandler für das Objekt auf, an das der Ereignishandler angefügt werden soll, und übergeben Sie den Ereignisbezeichner und den Handler als Parameter an die Methode.

So implementiert WPF angefügte Ereignisse

WPF-angefügte Ereignisse werden als Routingereignisse implementiert, die von einem RoutedEvent-Feld unterstützt werden. Folglich werden angefügte Ereignisse in der Elementstruktur verteilt, nachdem sie ausgelöst wurden. Im Allgemeinen handelt es sich bei dem Objekt, das das angefügte Ereignis auslöst (die Ereignisquelle), um eine System- oder Dienstquelle. System- oder Dienstquellen sind nicht direkt Teil der Elementstruktur. Bei anderen angefügten Ereignissen kann es sich bei der Ereignisquelle um ein Element in der Struktur handeln, z. B. eine Komponente innerhalb eines zusammengesetzten Steuerelements.

Szenarien für angefügte Ereignisse

In WPF werden angefügte Ereignisse in bestimmten Featurebereichen verwendet, in denen es eine Abstraktion auf Dienstebene gibt. Beispielsweise verwendet WPF angefügte Ereignisse, die von den statischen Mouse- oder Validation-Klassen aktiviert werden. Klassen, die mit einem Dienst interagieren oder diesen verwenden, können entweder mit einem Ereignis interagieren, indem sie die Syntax für angefügte Ereignisse verwenden, oder das angefügte Ereignis als Routingereignis darstellen. Die letztere Option ist Teil der Art und Weise, wie eine Klasse die Funktionen des Diensts integrieren könnte.

Das WPF-Eingabesystem verwendet angefügte Ereignisse in großem Umfang. Allerdings werden fast alle dieser angefügten Ereignisse als äquivalente nicht angefügte Routingereignisse über Basiselemente angezeigt. Jedes Routingereignis ist ein Mitglied der Basiselementklasse und wird durch einen CLR-Ereigniswrapper unterstützt. Sie werden angefügte Ereignisse nur selten direkt verwenden oder behandeln. So ist es beispielsweise einfacher, das zugrunde liegende angefügte Mouse.MouseDown-Ereignis für ein UIElement über das entsprechende UIElement.MouseDown-Routingereignis zu behandeln als mithilfe der angefügten Ereignissyntax in XAML oder CodeBehind.

Angefügte Ereignisse dienen der Architektur, indem sie eine zukünftige Erweiterung der Eingabegeräte ermöglichen. Beispielsweise müsste ein neues Eingabegerät nur Mouse.MouseDown auslösen, um die Mauseingabe zu simulieren, und es müsste dafür nicht von Mouse abgeleitet werden. Dieses Szenario umfasst die Codebehandlung für das Ereignis, da die XAML-Behandlung des angefügten Ereignisses nicht relevant wäre.

Behandeln eines angefügten Ereignisses

Das Verfahren zur Codierung und Bearbeitung eines angefügten Ereignisses ist im Grunde dasselbe wie bei einem nicht angefügten Routingereignis.

Wie zuvor erwähnt, sind die vorhandenen WPF-angefügten Ereignisse in der Regel nicht dafür konzipiert, direkt in WPF verarbeitet zu werden. Häufiger ist der Zweck eines angefügten Ereignisses, dass ein Element innerhalb eines zusammengesetzten Steuerelements seinen Zustand an ein übergeordnetes Element innerhalb des Steuerelements melden kann. In diesem Szenario wird das Ereignis im Code ausgelöst und hängt von der Klassenbehandlung in der entsprechenden übergeordneten Klasse ab. Beispielsweise wird von Elementen in einem Selector erwartet, dass sie das angefügte Selected-Ereignis auslösen, das dann von der Selector-Klasse behandelt wird. Die Selector-Klasse konvertiert das Selected-Ereignis potenziell in das SelectionChanged-Routingereignis. Weitere Informationen über Routingereignisse und Klassenbehandlung finden Sie unter Markieren von Routingereignissen als behandelt und Klassenbehandlung.

Definieren eines benutzerdefinierten angefügten Ereignisses

Wenn Sie von allgemeinen WPF-Basisklassen ableiten, können Sie Ihr eigenes angefügtes Ereignis implementieren, indem Sie zwei Zugriffsmethoden in Ihre Klasse einbeziehen. Diese Methoden sind:

  • Eine Add<event name>Handler-Methode mit einem ersten Parameter, der das Element darstellt, an das der Ereignishandler angefügt wird, und einem zweiten Parameter, der der hinzuzufügende Ereignishandler ist. Die Methode muss public und static sein, ohne Rückgabewert. Die Methode ruft die Methode der Basisklasse AddHandler auf und übergibt das Routingereignis und den Handler als Argumente. Diese Methode unterstützt die XAML-Attributsyntax zum Anfügen eines Ereignishandlers an ein Element. Diese Methode ermöglicht auch den Codezugriff auf den Speicher des Ereignishandlers für das angefügte Ereignis.

  • Eine Remove<event name>Handler-Methode mit einem ersten Parameter, der das Element darstellt, an das der Ereignishandler angefügt wird, und einem zweiten Parameter, der der zu entfernende Ereignishandler ist. Die Methode muss public und static sein, ohne Rückgabewert. Die Methode ruft die Methode der Basisklasse RemoveHandler auf und übergibt das Routingereignis und den Handler als Argumente. Diese Methode ermöglicht den Codezugriff auf den Speicher des Ereignishandlers für das angefügte Ereignis.

WPF implementiert angefügte Ereignisse als Routingereignisse, da der Bezeichner für eine RoutedEvent-Instanz vom WPF-Ereignissystem definiert wird. Außerdem ist das Routing eines Ereignisses eine natürliche Erweiterung des Konzepts eines angefügten Ereignisses auf XAML-Sprachebene. Diese Implementierungsstrategie beschränkt die Behandlung von angefügten Ereignissen entweder von UIElement abgeleitete Klasse oder von ContentElement abgeleitete Klassen, da nur diese Klassen AddHandler-Implementierungen aufweisen.

Der folgende Code definiert z. B. das an Clean angefügte Ereignis für die Besitzerklasse AquariumFilter, bei der es sich nicht um eine Elementklasse handelt. Der Code definiert das angefügte Ereignis als Routingereignis und implementiert die erforderlichen Zugriffsmethoden.

public class AquariumFilter
{
    // Register a custom routed event using the bubble routing strategy.
    public static readonly RoutedEvent CleanEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));

    // Provide an add handler accessor method for the Clean event.
    public static void AddCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.AddHandler(CleanEvent, handler);
    }

    // Provide a remove handler accessor method for the Clean event.
    public static void RemoveCleanHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
    {
        if (dependencyObject is not UIElement uiElement)
            return;

        uiElement.RemoveHandler(CleanEvent, handler);
    }
}
Public Class AquariumFilter

    ' Register a custom routed event using the bubble routing strategy.
    Public Shared ReadOnly CleanEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        "Clean", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(AquariumFilter))

    ' Provide an add handler accessor method for the Clean event.
    Public Shared Sub AddCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[AddHandler](CleanEvent, handler)
        End If
    End Sub

    ' Provide a remove handler accessor method for the Clean event.
    Public Shared Sub RemoveCleanHandler(dependencyObject As DependencyObject, handler As RoutedEventHandler)
        Dim uiElement As UIElement = TryCast(dependencyObject, UIElement)

        If uiElement IsNot Nothing Then
            uiElement.[RemoveHandler](CleanEvent, handler)
        End If
    End Sub

End Class

Die RegisterRoutedEvent-Methode, die den angefügten Ereignisbezeichner zurückgibt, ist dieselbe Methode, mit der nicht angefügte Routingereignisse registriert werden. Sowohl angefügte als auch nicht angefügte Routingereignisse werden in einem zentralen internen Speicher registriert. Diese Implementierung des Ereignisspeichers ermöglicht das Konzept „Ereignisse als Schnittstelle“, das in der Übersicht über Routingereignisse beschrieben wird.

Anders als der CLR-Ereigniswrapper, der zur Unterstützung von nicht angefügten Routingereignissen verwendet wird, können die Zugriffsmethoden für angefügte Ereignisse in Klassen implementiert werden, die nicht von UIElement oder ContentElement abgeleitet sind. Dies ist möglich, da der angefügte Ereignissicherungscode die Methoden UIElement.AddHandler und UIElement.RemoveHandler für eine übergebene UIElement-Instanz aufruft. Im Gegensatz dazu ruft der CLR-Wrapper für nicht angefügte Routingereignisse diese Methoden direkt für die besitzende Klasse auf, sodass die Klasse von UIElement abgeleitet werden muss.

Auslösen eines WPF-angefügten Ereignisses

Das Verfahren zum Auslösen eines angefügten Ereignisses ist im Wesentlichen dasselbe wie bei einem nicht angefügten Routingereignis.

In der Regel muss Ihr Code keine vorhandenen WPF-definierten angefügten Ereignisse auslösen, da diese Ereignisse dem allgemeinen konzeptuellen Modell für „Dienst“ folgen. In diesem Modell sind Dienstklassen wie InputManager für das Auslösen von WPF-definierten angefügten Ereignissen verantwortlich.

Wenn Sie ein benutzerdefiniertes angefügtes Ereignis mithilfe des WPF-Modells definieren, bei dem angefügte Ereignisse auf Routingereignissen basieren, verwenden Sie die Methode UIElement.RaiseEvent, um ein angefügtes Ereignis bei einem beliebigen UIElement oder ContentElement auszulösen. Beim Auslösen eines Routingereignisses, unabhängig davon, ob es angefügt ist oder nicht, müssen Sie ein Element in der Elementstruktur als Ereignisquelle festlegen. Diese Quelle wird dann als RaiseEvent-Aufrufer gemeldet. Gehen Sie wie folgt vor, um z. B. das an AquariumFilter.Clean angefügte Routingereignis für aquarium1 auszulösen:

aquarium1.RaiseEvent(new RoutedEventArgs(AquariumFilter.CleanEvent));
aquarium1.[RaiseEvent](New RoutedEventArgs(AquariumFilter.CleanEvent))

Im vorherigen Beispiel ist aquarium1 die Ereignisquelle.

Siehe auch