Événements en préversion (WPF .NET)

Les événements d’aperçu, également appelés événements de tunneling, sont des événements routés qui transitent vers le bas par le biais de l’arborescence d’éléments de l’élément racine de l’application vers l’élément qui a déclenché l’événement. L’élément qui déclenche un événement est signalé comme étant dans Source les données d’événement. Tous les scénarios d’événements ne prennent pas en charge ou nécessitent des événements en préversion. Cet article décrit l’emplacement des événements en préversion et la façon dont les applications ou les composants peuvent interagir avec eux. Pour plus d’informations sur la création d’un événement en préversion, consultez Comment créer un événement routé personnalisé.

Prérequis

L’article suppose une connaissance de base des événements routés et que vous avez lu la vue d’ensemble des événements routés. Pour suivre les exemples de cet article, il vous aide à connaître le langage XAML (Extensible Application Markup Language) et savoir comment écrire des applications Windows Presentation Foundation (WPF).

Événements en préversion marqués comme gérés

Soyez prudent lorsque vous marquez les événements d’aperçu comme gérés dans les données d’événement. Marquer un événement d’aperçu comme géré sur un élément autre que l’élément qui l’a déclenché peut empêcher l’élément qui l’a déclenché de gérer l’événement. Parfois, le marquage des événements d’aperçu comme gérés est intentionnel. Par exemple, un contrôle composite peut supprimer les événements déclenchés par des composants individuels et les remplacer par des événements déclenchés par le contrôle complet. Les événements personnalisés pour un contrôle peuvent fournir des données d’événement personnalisées et un déclencheur en fonction des relations d’état des composants.

Pour les événements d’entrée, les données d’événement sont partagées par les équivalents de préversion et de non-préversion (bubbling) de chaque événement. Si vous utilisez un gestionnaire de classes d’événements en préversion pour marquer un événement d’entrée comme géré, les gestionnaires de classes pour l’événement d’entrée de basculement ne seront généralement pas appelés. Sinon, si vous utilisez un gestionnaire d’instances d’événement en préversion pour marquer un événement comme géré, les gestionnaires d’instances pour l’événement d’entrée en cours d’envoi ne seront généralement pas appelés. Bien que vous puissiez configurer des gestionnaires de classes et d’instances à appeler même si un événement est marqué comme géré, cette configuration de gestionnaire n’est pas courante. Pour plus d’informations sur la gestion des classes et leur relation avec les événements d’aperçu, consultez Marquage des événements routés comme gérés et gestion des classes.

Remarque

Tous les événements d’aperçu ne sont pas des événements de tunneling . Par exemple, l’événement PreviewMouseLeftButtonDown d’entrée suit un itinéraire vers le bas par le biais de l’arborescence d’éléments, mais il s’agit d’un événement routé direct déclenché et réexéché par chacun UIElement dans l’itinéraire.

Contourner la suppression des événements par les contrôles

Certains contrôles composites suppriment les événements d’entrée au niveau du composant afin de les remplacer par un événement de haut niveau personnalisé. Par exemple, WPF ButtonBase marque l’événement MouseLeftButtonDown d’entrée de bouclage comme géré dans sa OnMouseLeftButtonDown méthode et déclenche l’événement Click . L’événement MouseLeftButtonDown et ses données d’événement continuent toujours le long de l’itinéraire de l’arborescence des éléments, mais, étant donné que l’événement est marqué comme Handled étant dans les données d’événement, seuls les gestionnaires configurés pour répondre aux événements gérés sont appelés.

Si vous souhaitez que d’autres éléments vers la racine de votre application gèrent un événement routé marqué comme géré, vous pouvez :

  • Attachez des gestionnaires en appelant la UIElement.AddHandler(RoutedEvent, Delegate, Boolean) méthode et en définissant le paramètre handledEventsToo sur true. Cette approche nécessite l’attachement du gestionnaire d’événements dans code-behind, après avoir obtenu une référence d’objet à l’élément auquel il sera attaché.

  • Si l’événement marqué comme géré est un événement de bulle, attachez des gestionnaires pour l’événement d’aperçu équivalent si disponible. Par exemple, si un contrôle supprime l’événement MouseLeftButtonDown , vous pouvez attacher un gestionnaire pour l’événement à la PreviewMouseLeftButtonDown place. Cette approche fonctionne uniquement pour les événements d’entrée d’élément de base qui implémentent à la fois des stratégies de tunneling et de routage de bubbling et partagent des données d’événement.

L’exemple suivant implémente un contrôle personnalisé rudimentaire nommé componentWrapper qui contient un TextBox. Le contrôle est ajouté à un StackPanel nom outerStackPanel.

<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>

Le componentWrapper contrôle écoute l’événement KeyDown de boublage déclenché par son TextBox composant chaque fois qu’une séquence de touches se produit. Sur cette occurrence, le componentWrapper contrôle :

  1. Marque l’événement KeyDown routé de bouclage comme géré pour le supprimer. Par conséquent, seul le outerStackPanel gestionnaire configuré dans le code-behind pour répondre aux événements gérés KeyDown est déclenché. Le outerStackPanel gestionnaire attaché en XAML pour KeyDown les événements n’est pas appelé.

  2. Déclenche un événement routé personnalisé nommé CustomKey, qui déclenche le outerStackPanel gestionnaire de l’événement CustomKey .

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

L’exemple illustre deux solutions de contournement pour obtenir l’événement routé KeyDown supprimé pour appeler un gestionnaire d’événements attaché au outerStackPanel:

  • Attachez un gestionnaire d’événements PreviewKeyDown au outerStackPanel. Étant donné qu’un événement routé d’entrée en préversion précède l’événement routé de bubbling équivalent, le PreviewKeyDown gestionnaire dans l’exemple s’exécute devant le KeyDown gestionnaire qui supprime à la fois les événements d’aperçu et de bouclage par le biais de leurs données d’événements partagées.

  • Attachez un gestionnaire d’événements KeyDown à l’aide outerStackPanel de la UIElement.AddHandler(RoutedEvent, Delegate, Boolean) méthode dans code-behind, avec le handledEventsToo paramètre défini sur true.

Remarque

Le marquage de l’aperçu ou des équivalents non préversion des événements d’entrée comme gérés sont les deux stratégies pour supprimer les événements déclenchés par les composants d’un contrôle. L’approche que vous utilisez dépend des exigences de votre application.

Voir aussi