2011-11-03 3 views
11

Questa domanda sembrerà ovvia a coloro che non hanno riscontrato il problema da soli.VirtualTreeView: gestire correttamente le variazioni di selezione

Devo gestire i cambiamenti di selezione in VTV. Ho una lista di nodi piatta. Ho bisogno di fare cose con tutti i nodi attualmente selezionati ogni volta

  1. L'utente fa clic su un nodo;
  2. Maiusc/Ctrl fa clic su un nodo;
  3. L'utente utilizza i tasti freccia per spostarsi nell'elenco;
  4. utente crea la selezione trascinando il mouse
  5. utente rimuove la selezione cliccando su uno spazio vuoto o Ctrl-clic l'unico nodo selezionato

ecc E 'il comportamento più comune e previsto, proprio come Esplora risorse di Windows: quando selezioni i file con mouse e/o tastiera, il pannello delle informazioni mostra le loro proprietà. Non ho bisogno di niente di più. Ed è qui che mi blocco.

Alcune delle mie ricerche seguono.


Inizialmente ho utilizzato OnChange. Sembrava funzionare bene, ma ho notato qualche strano sfarfallio e ho scoperto che nello scenario più comune (è stato selezionato un nodo, l'utente fa clic un altro) OnChange viene attivato due volte:

  1. Quando il vecchio nodo è deselezionata . In questo momento la selezione è vuota. Aggiorna la mia GUI per mostrare l'etichetta "nulla è selezionato" al posto di tutte le proprietà.
  2. Quando il nuovo nodo è selezionato. Aggiorna nuovamente la GUI per mostrare le proprietà del nuovo nodo. Da qui lo sfarfallio.

Questo problema era googleable, quindi ho scoperto che le persone utilizzano OnFocusChange e OnFocusChanging anziché OnChange. Ma in questo modo funziona solo per selezione singola. Con la selezione multipla, i tasti di trascinamento e navigazione non funzionano. In alcuni casi gli eventi di messa a fuoco non sparano nemmeno (ad es. Quando la selezione viene rimossa facendo clic su uno spazio vuoto).

Ho fatto uno studio di debug di output per imparare come questi gestori sono licenziati in diversi scenari. Quello che ho scoperto è un casino totale senza alcun senso o schema visibile.

C OnChange 
FC OnFocusChange 
FCg OnFocusChanging 
- nil parameter 
* non-nil parameter 
! valid selection 


Nodes  User action     Handlers fired (in order) 
selected     
0  Click node     FCg-* C*!  
1  Click same     FCg**   
1  Click another     C- FCg** C*! FC* 
1  Ctlr + Click same   FCg** C*!  
1  Ctrl + Click another   FCg** C*! FC* 
1  Shift + Click same   FCg** C*!  
1  Shift + Click another   FCg** C-! FC* 
N  Click focused selected  C-! FCg**  
N  Click unfocused selected  C-! FCg** FC* 
N  Click unselected    C- FCg** C*! FC* 
N  Ctrl + Click unselected  FCg** C*! FC* 
N  Ctrl + Click focused   FCg** C*!   
N  Shift + Click unselected  FCg** C-! FC* 
N  Shift + Click focused   FCg** C-!   
1  Arrow       FCg** FC* C- C*! 
1  Shift + Arrow     FCg** FC* C*! 
N  Arrow       FCg** FC* C- C*! 
N  Shift + Arrow (less)   C*! FCg** FC* 
N  Shift + Arrow (more)   FCg** FC* C*! 
Any Ctrl/Shift + Drag (more)  C*! C-!  
0  Click empty     -   
1/N Click Empty     C-!   
N  Ctrl/Shift + Drag (less)  C-!   
1  Ctrl/Shift + Drag (less)  C-!   
0  Arrow       FCg** FC* C*! 

Questo è piuttosto difficile da leggere. In poche parole dice che a seconda dell'azione specifica dell'utente, i tre gestori (OnChange, OnFocusChange e OnFocusChanging) vengono chiamati in ordine casuale con parametri casuali. FC e FCG a volte non vengono mai chiamati quando ho ancora bisogno dell'evento gestito, quindi è ovvio che devo usare OnChange.

Ma il prossimo compito è: all'interno di OnChange non posso sapere se dovrei usare questa chiamata o aspettare il prossimo. A volte l'insieme dei nodi selezionati è intermedio e non utile e l'elaborazione causerà sfarfallio della GUI e/o calcoli indesiderati.

Ho solo bisogno delle chiamate contrassegnate con "!" nella tabella sopra. Ma non c'è modo di distinguerli dall'interno. Ad esempio: se sono in "C-" (OnChange, Node = nil, SelectedCount = 0) potrebbe significare che l'utente ha rimosso la selezione (quindi devo gestirlo) o che ha fatto clic su un altro nodo (quindi devo aspettare la prossima chiamata OnChange quando si forma una nuova selezione).


In ogni caso, spero che la mia ricerca non sia stata necessaria. Spero di perdere qualcosa che possa rendere la soluzione semplice e chiara e che voi, ragazzi, me lo direte. Risolvere questo enigma usando ciò che ho finora genererebbe una logica terribilmente inaffidabile e complessa.

Grazie in anticipo!

risposta

12

Impostare la proprietà ChangeDelay su un valore appropriato, maggiore di zero in millisecondi, ad es. 100. Questo implementa il timer one-shot che Rob Kennedy suggerisce nella sua risposta.

+0

Grazie, @TOndrej! Non ho mai notato questa proprietà prima. E non mi aspetterei che una cosa del genere esista, per essere onesti. Ma questo sembra essere il modo "ufficiale" per risolvere il mio problema. L'ho provato e funziona, ma mi sembra un po 'imbarazzante ... risolvere questi problemi con i timer mi sembra una pessima idea. Ma se nessuna soluzione migliore si presentasse nel tempo, dovrò attenermi a questa. – 13x666

+0

@ 13x666 se ci pensate, evitare lo sfarfallio in questo caso significa sopprimere gli aggiornamenti dello schermo se si susseguono uno dopo l'altro "troppo velocemente" ... invece, rimandare fino a quando le cose (input dell'utente) "si calmano". –

