Contrassegnare gli eventi indirizzati come gestiti e la gestione delle classi (WPF .NET)

Anche se non esiste alcuna regola assoluta per quando contrassegnare un evento indirizzato come gestito, prendere in considerazione la contrassegnatura di un evento come gestito se il codice risponde all'evento in modo significativo. Un evento indirizzato contrassegnato come gestito continuerà lungo la route, ma vengono richiamati solo gestori configurati per rispondere agli eventi gestiti. Fondamentalmente, contrassegnando un evento instradato come limita la visibilità ai listener lungo la route dell'evento.

I gestori eventi indirizzati possono essere gestori di istanze o gestori di classi. I gestori di istanze gestiscono gli eventi indirizzati sugli oggetti o sugli elementi XAML. I gestori di classi gestiscono un evento indirizzato a livello di classe e vengono richiamati prima di qualsiasi gestore di istanze che risponde allo stesso evento in qualsiasi istanza della classe. Quando gli eventi indirizzati vengono contrassegnati come gestiti, vengono spesso contrassegnati come tali all'interno dei gestori di classi. Questo articolo illustra i vantaggi e le potenziali insodenze dei contrassegni degli eventi indirizzati come gestiti, i diversi tipi di eventi indirizzati e gestori eventi indirizzati e l'eliminazione degli eventi nei controlli compositi.

Importante

La documentazione di Desktop Guide per .NET 6 e .NET 5 (inclusa .NET Core 3.1) è in fase di costruzione.

Prerequisiti

L'articolo presuppone una conoscenza di base degli eventi indirizzati e che sono stati letti la 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).

Quando contrassegnare gli eventi indirizzati come gestiti

In genere, un solo gestore deve fornire una risposta significativa per ogni evento indirizzato. Evitare di usare il sistema eventi indirizzato per fornire una risposta significativa tra più gestori. La definizione di ciò che costituisce una risposta significativa è soggettiva e dipende dall'applicazione. Indicazioni generali:

  • Le risposte significative includono l'impostazione dello stato attivo, la modifica dello stato pubblico, l'impostazione delle proprietà che influiscono sulla rappresentazione visiva, la generazione di nuovi eventi e la gestione completa di un evento.
  • Le risposte insignificanti includono la modifica dello stato privato senza impatto visivo o programmatico, la registrazione degli eventi e l'analisi dei dati degli eventi senza rispondere all'evento.

Alcuni controlli WPF eliminano gli eventi a livello di componente che non richiedono ulteriore gestione contrassegnandoli come gestiti. Se si vuole gestire un evento contrassegnato come gestito da un controllo, vedere Utilizzo dell'eliminazione degli eventi dai controlli.

Per contrassegnare un evento come gestito, impostare il valore della proprietà nei dati dell'evento Handled su true. Anche se è possibile ripristinare tale valore in false, la necessità di farlo dovrebbe essere rara.

Anteprima e bubbling di coppie di eventi indirizzate

Le coppie di eventi in anteprima e bubbling indirizzate sono specifiche degli eventi di input. Diversi eventi di input implementano una coppia di eventi di tunneling e bubbling indirizzata, ad esempio PreviewKeyDown e KeyDown. Il Preview prefisso indica che l'evento bubbling viene avviato al termine dell'evento di anteprima. Ogni coppia di eventi di anteprima e bubbling condivide la stessa istanza dei dati dell'evento.

I gestori eventi indirizzati vengono richiamati in un ordine che corrisponde alla strategia di routing di un evento:

  1. L'evento di anteprima passa dall'elemento radice dell'applicazione all'elemento che ha generato l'evento indirizzato. I gestori eventi di anteprima collegati all'elemento radice dell'applicazione vengono richiamati prima, seguiti dai gestori collegati agli elementi nidificati successivi.
  2. Al termine dell'evento di anteprima, l'evento bubbling abbinato viaggia dall'elemento che ha generato l'evento indirizzato all'elemento radice dell'applicazione. I gestori eventi Bubbling collegati allo stesso elemento che ha generato l'evento indirizzato vengono richiamati prima, seguiti dai gestori collegati agli elementi padre successivi.

