Eventi di anteprima (WPF .NET)

Gli eventi di anteprima, noti anche come eventi di tunneling, sono eventi indirizzati che attraversano verso il basso l'albero degli elementi dall'elemento radice dell'applicazione all'elemento che ha generato l'evento. L'elemento che genera un evento viene segnalato come nell'oggetto nei dati dell'evento Source . Non tutti gli scenari di eventi supportano o richiedono eventi di anteprima. Questo articolo descrive dove esistono eventi di anteprima e come le applicazioni o i componenti possono interagire con essi. Per informazioni su come creare un evento di anteprima, vedere Come creare un evento indirizzato personalizzato.

Importante

La documentazione di Desktop Guide per .NET 7 e .NET 6 è in fase di costruzione.

Prerequisiti

L'articolo presuppone una conoscenza di base degli eventi indirizzati e di aver letto panoramica degli eventi indirizzati. Per seguire gli esempi in questo articolo, è utile se si ha familiarità con Extensible Application Markup Language (XAML) e si sa come scrivere applicazioni Windows Presentation Foundation (WPF).

Eventi di anteprima contrassegnati come gestiti

Prestare attenzione quando si contrassegnano gli eventi di anteprima come gestiti nei dati dell'evento. Contrassegnando un evento di anteprima come gestito su un elemento diverso dall'elemento che lo ha generato può impedire all'elemento che lo ha generato di gestire l'evento. A volte contrassegnare gli eventi di anteprima come gestiti è intenzionale. Ad esempio, un controllo composito potrebbe eliminare gli eventi generati dai singoli componenti e sostituirli con gli eventi generati dal controllo completo. Gli eventi personalizzati per un controllo possono fornire dati e trigger di eventi personalizzati in base alle relazioni di stato del componente.

Per gli eventi di input, i dati degli eventi vengono condivisi sia dall'anteprima che dagli equivalenti non di anteprima (bubbling) di ogni evento. Se si usa un gestore della classe di evento di anteprima per contrassegnare un evento di input come gestito, i gestori di classe per l'evento di input di bubbling in genere non verranno richiamati. In alternativa, se si usa un gestore dell'istanza dell'evento di anteprima per contrassegnare un evento come gestito, i gestori di istanza per l'evento di input di bubbling in genere non verranno richiamati. Sebbene sia possibile configurare gestori di classi e istanze da richiamare anche se un evento è contrassegnato come gestito, tale configurazione del gestore non è comune. Per altre informazioni sulla gestione delle classi e sulla relativa correlazione con gli eventi di anteprima, vedere Contrassegnare gli eventi indirizzati come gestiti e la gestione delle classi.

Nota

Non tutti gli eventi di anteprima sono eventi di tunneling . Ad esempio, l'evento PreviewMouseLeftButtonDown di input segue una route verso il basso attraverso l'albero degli elementi, ma è un evento indirizzato diretto che viene generato e generato da ognuno UIElement nella route.

Uso dell'eliminazione degli eventi da parte dei controlli

Alcuni controlli compositi eliminano gli eventi di input a livello di componente per sostituirli con un evento di alto livello personalizzato. Ad esempio, WPF ButtonBase contrassegna l'evento MouseLeftButtonDown di input di bubbling come gestito nel relativo OnMouseLeftButtonDown metodo e genera l'evento Click . L'evento MouseLeftButtonDown e i relativi dati dell'evento continuano ancora lungo la route dell'albero degli elementi, ma poiché l'evento è contrassegnato come Handled nei dati dell'evento, vengono richiamati solo i gestori configurati per rispondere agli eventi gestiti.

Se si desidera che altri elementi verso la radice dell'applicazione gestisca un evento indirizzato contrassegnato come gestito, è possibile:

  • Collegare i gestori chiamando il UIElement.AddHandler(RoutedEvent, Delegate, Boolean) metodo e impostando il parametro handledEventsToo su true. Questo approccio richiede l'associazione del gestore eventi nel code-behind, dopo aver ottenuto un riferimento a un oggetto all'elemento a cui verrà associato.

  • Se l'evento contrassegnato come gestito è un evento di bubbling, collegare gestori per l'evento di anteprima equivalente, se disponibile. Ad esempio, se un controllo elimina l'evento MouseLeftButtonDown , è possibile associare un gestore per l'evento PreviewMouseLeftButtonDown . Questo approccio funziona solo per gli eventi di input degli elementi di base che implementano strategie di routing di tunneling e bubbling e condividono i dati degli eventi.

Nell'esempio seguente viene implementato un controllo personalizzato rudimentale denominato componentWrapper che contiene un oggetto TextBox. Il controllo viene aggiunto a un StackPanel oggetto denominato 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>

Il componentWrapper controllo è in ascolto dell'evento KeyDown di bubbling generato dal relativo TextBox componente ogni volta che si verifica una sequenza di tasti. In tale occorrenza, il componentWrapper controllo :

  1. Contrassegna l'evento KeyDown indirizzato di bubbling come gestito per eliminarlo. Di conseguenza, viene attivato solo il outerStackPanel gestore configurato nel code-behind per rispondere agli eventi gestitiKeyDown . Il outerStackPanel gestore collegato in XAML per KeyDown gli eventi non viene richiamato.

  2. Genera un evento indirizzato di bubbling personalizzato denominato CustomKey, che attiva il outerStackPanel gestore per l'evento 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

Nell'esempio vengono illustrate due soluzioni alternative per ottenere l'evento indirizzato eliminato KeyDown per richiamare un gestore eventi associato a outerStackPanel:

  • Collegare un PreviewKeyDown gestore eventi all'oggetto outerStackPanel. Poiché un evento indirizzato di input di anteprima precede l'evento indirizzato bubbling equivalente, il PreviewKeyDown gestore nell'esempio viene eseguito prima del KeyDown gestore che elimina gli eventi di anteprima e di bubbling tramite i dati degli eventi condivisi.

  • Collegare un KeyDown gestore eventi a outerStackPanel usando il UIElement.AddHandler(RoutedEvent, Delegate, Boolean) metodo nel code-behind, con il handledEventsToo parametro impostato su true.

Nota

Contrassegnare gli equivalenti di anteprima o non di anteprima degli eventi di input gestiti sono entrambe le strategie per eliminare gli eventi generati dai componenti di un controllo. L'approccio usato dipende dai requisiti dell'applicazione.

Vedi anche