+1

+1. @ 13x666, un timer è in realtà una soluzione * molto * leggera per attendere che l'input dell'utente si "calmi", come dice TOndrej. È essenzialmente solo una chiamata all'API SetTimer. Ho usato esplicitamente i timer per questo scopo molte volte, con grato successo.L'utente non noterà il ritardo di sub-200-ms, ma l'utente noterà lo sfarfallio e ritardi nell'elaborazione dei comandi successivi causati dalla pittura della GUI inutilmente. –

3

Utilizzare un timer one-shot. Quando scatta il timer, controlla se la selezione è diversa, aggiorna il display se lo è, e disattiva il timer. Ogni volta che ricevi un potenziale evento che cambia la selezione (che credo sia sempre OnChange), ripristina il timer.

Questo ti dà un modo di attendere l'evento che desideri veramente ed evitare lo sfarfallio. Il costo è un'interfaccia utente leggermente ritardata.

+0

Grazie per la risposta, Rob. Ho preso in considerazione questa soluzione ad un certo punto, ma il prezzo è troppo alto. In realtà, se nessuna soluzione pulita si mostrerà, semplicemente usando ogni OnChange costerà meno: il flicker è più facile da tollerare che il ritardo. Tuttavia, entrambi i trade-off sono brutti. – 13x666

+0

Per i controlli senza una proprietà ChangeDelay, questa è la strada da percorrere. –

0

Suppongo che si potrebbe avere utilizzato le risposte qui riportati o anche trovato un'altra soluzione, ma mi piacerebbe contribuire un po 'qui ...

in un ambiente non-Multiselect (non ho ancora testato in un ambiente multi-selezione) Ho trovato una soluzione abbastanza semplice senza ritardo:

Mantieni un puntatore PVirtualNode globale (lo chiamiamo FSelectedTreeNode). All'avvio ovviamente assegnerai zero.

Ora ogni volta che si utilizzano i tasti della tastiera a freccia per selezionare il nodo successivo, OnTreeChange si verificherà due volte. Una volta per il nodo che verrà deselezionato e una volta per il nodo appena selezionato. Nel vostro evento OnTreeChange effettuare le seguenti operazioni:

If Node <> FSelectedTreeNode then 
    begin 
     FSelectedTreeNode := Node; 
     If Node = nil then 
     {Do some "Node Deselected" code} 
     else 
     {Do whatever you want to do when a new node is selected} 
    end; 

Questo funziona abbastanza bene con il mio codice e non ha nessuna luce intermittente e almeno nessun ritardo.

Il trucco è che il nodo appena selezionato verrà assegnato al puntatore globale e accadrà per ultimo. Quindi, quando si seleziona un altro nodo in seguito, non verrà eseguito nulla sul primo OnTreeChange perché il puntatore globale sarà uguale al nodo che viene deselezionato.