Vorschauereignisse (WPF .NET)

Vorschauereignisse, auch als Tunnelingereignisse bezeichnet, sind Routingereignisse, die die Elementstruktur vom Stammelement der Anwendung aus zum auslösenden Element des Ereignisses abwärts durchlaufen. Das Element, das ein Ereignis auslöst, wird als Source in den Ereignisdaten gemeldet. Nicht alle Ereignisszenarien unterstützen oder erfordern Vorschauereignisse. In diesem Artikel wird beschrieben, wo Vorschauereignisse auftreten und wie Anwendungen oder Komponenten mit ihnen interagieren können. Informationen zum Erstellen eines Vorschauereignisses finden Sie unter Gewusst wie: Erstellen eines benutzerdefinierten Routingereignisses.

Wichtig

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

Voraussetzungen

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

Als behandelt gekennzeichnete Vorschauereignisse

Erwägen Sie sorgfältig, ob Sie Vorschauereignisse in Ereignisdaten als behandelt markieren. Ein Vorschauereigniss auf einem anderen Element als dem Element, das es ausgelöst hat, als behandelt zu markieren, kann verhindern, dass das Element, das es ausgelöst hat, das Ereignis behandelt. Manchmal ist es beabsichtigt, Vorschauereignisse als behandelt zu markieren. Ein zusammengesetztes Steuerelement kann z. B. Ereignisse unterdrücken, die von einzelnen Komponenten ausgelöst werden, und sie durch Ereignisse ersetzen, die vom vollständigen Steuerelement ausgelöst werden. Benutzerdefinierte Ereignisse für ein Steuerelement können angepasste Ereignisdaten enthalten und basierend auf Statusbeziehungen von Komponenten auslösen.

Bei Eingabeereignissen werden Ereignisdaten sowohl durch die Vorschau- als auch durch Nichtvorschauäquivalente (Bubbling) jedes Ereignisses bereitgestellt. Wenn Sie einen Vorschauereignis-Klassenhandler verwenden, um ein Eingabeereignis als behandelt zu markieren, werden normalerweise keine Klassenhandler für das Bubbling-Eingabeereignis aufgerufen. Wenn Sie hingegen einen Vorschauereignis-Instanzhandler verwenden, um ein Ereignis als behandelt zu markieren, werden normalerweise keine Instanzhandler für das Bubbling-Eingabeereignis aufgerufen. Obwohl Sie einstellen können, dass Klassen- und Instanzhandler auch aufgerufen werden sollen, wenn ein Ereignis als behandelt gekennzeichnet ist, ist dies unüblich. Weitere Informationen zur Klassenbehandlung und ihrem Zusammenhang mit Vorschauereignissen finden Sie unter Markieren von Routingereignissen als behandelt und Klassenbehandlung.

Hinweis

Nicht alle Vorschauereignisse sind Tunnelingereignisse. Beispielsweise folgt das Eingabeereignis PreviewMouseLeftButtonDown einer Abwärtsroute durch die Elementstruktur, ist jedoch ein direktes Routingereignis, das von jedem UIElement in der Route ausgelöst und erneut ausgelöst wird.

Umgehen der Ereignisunterdrückung von Steuerelementen

Einige zusammengesetzte Steuerelemente unterdrücken Eingabeereignisse auf Komponentenebene, um sie durch ein angepasstes Ereignis auf hoher Ebene zu ersetzen. Beispielsweise markiert WPF-ButtonBase das Bubbling-Eingabeereignis MouseLeftButtonDown in seiner OnMouseLeftButtonDown-Methode als behandelt und löst das Click-Ereignis aus. Das MouseLeftButtonDown-Ereignis und seine Ereignisdaten bewegen sich weiterhin entlang der Elementstrukturroute, da das Ereignis jedoch in den Ereignisdaten als Handled gekennzeichnet ist, werden nur Handler aufgerufen, die für die Reaktion auf behandelte Ereignisse konfiguriert sind.

