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 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.
- 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.
- 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 eventi di classe statici, registrati chiamando il metodo RegisterClassHandler all'interno di un costruttore di classi statiche.
- Sovrascrivi i gestori degli eventi di 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 On<evento nome> e OnPreview<evento nome>.
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 sutrue
. 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
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 è:
- Il gestore dell'istanza associato a
componentWrapper
viene attivato dall'evento instradatoPreviewKeyDown
. - Il gestore statico di classe associato a
componentWrapper
viene attivato dall'evento instradatoKeyDown
. - Il gestore di classe statica associato a
componentWrapperBase
viene attivato dall'evento instradatoKeyDown
. - Il gestore di classe di override associato a
componentWrapper
viene attivato dall'evento instradatoKeyDown
. - Il gestore della classe di override associato a
componentWrapperBase
viene attivato dall'evento instradatoKeyDown
. - L'evento indirizzato
KeyDown
viene contrassegnato come gestito. - Il gestore dell'istanza associato a
componentWrapper
viene attivato dall'evento instradatoKeyDown
. Il gestore è stato registrato con il parametrohandledEventsToo
impostato sutrue
.
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 sutrue
. 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 ilPreviewMouseLeftButtonDown
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
.NET Desktop feedback