Gli eventi di anteprima abbinati e bubbling fanno parte dell'implementazione interna di diverse classi WPF che dichiarano e generano eventi indirizzati. Senza l'implementazione interna a livello di classe, l'anteprima e il bubbling degli eventi sono completamente separati e non condivideranno i dati degli eventi, indipendentemente dalla denominazione degli eventi. Per informazioni su come implementare eventi instradati di input bubbling o tunneling in una classe personalizzata, vedere Creare un evento indirizzato personalizzato.

Poiché ogni coppia di eventi di anteprima e bubbling condivide la stessa istanza dei dati dell'evento, se un evento indirizzato in anteprima viene contrassegnato come gestito, verrà gestito anche l'evento bubbling associato. Se un evento instradato bubbling viene contrassegnato come gestito, non influisce sull'evento di anteprima abbinato perché l'evento di anteprima è stato completato. Prestare attenzione quando si contrassegnano le coppie di eventi di input di anteprima e bubbling come gestite. Un evento di input di anteprima gestito non richiama normalmente gestori eventi registrati per il resto della route di tunneling e l'evento bubbling associato non verrà generato. Un evento di input bubbling gestito non richiama normalmente gestori eventi registrati per il resto della route bubbling.

Gestori eventi indirizzati a istanze e classi

I gestori eventi indirizzati possono essere gestori di istanze o gestori di classi . I gestori di classi per una determinata classe vengono richiamati prima di qualsiasi gestore dell'istanza che risponde allo stesso evento in qualsiasi istanza di tale classe. A causa di questo comportamento, quando gli eventi indirizzati vengono contrassegnati come gestiti, spesso vengono contrassegnati come tali all'interno dei gestori di classi. Esistono due tipi di gestori di classi:

Gestori eventi dell'istanza

È possibile collegare gestori di istanze a oggetti o elementi XAML chiamando direttamente il AddHandler metodo . Gli eventi indirizzati WPF implementano un wrapper di eventi CLR (Common Language Runtime) che usa il AddHandler metodo per collegare gestori eventi. Poiché la sintassi degli attributi XAML per l'associazione dei gestori eventi comporta una chiamata al wrapper dell'evento CLR, anche i gestori di collegamento in XAML vengono risolti in una AddHandler chiamata. Per gli eventi gestiti:

  • I gestori collegati usando la sintassi degli attributi XAML o la firma comune di AddHandler non vengono richiamati.
  • I gestori collegati usando l'overload con il AddHandler(RoutedEvent, Delegate, Boolean)handledEventsToo parametro impostato su true vengono richiamati. Questo overload è disponibile per i rari casi in cui è necessario rispondere agli eventi gestiti. Ad esempio, alcuni elementi in un albero di elementi hanno contrassegnato un evento come gestito, ma altri elementi lungo la route evento devono rispondere all'evento gestito.

L'esempio XAML seguente aggiunge un controllo personalizzato denominato , che esegue il wrapping di un oggetto denominato componentWrapper, a un StackPanel oggetto denominato componentTextBoxouterStackPanel.TextBox Un gestore eventi di istanza per l'evento è collegato alla sintassi dell'attributo PreviewKeyDowncomponentWrapper XAML. Di conseguenza, il gestore dell'istanza risponderà solo agli PreviewKeyDown eventi di tunneling non gestiti generati dall'oggetto componentTextBox.

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

Il MainWindow costruttore collega un gestore di istanze per l'evento bubbling all'uso KeyDowncomponentWrapper dell'overload UIElement.AddHandler(RoutedEvent, Delegate, Boolean) , con il handledEventsToo parametro impostato su true. Di conseguenza, il gestore eventi dell'istanza risponderà agli eventi non gestiti e gestiti.

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

        // Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
            handledEventsToo: true);
    }

    // The handler attached to componentWrapper in XAML.
    public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => 
        Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()

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

    ' The handler attached to componentWrapper in XAML.
    Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
        InstanceEventInfo(sender, e)
    End Sub

End Class

L'implementazione code-behind di ComponentWrapper viene visualizzata nella sezione successiva.

Gestori eventi di classe statici

È possibile collegare gestori eventi di classe statici chiamando il RegisterClassHandler metodo nel costruttore statico di una classe. Ogni classe in una gerarchia di classi può registrare il proprio gestore di classi statiche per ogni evento indirizzato. Di conseguenza, è possibile richiamare più gestori di classi statici per lo stesso evento in qualsiasi nodo specificato nella route evento. Quando viene creata la route evento per l'evento, tutti i gestori di classi statici per ogni nodo vengono aggiunti alla route evento. L'ordine di chiamata dei gestori di classi statiche in un nodo inizia con il gestore della classe statica più derivata, seguito dai gestori di classi statici da ogni classe di base successiva.

