Condividi tramite


Contrassegnare gli eventi instradati 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 instradato contrassegnato come gestito continuerà lungo il suo percorso, ma vengono richiamati solo i gestori configurati per rispondere agli eventi già gestiti. In pratica, contrassegnare un evento indirizzato come gestito ne limita la visibilità agli ascoltatori lungo il percorso dell'evento.

I gestori di eventi indirizzati possono essere gestori di istanza o gestori di classe. I gestori dell'istanza gestiscono gli eventi instradati su oggetti o elementi XAML. I gestori di classe gestiscono un evento instradato 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 instradati vengono indicati come gestiti, spesso ciò avviene all'interno dei gestori di classe. Questo articolo illustra i vantaggi e le potenziali insidie della marcatura degli eventi indirizzati come gestiti, i diversi tipi di eventi indirizzati e di gestori di eventi indirizzati, e la soppressione degli eventi nei controlli compositi.

Prerequisiti

L'articolo presuppone una conoscenza di base degli eventi routati e che si sia letto panoramica degli eventi routati. 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 instradati per fornire una risposta significativa per gestire più eventi. La definizione di ciò che costituisce una risposta significativa è soggettiva e dipende dall'applicazione. Come indicazioni generali:

  • Le risposte significative includono impostare lo stato attivo, modificare lo stato pubblico, impostare proprietà che influiscono sulla rappresentazione visiva, generare nuovi eventi e gestire completamente 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. Per gestire un evento contrassegnato come gestito da un controllo, consultare Come aggirare la soppressione degli eventi da parte dei controlli.

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

Anteprima e coppie di eventi indirizzati di bubbling

di anteprima e le coppie di eventi indirizzati bubbling sono specifiche per gli eventi di input . Diversi eventi di input implementano una di tunneling e coppia di eventi indirizzati, ad esempio e . Il prefisso Preview indica che l'evento di bubbling viene avviato una volta completato l'evento di anteprima. Ogni coppia di eventi di anteprima e bubbling condivide la stessa istanza dei dati dell'evento.

I gestori degli eventi instradati 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. Alla fine dell'evento di anteprima, l'evento di bubbling accoppiato passa dall'elemento che ha generato l'evento instradato all'elemento radice dell'applicazione. I gestori di eventi di bubbling collegati allo stesso elemento che ha generato l'evento instradato 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 'preview' e 'bubbling' sono completamente separati e non condividono i dati degli eventi, indipendentemente dal nome degli eventi. Per informazioni su come implementare eventi routing di input bubbling o tunneling in una classe personalizzata, vedere Creare un evento routing 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 corrispondente perché la gestione dell'anteprima è già stata completata. 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 di eventi normalmente registrati per il resto del percorso del bubbling.

Gestori di eventi instradati per istanze e classi

I gestori eventi indirizzati possono essere gestori di istanza o gestori di classe. 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 di eventi dell'istanza

Puoi collegare gestori di istanze a oggetti o elementi XAML chiamando direttamente il metodo AddHandler. Gli eventi indirizzati WPF implementano un wrapper di eventi CLR (Common Language Runtime) che usa il metodo AddHandler per collegare i gestori eventi. Poiché la sintassi dell'attributo XAML per l'associazione dei gestori eventi comporta una chiamata al wrapper dell'evento CLR, persino l'associazione dei gestori in XAML risulta in una chiamata AddHandler. 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 parametro handledEventsToo impostato su true. Questo overload è disponibile nei 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 il percorso dell'evento devono rispondere all'evento gestito.

L'esempio XAML seguente aggiunge un controllo personalizzato denominato componentWrapper, che avvolge un TextBox denominato componentTextBox, a un StackPanel denominato outerStackPanel. Un gestore eventi dell'istanza per l'evento PreviewKeyDown viene associato al componentWrapper usando la sintassi dell'attributo XAML. Di conseguenza, il gestore dell'istanza risponderà solo agli eventi di tunneling PreviewKeyDown non gestiti generati dal 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 costruttore MainWindow collega un gestore di istanze per l'evento di bubbling KeyDown a componentWrapper utilizzando l'overload UIElement.AddHandler(RoutedEvent, Delegate, Boolean), con il parametro handledEventsToo impostato su true. Di conseguenza, il gestore degli eventi dell'istanza risponderà agli eventi sia gestiti che non 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 metodo RegisterClassHandler 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.

Gestori eventi di classe statici registrati usando l'overload RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) con il parametro handledEventsToo impostato su true risponderanno sia agli eventi routed non gestiti sia a quelli 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 delle classi di base spesso contribuiscono alla progettazione del controllo in aree quali l'aspetto visivo, la logica di stato, la gestione degli input e la gestione dei comandi, quindi è importante essere cauti nel sostituirli. 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 controllo personalizzato ComponentWrapper 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 metodo RegisterClassHandler, utilizzato nel costruttore statico delle classi ComponentWrapper e ComponentWrapperBase, registra un gestore eventi di classe statico per ognuna di queste classi. Il sistema di eventi WPF richiama il gestore della classe statica ComponentWrapper prima del gestore della classe statica ComponentWrapperBase.

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 del codice sottostante dei gestori di eventi della classe di override in questo esempio di codice è illustrata nella prossima sezione.

Sovrascrivere i gestori di eventi della classe

Alcune classi di base degli elementi visivi espongono metodi virtuali On<nome evento> e OnPreview<nome evento> per ciascuno dei loro eventi di input instradavati pubblici. Ad esempio, UIElement implementa i gestori eventi virtuali OnKeyDown 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 DragEnter in qualsiasi classe derivata UIElement eseguendo l'override del metodo virtuale OnDragEnter. 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 di eventi delle classi statiche, il sistema di eventi WPF richiama solo i gestori di eventi di override per la classe più derivata all'interno di 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. Che si chiami l'implementazione di base prima o dopo il proprio codice di override dipende dalla natura della propria implementazione.

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

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

Eliminazione degli eventi di input nei controlli compositi

Alcuni controlli compositi eliminano 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 controllo Button, che trasforma vari eventi del mouse in un evento Click indirizzato. La classe base Button è ButtonBase, che deriva indirettamente da UIElement. Gran parte dell'infrastruttura di eventi necessaria per l'elaborazione degli input di controllo è disponibile a livello di UIElement. UIElement espone diversi eventi Mouse, ad esempio MouseLeftButtonDown e MouseRightButtonDown. UIElement implementa anche i metodi virtuali vuoti OnMouseLeftButtonDown e OnMouseRightButtonDown come gestori di classi preregistrati. ButtonBase esegue l'override di questi gestori di classe e all'interno del gestore di override imposta la proprietà Handled su true e genera un evento Click. Il risultato finale per la maggior parte dei listener è che gli eventi MouseLeftButtonDown e MouseRightButtonDown sono nascosti e l'evento Click di alto livello è visibile.

Gestione dell'aggiramento della soppressione 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 MouseLeftButtonDown nell'elemento radice XAML, tale gestore non verrà richiamato perché il controllo Button 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 metodo UIElement.AddHandler(RoutedEvent, Delegate, Boolean) con il parametro handledEventsToo impostato su true. Questo approccio richiede di allegare il gestore eventi nel file code-behind, dopo aver ottenuto un riferimento all'oggetto per l'elemento al quale sarà 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 sopprime 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 il PreviewMouseLeftButtonDown come gestito perché elimina completamente l'evento Click.

Per un esempio di come aggirare la soppressione degli eventi di input, vedere Aggiramento attraverso i controlli.

Vedere anche