Wenn Sie möchten, dass andere Elemente in nahe des Stamms Ihrer Anwendung ein routingfähiges Ereignis behandeln, das als behandelt gekennzeichnet ist, haben Sie folgende Möglichkeiten:

  • Fügen Sie Handler an, indem Sie die Methode UIElement.AddHandler(RoutedEvent, Delegate, Boolean) aufrufen und den Parameter handledEventsToo auf true setzen. Bei diesem Ansatz muss der Ereignishandler im CodeBehind angefügt werden, nachdem ein Objektverweis auf das Element abgerufen wurde, an das er angefügt wird.

  • Wenn das als behandelt markierte Ereignis ein Bubbling-Ereignis ist, fügen Sie Handler für das entsprechende Vorschauereignis an, sofern verfügbar. Wenn beispielsweise ein Steuerelement das MouseLeftButtonDown-Ereignis unterdrückt, können Sie stattdessen einen Handler für das PreviewMouseLeftButtonDown-Ereignis anfügen. Dieser Ansatz funktioniert nur für Basiselement-Eingabeereignisse, die sowohl Tunneling- als auch Bubbling-Routingstrategien implementieren und Ereignisdaten bereitstellen.

Im folgenden Beispiel wird ein rudimentäres benutzerdefiniertes Steuerelement namens componentWrapper implementiert, das eine TextBox enthält. Das Steuerelement wird einem StackPanel namens outerStackPanel hinzugefügt.

<StackPanel Name="outerStackPanel"
    VerticalAlignment="Center"
    custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
    TextBox.KeyDown="Handler_PrintEventInfo"
    TextBox.PreviewKeyDown="Handler_PrintEventInfo" >
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.KeyDown="ComponentWrapper_KeyDown"
        custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" KeyDown="Handler_PrintEventInfo" />
    </custom:ComponentWrapper>
</StackPanel>