Gestori eventi di classe statici registrati usando l'overload con il RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean)handledEventsToo parametro impostato per risponderà sia agli true eventi indirizzati non gestiti che gestiti.

I gestori di classi statici vengono in genere registrati per rispondere solo agli eventi non gestiti. In tal caso, se un gestore di classi derivate in un nodo contrassegna un evento come gestito, i gestori di classi di base per tale evento non verranno richiamati. In questo scenario, il gestore della classe di base viene sostituito in modo efficace dal gestore della classe derivata. I gestori di classi di base spesso contribuiscono a controllare la progettazione in aree quali aspetto visivo, logica di stato, gestione degli input e gestione dei comandi, quindi prestare attenzione alla sostituzione. Gestori di classi derivati che non contrassegnano un evento come gestito terminano a integrare i gestori della classe di base anziché sostituirli.

Nell'esempio di codice seguente viene illustrata la gerarchia di classi per il ComponentWrapper controllo personalizzato a cui si fa riferimento nel codice XAML precedente. La classe ComponentWrapper deriva dalla classe ComponentWrapperBase, che a sua volta deriva dalla classe StackPanel. Il RegisterClassHandler metodo, usato nel costruttore statico delle ComponentWrapper classi e ComponentWrapperBase , registra un gestore eventi di classe statico per ognuna di queste classi. Il sistema di eventi WPF richiama il ComponentWrapper gestore della classe statica prima del gestore della ComponentWrapperBase classe statica.

public class ComponentWrapper : ComponentWrapperBase
{
    static ComponentWrapper()
    {
        // Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfo_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfo_Override(this, e);

        // Call the base OnKeyDown implementation on ComponentWrapperBase.
        base.OnKeyDown(e);
    }
}

public class ComponentWrapperBase : StackPanel
{
    // Class event handler implemented in the static constructor.
    static ComponentWrapperBase()
    {
        EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfoBase_Override(this, e);

        e.Handled = true;
        Debug.WriteLine("The KeyDown routed event is marked as handled.");

        // Call the base OnKeyDown implementation on StackPanel.
        base.OnKeyDown(e);
    }
}
Public Class ComponentWrapper
    Inherits ComponentWrapperBase

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfo_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfo_Override(Me, e)

        ' Call the base OnKeyDown implementation on ComponentWrapperBase.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Public Class ComponentWrapperBase
    Inherits StackPanel

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfoBase_Override(Me, e)

        e.Handled = True
        Debug.WriteLine("The KeyDown event is marked as handled.")

        ' Call the base OnKeyDown implementation on StackPanel.
        MyBase.OnKeyDown(e)
    End Sub

End Class

L'implementazione code-behind dei gestori eventi di override in questo esempio di codice viene illustrata nella sezione successiva.

Eseguire l'override dei gestori eventi di classe

Alcune classi di base degli elementi visivi espongono metodi virtuali di nome> Onevent vuoti e OnPreviewevent<>per ognuno dei relativi eventi di input indirizzati pubblici.< Ad esempio, UIElement implementa i OnKeyDown gestori eventi virtuali e OnPreviewKeyDown molti altri. È possibile eseguire l'override dei gestori eventi virtuali della classe di base per implementare gestori eventi di override per le classi derivate. Ad esempio, è possibile aggiungere un gestore classi di override per l'evento in qualsiasi UIElement classe derivata eseguendo l'override DragEnter del OnDragEnter metodo virtuale. L'override dei metodi virtuali della classe di base è un modo più semplice per implementare gestori di classi rispetto alla registrazione dei gestori di classi in un costruttore statico. All'interno dell'override è possibile generare eventi, avviare la logica specifica della classe per modificare le proprietà degli elementi nelle istanze, contrassegnare l'evento come gestito o eseguire altre logiche di gestione degli eventi.

A differenza dei gestori eventi di classe statici, il sistema di eventi WPF richiama solo gestori eventi di classe di override per la classe più derivata in una gerarchia di classi. La classe più derivata in una gerarchia di classi può quindi usare la parola chiave di base per chiamare l'implementazione di base del metodo virtuale. Nella maggior parte dei casi, è necessario chiamare l'implementazione di base, indipendentemente dal fatto che si contrassegni un evento come gestito. È consigliabile omettere di chiamare l'implementazione di base se la classe ha un requisito per sostituire la logica di implementazione di base, se presente. Se si chiama l'implementazione di base prima o dopo l'override del codice dipende dalla natura dell'implementazione.

