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, valutare la possibilità di contrassegnare 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 i gestori configurati per rispondere agli eventi gestiti. In pratica, contrassegnare un evento indirizzato come gestito 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 dell'istanza gestiscono gli eventi indirizzati su oggetti o elementi XAML. I gestori di classe gestiscono un evento indirizzato a livello di classe e vengono richiamati prima che qualsiasi gestore dell'istanza risponda 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 classe. Questo articolo illustra i vantaggi e le potenziali insidie del contrassegno degli eventi indirizzati come gestiti, i diversi tipi di eventi indirizzati e i gestori eventi indirizzati e l'eliminazione degli eventi nei controlli compositi.

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

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 di 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 di proprietà che influiscono sulla rappresentazione visiva, la generazione di nuovi eventi e la gestione completa di un evento.
  • Le risposte non significative 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 Working around event suppression by controls .If you want to handle an event that was marked as handled by a control, see Working around event suppression by controls.

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

Anteprima e coppie di eventi indirizzati di bubbling

Le coppie di eventi indirizzati di anteprima e bubbling sono specifiche degli eventi di input. Diversi eventi di input implementano una coppia di eventi indirizzati di tunneling e bubbling , ad esempio PreviewKeyDown e KeyDown. Il Preview prefisso indica che l'evento di 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 fino all'elemento che ha generato l'evento indirizzato. I gestori eventi di anteprima collegati all'elemento radice dell'applicazione vengono richiamati per primi, seguiti dai gestori collegati agli elementi annidati successivi.
  2. Al termine dell'evento di anteprima, l'evento di bubbling associato passa 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 per primi, seguiti dai gestori collegati agli elementi padre successivi.

Gli eventi di anteprima e bubbling associati fanno parte dell'implementazione interna di diverse classi WPF che dichiarano e generano eventi indirizzati personalizzati. Senza l'implementazione interna a livello di classe, gli eventi indirizzati di anteprima e bubbling sono completamente separati e non condividono i dati degli eventi, indipendentemente dalla denominazione degli eventi. Per informazioni su come implementare eventi indirizzati di input di 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 il relativo evento di bubbling associato. Se un evento indirizzato bubbling viene contrassegnato come gestito, non influirà 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 di bubbling come gestite. Un evento di input di anteprima gestito non richiama i gestori eventi normalmente registrati per il resto della route di tunneling e l'evento di bubbling associato non verrà generato. Un evento di input bubbling gestito non richiama i gestori eventi normalmente registrati per il resto della route di bubbling.

Gestori eventi indirizzati a istanze e classi

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

  • Gestori eventi di classe statici, registrati chiamando il RegisterClassHandler metodo all'interno di un costruttore di classi statiche.
  • Eseguire l'override dei gestori eventi della classe, registrati eseguendo l'override dei metodi di evento virtuali della classe base. I metodi di evento virtuali della classe base esistono principalmente per gli eventi di input e hanno nomi che iniziano con Il nome dell'evento On e il<nome>> dell'evento OnPreview.<

Gestori eventi dell'istanza

Puoi 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 il collegamento dei gestori eventi comporta una chiamata al wrapper dell'evento CLR, anche il collegamento di gestori in XAML viene risolto in una AddHandler chiamata. Per gli eventi gestiti:

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

Nell'esempio XAML seguente viene aggiunto un controllo personalizzato denominato , che esegue il wrapping di un oggetto denominato componentWrappera un StackPanel oggetto denominato componentTextBoxouterStackPanel.TextBox Un gestore eventi dell'istanza per l'evento PreviewKeyDown viene associato all'oggetto utilizzando la sintassi degli componentWrapper attributi XAML. Di conseguenza, il gestore dell'istanza risponderà solo agli eventi di tunneling non gestiti PreviewKeyDown generati da 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 associa un gestore dell'istanza per l'evento KeyDown di bubbling all'oggetto componentWrapper utilizzando l'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 è illustrata nella sezione successiva.

Gestori eventi di classe statici

È possibile associare 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 classi statico per ogni evento indirizzato. Di conseguenza, possono essere richiamati più gestori di classi statici per lo stesso evento in qualsiasi nodo specificato nella route dell'evento. Quando viene costruita la route di eventi per l'evento, tutti i gestori di classe statici per ogni nodo vengono aggiunti alla route di eventi. L'ordine di chiamata dei gestori di classi statiche in un nodo inizia con il gestore di classi statiche più derivate, seguito da gestori di classi statiche di ogni classe base successiva.

I gestori eventi di classe statici registrati usando l'overload RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) con il handledEventsToo parametro impostato su true risponderanno sia agli eventi indirizzati non gestiti che gestiti.

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

L'esempio di codice seguente mostra 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 utilizzato 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 ComponentWrapperBase gestore della 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 della classe di override in questo esempio di codice è illustrata nella sezione successiva.

Eseguire l'override dei gestori eventi della classe

Alcune classi di base degli elementi visivi espongono metodi virtuali on<nome> evento on e nome> evento OnPreview<per ognuno dei relativi eventi di input indirizzati pubblici. Ad esempio, UIElement implementa i OnKeyDown gestori eventi virtuali e OnPreviewKeyDown e molti altri. È possibile eseguire l'override dei gestori eventi virtuali della classe base per implementare gestori eventi di classe 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 base è un modo più semplice per implementare i gestori di classi rispetto alla registrazione dei gestori di classi in un costruttore statico. All'interno dell'override, è possibile generare eventi, avviare una 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 della classe statica, 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 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 la chiamata all'implementazione di base solo 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 ComponentWrapper classe OnKeyDown base viene sottoposto a override nelle classi e ComponentWrapperBase . Poiché il sistema di eventi WPF richiama solo il gestore eventi della ComponentWrapper.OnKeyDown classe di override, tale gestore usa base.OnKeyDown(e) per chiamare il gestore eventi della ComponentWrapperBase.OnKeyDown classe di override, che a sua volta usa base.OnKeyDown(e) per chiamare il StackPanel.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 classi statico a cui è componentWrapper associato viene attivato dall'evento KeyDown indirizzato.
  3. Il gestore classi statico a cui è componentWrapperBase associato viene attivato dall'evento KeyDown indirizzato.
  4. Il gestore della classe di override associato a componentWrapper viene attivato dall'evento KeyDown indirizzato.
  5. Il gestore della classe di override associato a componentWrapperBase 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 altre informazioni o implica un comportamento più specifico. Un controllo composito è costituito da più controlli pratici o classi di base del 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. UIElement espone diversi Mouse eventi, MouseLeftButtonDown ad esempio e MouseRightButtonDown. UIElement implementa anche i metodi OnMouseLeftButtonDown virtuali vuoti e OnMouseRightButtonDown come gestori di classi pre-registrati. 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

Talvolta 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 dell'attributo 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 i 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 bubbling, collegare i gestori per l'evento di anteprima abbinato, se disponibile. Ad esempio, se un controllo elimina l'evento MouseLeftButtonDown , è possibile associare un gestore per l'evento PreviewMouseLeftButtonDown . Questo approccio funziona solo per le coppie di eventi di input di anteprima e di bubbling, che condividono i dati dell'evento. Prestare attenzione a non contrassegnare l'oggetto PreviewMouseLeftButtonDown come gestito perché 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 by controls.

Vedi anche