Das componentWrapper-Steuerelement lauscht auf das KeyDown-Bubbling-Ereignis, das von seiner TextBox-Komponente ausgelöst wird, wenn eine Taste betätigt wird. Tritt dies ein, führt das componentWrapper-Steuerelement Folgendes aus:

  1. Markiert das KeyDown-Bubblingroutingereignis als behandelt, um es zu unterdrücken. Daher wird nur der outerStackPanel-Handler im CodeBehind ausgelöst, der konfiguriert ist, um auf behandelteKeyDown Ereignisse zu reagieren. Der in XAML für KeyDown-Ereignisse angefügte outerStackPanel-Handler wird nicht aufgerufen.

  2. Löst ein benutzerdefiniertes Bubblingroutingereignis namens CustomKey aus, das den outerStackPanel-Handler für das CustomKey-Ereignis auslöst.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
        outerStackPanel.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler_PrintEventInfo), 
            handledEventsToo: true);
    }

    private void ComponentWrapper_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        Handler_PrintEventInfo(sender, e);

        Debug.WriteLine("KeyDown event marked as handled on componentWrapper.\r\n" +
            "CustomKey event raised on componentWrapper.");

        // Mark the event as handled.
        e.Handled = true;

        // Raise the custom click event.
        componentWrapper.RaiseCustomRoutedEvent();
    }

    private void Handler_PrintEventInfo(object sender, System.Windows.Input.KeyEventArgs e)
    {
        string senderName = ((FrameworkElement)sender).Name;
        string sourceName = ((FrameworkElement)e.Source).Name;
        string eventName = e.RoutedEvent.Name;
        string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";

        Debug.WriteLine($"Handler attached to {senderName} " +
            $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
    }

    private void Handler_PrintEventInfo(object sender, RoutedEventArgs e)
    {
        string senderName = ((FrameworkElement)sender).Name;
        string sourceName = ((FrameworkElement)e.Source).Name;
        string eventName = e.RoutedEvent.Name;
        string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";

        Debug.WriteLine($"Handler attached to {senderName} " +
            $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
    }

    // Debug output:
    //
    // Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
    // Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
    // Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
    // KeyDown event marked as handled on componentWrapper.
    // CustomKey event raised on componentWrapper.
    // Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
    // Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
    // Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
}

public class ComponentWrapper : StackPanel
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent CustomKeyEvent = 
        EventManager.RegisterRoutedEvent(
            name: "CustomKey",
            routingStrategy: RoutingStrategy.Bubble,
            handlerType: typeof(RoutedEventHandler),
            ownerType: typeof(ComponentWrapper));

    // Provide CLR accessors for assigning an event handler.
    public event RoutedEventHandler CustomKey
    {
        add { AddHandler(CustomKeyEvent, value); }
        remove { RemoveHandler(CustomKeyEvent, value); }
    }

    public void RaiseCustomRoutedEvent()
    {
        // Create a RoutedEventArgs instance.
        RoutedEventArgs routedEventArgs = new(routedEvent: CustomKeyEvent);

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }
}
Partial Public Class MainWindow
        Inherits Window

        Public Sub New()
        InitializeComponent()

        ' Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
        outerStackPanel.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf Handler_PrintEventInfo),
                                     handledEventsToo:=True)
    End Sub

    Private Sub ComponentWrapper_KeyDown(sender As Object, e As KeyEventArgs)
        Handler_PrintEventInfo(sender, e)
        Debug.WriteLine("KeyDown event marked as handled on componentWrapper." &
                        vbCrLf & "CustomKey event raised on componentWrapper.")

        ' Mark the event as handled.
        e.Handled = True

        ' Raise the custom click event.
        componentWrapper.RaiseCustomRoutedEvent()
    End Sub

    Private Sub Handler_PrintEventInfo(sender As Object, e As KeyEventArgs)
        Dim senderName As String = CType(sender, FrameworkElement).Name
        Dim sourceName As String = CType(e.Source, FrameworkElement).Name
        Dim eventName As String = e.RoutedEvent.Name
        Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
        Debug.WriteLine($"Handler attached to {senderName} " &
                        $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
    End Sub

    Private Sub Handler_PrintEventInfo(sender As Object, e As RoutedEventArgs)
        Dim senderName As String = CType(sender, FrameworkElement).Name
        Dim sourceName As String = CType(e.Source, FrameworkElement).Name
        Dim eventName As String = e.RoutedEvent.Name
        Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
        Debug.WriteLine($"Handler attached to {senderName} " &
                        $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
    End Sub

    ' Debug output
    '
    ' Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
    ' Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
    ' Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
    ' KeyDown event marked as handled on componentWrapper.
    ' CustomKey event raised on componentWrapper.
    ' Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
    ' Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
    ' Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
End Class

    Public Class ComponentWrapper
        Inherits StackPanel

        ' Register a custom routed event with the Bubble routing strategy.
        Public Shared ReadOnly CustomKeyEvent As RoutedEvent =
            EventManager.RegisterRoutedEvent(
                name:="CustomKey",
                routingStrategy:=RoutingStrategy.Bubble,
                handlerType:=GetType(RoutedEventHandler),
                ownerType:=GetType(ComponentWrapper))

        ' Provide CLR accessors to support event handler assignment.
        Public Custom Event CustomKey As RoutedEventHandler

            AddHandler(value As RoutedEventHandler)
                [AddHandler](CustomKeyEvent, value)
            End AddHandler

            RemoveHandler(value As RoutedEventHandler)
                [RemoveHandler](CustomKeyEvent, value)
            End RemoveHandler

            RaiseEvent(sender As Object, e As RoutedEventArgs)
                [RaiseEvent](e)
            End RaiseEvent

        End Event

    Public Sub RaiseCustomRoutedEvent()
        ' Create a RoutedEventArgs instance & raise the event,
        ' which will bubble up through the element tree.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=CustomKeyEvent)
            [RaiseEvent](routedEventArgs)
        End Sub
    End Class

Das Beispiel veranschaulicht zwei Workarounds zum Abrufen des unterdrückten Routingereignisses KeyDown, sodass ein Ereignishandler aufgerufen wird, der an das outerStackPanel angefügt ist:

  • Anfügen eines PreviewKeyDown-Ereignishandlers an das outerStackPanel. Da ein Vorschaueingaberoutingereignis dem entsprechenden Bubblingroutingereignis vorangestellt ist, wird der PreviewKeyDown-Handler im Beispiel vor dem KeyDown-Handler ausgeführt, der sowohl Vorschau- als auch Bubblingereignisse über ihre gemeinsamen Ereignisdaten unterdrückt.

  • Anfügen eines KeyDown-Ereignishandlers an das outerStackPanel mithilfe der UIElement.AddHandler(RoutedEvent, Delegate, Boolean)-Methode im CodeBehind an, wobei der handledEventsToo-Parameter auf true festgelegt ist.

Hinweis

Vorschau- oder Nichtvorschauäquivalenten von Eingabeereignissen als behandelt zu markieren sind beides Strategien zum Unterdrücken von Ereignissen, die von den Komponenten eines Steuerelements ausgelöst werden. Der von Ihnen verwendete Ansatz hängt von Ihren Anwendungsanforderungen ab.

Siehe auch