Contrassegno degli eventi indirizzati come gestiti e gestione delle classi
I gestori per un evento indirizzato possono contrassegnare l'evento come gestito all'interno dei dati dell'evento. La gestione dell'evento abbrevia efficacemente la route. La gestione delle classi è un concetto di programmazione supportato dagli eventi indirizzati. Un gestore classi ha l'opportunità di gestire un evento indirizzato specifico a livello di classe con un gestore richiamato prima di qualsiasi gestore istanze in qualsiasi istanza di classe.
Prerequisiti
Questo argomento elabora i concetti introdotti in Cenni preliminari sugli eventi indirizzati.
Quando contrassegnare eventi come gestiti
Quando si imposta il valore della Handled proprietà su true
nei dati dell'evento per un evento indirizzato, questo viene definito "contrassegno dell'evento gestito". Non esiste una regola assoluta riguardo a quando contrassegnare gli eventi indirizzati come gestiti, né per un autore di applicazioni né per un autore di controlli che risponde a eventi indirizzati esistenti o implementa nuovi eventi indirizzati. Per la maggior parte, il concetto di "gestito" come trasportato nei dati dell'evento indirizzato deve essere usato come protocollo limitato per le risposte dell'applicazione ai vari eventi indirizzati esposti nelle API WPF e per eventuali eventi indirizzati personalizzati. Un altro modo di considerare il concetto di "gestito" è che in genere è necessario contrassegnare un evento indirizzato come gestito se il codice ha risposto all'evento indirizzato in modo significativo e relativamente completo. In genere, deve essere presente una sola risposta significativa che richiede implementazioni del gestore separate per qualsiasi singola occorrenza di un evento indirizzato. Se sono necessarie più risposte, il codice richiesto deve essere implementato tramite logica dell'applicazione concatenata a un singolo gestore, anziché usare il sistema degli eventi indirizzati per l'inoltro. Anche il concetto di "significativo" è soggettivo e dipende dall'applicazione o dal codice. Come indicazione generale, ecco alcuni esempi di "risposta significativa": impostazione dello stato attivo, modifica dello stato pubblico, impostazione delle proprietà che influiscono sulla rappresentazione visiva e generazione di altri nuovi eventi. Ecco inoltre alcuni esempi di risposte non significative: modifica dello stato privato (senza alcun impatto visivo o sulla rappresentazione programmatica), registrazione di eventi o esame degli argomenti di un evento e scelta di non rispondervi.
Il comportamento del sistema di eventi indirizzati rafforza questo modello di "risposta significativa" per l'uso dello stato gestito di un evento indirizzato, perché i gestori aggiunti in XAML o la firma comune di AddHandler non vengono richiamati in risposta a un evento indirizzato in cui i dati dell'evento sono già contrassegnati come gestiti. Per gestire gli eventi indirizzati contrassegnati dai partecipanti precedenti nella route degli eventi, è necessario eseguire ulteriori operazioni di aggiunta di un gestore con la versione del handledEventsToo
parametro (AddHandler(RoutedEvent, Delegate, Boolean)).
In alcuni casi, i controlli stessi contrassegnano determinati eventi indirizzati come gestiti. Un evento indirizzato gestito rappresenta una decisione da parte degli autori di controlli WPF che le azioni del controllo in risposta all'evento indirizzato sono significative o complete nell'ambito dell'implementazione del controllo e l'evento non richiede ulteriore gestione. Questo avviene in genere aggiungendo un gestore classi per un evento oppure eseguendo l'override di uno dei metodi virtuali del gestore classi presenti in una classe base. Se necessario, è comunque possibile trovare soluzioni alternative per questa gestione degli eventi. Vedere Soluzioni alternative all'eliminazione di eventi da parte dei controlli più avanti in questo argomento.
Eventi "Preview" (tunneling) e Bubbling Events e Event Handling
Gli eventi indirizzati di anteprima sono eventi che seguono una route di tunneling attraverso l'albero degli elementi. Il termine "anteprima" usato nella convenzione di denominazione è indicativo del principio generale per gli eventi di input in base al quale gli eventi indirizzati (di tunneling) di anteprima vengono generati prima dell'evento indirizzato di bubbling equivalente. Inoltre, gli eventi indirizzati di input dotati di una coppia di tunneling e bubbling hanno una logica di gestione distinta. Se l'evento indirizzato di tunneling/anteprima viene contrassegnato come gestito da un listener di eventi, l'evento indirizzato di bubbling verrà contrassegnato come gestito anche prima che qualsiasi listener dell'evento indirizzato di bubbling lo riceva. Gli eventi indirizzati di tunneling e bubbling sono tecnicamente eventi separati, ma condividono intenzionalmente la stessa istanza dei dati degli eventi per permettere questo comportamento.
La connessione tra gli eventi indirizzati di tunneling e bubbling viene eseguita dall'implementazione interna del modo in cui una determinata classe WPF genera eventi indirizzati dichiarati e questo vale per gli eventi indirizzati di input associati. Tuttavia, se questa implementazione a livello di classe non esiste, non vi è alcuna connessione tra un evento indirizzato di tunneling e un evento indirizzato di bubbling che condividono lo schema di denominazione: senza questa implementazione, i due eventi indirizzati sono completamente separati e non vengono generati in sequenza né condividono i dati degli eventi.
Per altre informazioni su come implementare coppie di eventi indirizzati di input di tunneling/bubbling in una classe personalizzata, vedere Creare un evento indirizzato personalizzato.
Gestori classi e gestori istanze
Gli eventi indirizzati considerano due tipi diversi di listener per l'evento: listener di classi e listener di istanze. I listener di classe esistono perché i tipi hanno chiamato un'APIRegisterClassHandler specificaEventManager, nel relativo costruttore statico o hanno eseguito l'override di un metodo virtuale del gestore classi da una classe di base dell'elemento. I listener di istanza sono istanze/elementi di classe specifici in cui uno o più gestori sono stati collegati per l'evento indirizzato da una chiamata a AddHandler. Gli eventi indirizzati WPF esistenti effettuano chiamate a AddHandler come parte del wrapper di eventi CLR (Common Language Runtime) aggiungono{} e rimuovono{} implementazioni dell'evento, che è anche il semplice meccanismo XAML di collegamento dei gestori eventi tramite una sintassi di attributo è abilitato. Pertanto, anche l'utilizzo XAML semplice in definitiva equivale a una AddHandler chiamata.
Gli elementi all'interno dell'albero visuale vengono controllati per individuare le eventuali implementazioni di gestori registrati. I gestori vengono potenzialmente richiamati lungo la route, nell'ordine ereditato nel tipo della strategia di routing per l'evento indirizzato specifico. Ad esempio, gli eventi indirizzati di bubbling richiameranno prima di tutto i gestori collegati allo stesso elemento che ha generato l'evento indirizzato. L'evento indirizzato viene propagato al successivo elemento padre e così via fino a raggiungere l'elemento radice dell'applicazione.
Dal punto di vista dell'elemento radice in una route di bubbling, se la gestione delle classi o qualsiasi elemento più vicino all'origine dell'evento indirizzato richiama gestori che contrassegnano gli argomenti dell'evento come gestiti, i gestori negli elementi radice non vengono richiamati e la route dell'evento viene abbreviata in modo efficace prima di raggiungere l'elemento radice. Tuttavia, la route non viene completamente interrotta, perché è possibile aggiungere gestori con una speciale condizione in base alla quale devono comunque essere richiamati, anche se un gestore classi o un gestore istanze ha contrassegnato l'evento indirizzato come gestito. Questo comportamento viene descritto in Aggiunta di gestori istanze generati anche se gli eventi sono contrassegnati come gestiti più avanti in questo argomento.
A un livello più profondo rispetto a quello della route degli eventi esistono potenzialmente anche più gestori classi che agiscono su qualsiasi istanza specifica di una classe. Questo avviene perché il modello di gestione delle classi per eventi indirizzati permette a tutte le possibili classi in una gerarchia di classi di registrare ciascuna il proprio gestore classi per ogni evento indirizzato. Ogni gestore classi viene aggiunto a un archivio interno e quando viene creata la route degli eventi per un'applicazione, i gestori classi vengono tutti aggiunti alla route. I gestori classi vengono aggiunti alla route in modo che venga richiamato per primo il gestore della classe più derivata, richiamando quindi i gestori classi da ogni classe base successiva. In genere i gestori classi non sono registrati in modo da rispondere anche a eventi indirizzati già contrassegnati come gestiti. Di conseguenza, questo meccanismo di gestione delle classi permette di scegliere tra due opzioni:
Le classi derivate possono completare la gestione delle classi ereditata dalla classe base aggiungendo un gestore che non contrassegna l'evento indirizzato come gestito, perché il gestore della classe base verrà richiamato in un momento successivo al gestore delle classi derivate.
Le classi derivate possono sostituire la gestione delle classi dalla classe base tramite l'aggiunta di un gestore classi che contrassegna l'evento indirizzato come gestito. È necessario usare questo approccio con cautela, perché potrebbe modificare la progettazione dei controlli di base desiderata, ad esempio in aree come l'aspetto visivo, la logica di stato, la gestione degli input e la gestione dei comandi.
Gestione delle classi degli eventi indirizzati tramite classi di base dei controlli
Nel nodo di ogni elemento specifico in una route di eventi i listener di classi possono rispondere all'evento indirizzato prima di qualsiasi listener di istanze per l'elemento. Per questo motivo, i gestori classi vengono usati talvolta per eliminare gli eventi indirizzati che l'implementazione di una determinata classe di controlli non desidera propagare ulteriormente oppure per fornire una gestione speciale dell'evento indirizzato che è una caratteristica della classe. Ad esempio, una classe potrebbe generare il proprio evento specifico della classe che contiene più specifiche sul significato di una certa condizione di input utente nel contesto di una determinata classe. L'implementazione della classe potrebbe quindi contrassegnare come gestito l'evento indirizzato più generale. I gestori di classe vengono in genere aggiunti in modo che non vengano richiamati per gli eventi indirizzati in cui i dati degli eventi condivisi sono già stati contrassegnati come gestiti, ma per i casi atipici esiste anche una RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) firma che registra i gestori di classi da richiamare anche quando gli eventi indirizzati vengono contrassegnati come gestiti.
Metodi virtuali dei gestori classi
Alcuni elementi, in particolare gli elementi di base, ad UIElementesempio , espongono metodi virtuali "On*Event" e "OnPreview*Event" vuoti che corrispondono al relativo elenco di eventi indirizzati pubblici. È possibile eseguire l'override di questi metodi virtuali per implementare un gestore classi per l'evento indirizzato. Le classi di elementi di base registrano questi metodi virtuali come gestore classi per ogni evento indirizzato usando RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) come descritto in precedenza. I metodi virtuali On*Event rendono molto più semplice implementare la gestione delle classi per gli eventi indirizzati pertinenti, senza richiedere l'inizializzazione speciale nei costruttori statici per ogni tipo. Ad esempio, è possibile aggiungere la gestione delle classi per l'evento in qualsiasi UIElement classe derivata eseguendo l'override DragEnter del OnDragEnter metodo virtuale. All'interno dell'override è possibile gestire l'evento indirizzato, generare altri eventi, avviare una logica specifica della classe che può modificare le proprietà degli elementi nelle istanze o scegliere qualsiasi combinazione di queste azioni. È in genere consigliabile chiamare l'implementazione base in questi override, anche se si contrassegna l'evento come gestito. La chiamata dell'implementazione base è fortemente consigliata perché il metodo virtuale è incluso nella classe base. Il modello virtuale protetto standard costituito dalla chiamata delle implementazioni base da ogni metodo virtuale essenzialmente sostituisce ed eguaglia un meccanismo simile che è nativo per la gestione delle classi degli eventi indirizzati, in cui i gestori classi per tutte le classi in una gerarchia di classi vengono chiamati in qualsiasi istanza specifica, a partire dal gestore della classe più derivata e continuando fino al gestore della classe base. È necessario omettere la chiamata dell'implementazione base solo se la classe prevede un requisito intenzionale relativo alla modifica della logica di gestione delle classi base. Se si chiamerà l'implementazione base prima o dopo l'override del codice dipende dalla natura dell'implementazione.
Gestione delle classi degli eventi di input
I metodi virtuali dei gestori classi vengono tutti registrati in modo da essere richiamati solo in presenza di dati degli eventi condivisi che non sono già stati contrassegnati come gestiti. Inoltre, solo per gli eventi di input, le versioni di tunneling e bubbling vengono normalmente generate in sequenza e condividono i dati degli eventi. Di conseguenza, per una specifica coppia di gestori classi di eventi di input in cui uno corrisponde alla versione di tunneling e l'altro alla versione di bubbling, si potrebbe non voler contrassegnare immediatamente l'evento come gestito. Se si implementa il metodo virtuale della gestione delle classi di tunneling per contrassegnare l'evento come gestito, si impedirà al gestore classi di bubbling di essere richiamato, oltre a impedire la chiamata dei gestori istanze normalmente registrati per l'evento di tunneling o di bubbling.
Quando la gestione delle classi in un nodo è completa, vengono presi in considerazione i listener di istanze.
Aggiunta di gestori istanze generati anche se gli eventi sono contrassegnati come gestiti
Il AddHandler metodo fornisce un overload specifico che consente di aggiungere gestori che verranno richiamati dal sistema eventi ogni volta che un evento raggiunge l'elemento di gestione nella route, anche se alcuni altri gestori hanno già modificato i dati dell'evento per contrassegnare tale evento come gestito. Questo non è il comportamento più comune. In genere, i gestori possono essere scritti in modo da modificare tutte le aree del codice dell'applicazione che potrebbero essere influenzate da un evento, indipendentemente dal punto in cui questo è stato gestito nell'albero degli elementi, anche nei casi in cui si desiderano più risultati finali. Inoltre, in genere, un solo elemento deve rispondere all'evento e la logica dell'applicazione appropriata è già stata applicata. Tuttavia, è disponibile l'overload handledEventsToo
per i casi eccezionali in cui un altro elemento in un albero di elementi o in una composizione di controlli ha già contrassegnato un evento come gestito, ma altri elementi in posizione superiore o inferiore nell'albero degli elementi (a seconda della route) vogliono comunque che i rispettivi gestori vengano richiamati.
Quando contrassegnare eventi gestiti come non gestiti
In genere, gli eventi indirizzati contrassegnati come gestiti non devono essere contrassegnati come non gestiti (Handled impostare di nuovo su false
) anche da gestori che agiscono su handledEventsToo
. Tuttavia, alcuni eventi di input hanno rappresentazioni di eventi di alto livello e di livello inferiore che possono sovrapporsi quando l'evento di alto livello viene visualizzato in una posizione nell'albero e quello di basso livello viene visualizzato in un'altra posizione. Si consideri, ad esempio, il caso in cui un elemento figlio è in ascolto di un evento chiave di alto livello, TextInput ad esempio mentre un elemento padre è in ascolto di un evento di basso livello, KeyDownad esempio . Se l'elemento padre gestisce l'evento di basso livello, l'evento di livello superiore può essere eliminato anche nell'elemento figlio che dovrebbe avere la prima opportunità di gestire l'evento.
In queste situazioni può essere necessario aggiungere gestori sia agli elementi padre sia agli elementi figlio per l'evento di basso livello. L'implementazione del gestore dell'elemento figlio può contrassegnare l'evento di basso livello come gestito, ma l'implementazione del gestore dell'elemento padre lo imposterebbe di nuovo come non gestito per permettere ad altri elementi di livello superiore nell'albero (nonché all'evento di alto livello) di rispondere. Questa situazione dovrebbe essere piuttosto rara.
Eliminazione intenzionale di eventi di input per la composizione dei controlli
Lo scenario principale in cui viene usata la gestione delle classi per eventi indirizzati riguarda gli eventi di input e i controlli compositi. Un controllo composito è per definizione composto da più controlli pratici o classi base di controlli. Spesso l'autore del controllo desidera comporre in modo uniforme tutti i possibili eventi di input che possono essere generati da ognuno dei sottocomponenti, per segnalare l'intero controllo come singola origine evento. In alcuni casi, l'autore del controllo potrebbe desiderare di eliminare interamente gli eventi dai componenti oppure sostituire un evento definito da un componente che contiene più informazioni o implica un comportamento più specifico. L'esempio canonico immediatamente visibile a qualsiasi autore di componenti è il modo in cui windows Presentation Foundation (WPF) Button gestisce qualsiasi evento del mouse che alla fine risolverà nell'evento intuitivo che tutti i pulsanti hanno: un Click evento.
La Button classe base (ButtonBase) deriva dalla Control quale a sua volta deriva da FrameworkElement e UIElemente e gran parte dell'infrastruttura di eventi necessaria per l'elaborazione dell'input di controllo è disponibile a UIElement livello. In particolare, UIElement elabora gli eventi generali Mouse che gestiscono l'hit testing per il cursore del mouse all'interno dei relativi limiti e fornisce eventi distinti per le azioni del pulsante più comuni, ad esempio MouseLeftButtonDown. UIElement fornisce anche una virtuale OnMouseLeftButtonDown vuota come gestore di classi preregisterate per MouseLeftButtonDowne ButtonBase ne esegue l'override. Analogamente, ButtonBase usa i gestori di classe per MouseLeftButtonUp. Nelle sostituzioni, che vengono passati i dati dell'evento, le implementazioni contrassegnano tale RoutedEventArgs istanza come gestita impostando Handled su true
e che gli stessi dati dell'evento continuano lungo il resto della route ad altri gestori di classi e anche ai gestori di istanze o ai setter di eventi. Inoltre, l'override OnMouseLeftButtonUp genererà l'evento Click . Il risultato finale per la maggior parte dei listener sarà che gli MouseLeftButtonDown eventi e MouseLeftButtonUp "scompaiono" e vengono sostituiti invece da Click, un evento che contiene più significato perché è noto che questo evento ha avuto origine da un pulsante vero e non da una parte composita del pulsante o da un altro elemento completamente.
Soluzioni alternative all'eliminazione di eventi da parte dei controlli
A volte questo comportamento di eliminazione di eventi all'interno di singoli controlli può interferire con alcune intenzioni più generali della logica di gestione degli eventi per l'applicazione. Ad esempio, se per qualche motivo l'applicazione dispone di un gestore per MouseLeftButtonDown la posizione nell'elemento radice dell'applicazione, si noterà che qualsiasi clic del mouse su un pulsante non richiama MouseLeftButtonDown o MouseLeftButtonUp gestori a livello radice. L'evento stesso è stato effettivamente propagato. Come già detto, le route degli eventi non vengono davvero completate, ma il sistema degli eventi indirizzati ne modifica il comportamento di chiamata del gestore dopo che gli eventi sono stati contrassegnati come gestiti. Quando l'evento indirizzato ha raggiunto il pulsante, la ButtonBase classe che gestisce ha contrassegnato l'handle MouseLeftButtonDown perché desiderava sostituire l'evento Click con un significato maggiore. Pertanto, qualsiasi gestore standard MouseLeftButtonDown verso l'alto verso l'alto della route non verrà richiamato. Esistono due tecniche che è possibile usare per garantire che i gestori vengano richiamati in questo caso.
La prima tecnica consiste nell'aggiungere deliberatamente il gestore usando la handledEventsToo
firma di AddHandler(RoutedEvent, Delegate, Boolean). Una limitazione di questo approccio è che la tecnica di collegamento di un gestore eventi è possibile solo dal codice, non dal markup. La sintassi semplice di specificare il nome del gestore eventi come valore dell'attributo evento tramite XAML (Extensible Application Markup Language) non abilita tale comportamento.
La seconda tecnica può essere usata solo per gli eventi di input, le cui versioni di tunneling e bubbling dell'evento indirizzato sono associate. Per questi eventi indirizzati, è invece possibile aggiungere gestori all'evento indirizzato di anteprima/tunneling equivalente. Poiché l'evento indirizzato percorre tramite tunneling la route a partire dalla radice, il codice di gestione delle classi del pulsante non lo intercetta, presumendo che il gestore di anteprima sia stato collegato a livello di un elemento predecessore nell'albero degli elementi dell'applicazione. Se si usa questo approccio, contrassegnare con cautela qualsiasi evento di anteprima come gestito. Per l'esempio specificato con PreviewMouseLeftButtonDown essere gestito nell'elemento radice, se l'evento è stato contrassegnato come Handled nell'implementazione del gestore, si elimina effettivamente l'evento Click . Questo non è un comportamento consigliato.
Vedi anche
.NET Desktop feedback