Nell'esempio di codice precedente, il metodo virtuale della classe OnKeyDown base viene sottoposto a override sia nelle classi che ComponentWrapperBase nelle ComponentWrapper classi. Poiché il sistema di eventi WPF richiama solo il gestore eventi di override della classe, tale gestore usa per chiamare il ComponentWrapper.OnKeyDown gestore eventi della classe di override, che a sua volta usa base.OnKeyDown(e)base.OnKeyDown(e) per chiamare il StackPanel.OnKeyDownComponentWrapperBase.OnKeyDown metodo virtuale. L'ordine degli eventi nell'esempio di codice precedente è:

  1. Il gestore dell'istanza a cui componentWrapper è associato viene attivato dall'evento PreviewKeyDown indirizzato.
  2. Il gestore della classe statico a cui componentWrapper è associato viene attivato dall'evento KeyDown indirizzato.
  3. Il gestore della classe statico a cui componentWrapperBase è associato viene attivato dall'evento KeyDown indirizzato.
  4. Il gestore della classe di componentWrapper override a cui è associato viene attivato dall'evento KeyDown indirizzato.
  5. Il gestore della classe di componentWrapperBase override a cui è associato viene attivato dall'evento KeyDown indirizzato.
  6. L'evento KeyDown indirizzato viene contrassegnato come gestito.
  7. Il gestore dell'istanza a cui componentWrapper è associato viene attivato dall'evento KeyDown indirizzato. Il gestore è stato registrato con il handledEventsToo parametro impostato su true.

Eliminazione degli eventi di input nei controlli compositi

Alcuni controlli compositi eliminano gli eventi di input a livello di componente per sostituirli con un evento di alto livello personalizzato che contiene più informazioni o implica un comportamento più specifico. Un controllo composito è costituito da più controlli pratici o classi di base di controllo. Un esempio classico è il Button controllo , che trasforma vari eventi del mouse in un Click evento indirizzato. La Button classe base è ButtonBase, che deriva indirettamente da UIElement. Gran parte dell'infrastruttura di eventi necessaria per l'elaborazione dell'input di controllo è disponibile a UIElement livello di . UIElement espone diversi Mouse eventi, ad MouseLeftButtonDown esempio e MouseRightButtonDown. UIElement implementa anche i metodi OnMouseLeftButtonDown virtuali vuoti e OnMouseRightButtonDown come gestori di classi preregisterati. ButtonBase esegue l'override di questi gestori di classe e all'interno del gestore di override imposta la Handled proprietà su true e genera un Click evento. Il risultato finale per la maggior parte dei listener è che gli MouseLeftButtonDown eventi e MouseRightButtonDown sono nascosti e l'evento di alto livello Click è visibile.

Uso dell'eliminazione degli eventi di input

A volte l'eliminazione di eventi all'interno di singoli controlli può interferire con la logica di gestione degli eventi nell'applicazione. Ad esempio, se l'applicazione usa la sintassi degli attributi XAML per associare un gestore per l'evento nell'elemento MouseLeftButtonDown radice XAML, tale gestore non verrà richiamato perché il Button controllo contrassegna l'evento MouseLeftButtonDown come gestito. Se si desidera che gli elementi verso la radice dell'applicazione vengano richiamati per un evento indirizzato gestito, è possibile:

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

  • Se l'evento contrassegnato come gestito è un evento di input di bubbling, collegare i gestori per l'evento di anteprima abbinata, se disponibile. Ad esempio, se un controllo elimina l'evento MouseLeftButtonDown , è possibile allegare un gestore per l'evento PreviewMouseLeftButtonDown . Questo approccio funziona solo per le coppie di eventi di input di anteprima e bubbling, che condividono i dati degli eventi. Prestare attenzione a non contrassegnare l'oggetto PreviewMouseLeftButtonDown come gestito perché ciò elimina completamente l'evento Click .

Per un esempio di come aggirare l'eliminazione degli eventi di input, vedere Working around event suppression by controls .For an example of how to work around input event suppression, see Working around event suppression by controls.

Vedi anche