2011-11-03 12 views
11

Esta pregunta parecerá obvia para aquellos que no han encontrado el problema por sí mismos.VirtualTreeView: manejo adecuado de los cambios de selección

Necesito gestionar los cambios de selección en VTV. Tengo una lista plana de nodos. Necesito hacer cosas con todos los nodos actualmente seleccionados cuando

  1. El usuario hace clic en un nodo;
  2. Mayús/Ctrl-clic del usuario en un nodo;
  3. El usuario utiliza las teclas de flecha para navegar por la lista;
  4. usuario crea selección arrastrando el ratón
  5. usuario quita la selección haciendo clic en el espacio vacío o Ctrl-clic en el único nodo seleccionado

etc. Es el comportamiento más común y esperado, al igual que el Explorador de Windows: cuando selecciona archivos con mouse y/o teclado, el panel de información muestra sus propiedades. No necesito nada más que eso. Y aquí es donde me quedo atascado.

A continuación, se detallan algunas de mis investigaciones.


Al principio utilicé OnChange. Parece que ha funcionado bien, pero me di cuenta de un extraño parpadeo y me encontré con que en el escenario más común (se selecciona un nodo, el usuario hace clic en otro) AlCambiar se dispara dos veces:

  1. Cuando el nodo de edad no está seleccionada . En este momento, la selección está vacía. Actualizo mi GUI para mostrar la etiqueta "no se selecciona nada" en lugar de todas las propiedades.
  2. Cuando se selecciona el nuevo nodo. Actualizo mi GUI nuevamente para mostrar las propiedades del nuevo nodo. De ahí el parpadeo.

Este problema era googleable, por lo que encontré que la gente usa OnFocusChange y OnFocusChanging en lugar de OnChange. Pero de esta manera solo funciona para una sola selección. Con la selección múltiple, la selección de arrastre y las teclas de navegación, esto no funciona. En algunos casos, los eventos de Enfoque ni siquiera se disparan (por ejemplo, cuando se elimina la selección haciendo clic en el espacio vacío).

Hice algunos estudios de resultados de depuración para aprender cómo se disparan estos controladores en diferentes escenarios. Lo que descubrí es un desastre total sin ningún sentido o patrón visible.

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*! 

Esto es bastante difícil de leer. En pocas palabras, dice que dependiendo de la acción específica del usuario, los tres manejadores (OnChange, OnFocusChange y OnFocusChanging) son llamados en orden aleatorio con parámetros aleatorios. FC y FCg a veces nunca se llaman cuando todavía necesito el evento manejado, por lo que es obvio que tengo que usar OnChange.

Pero la siguiente tarea es: dentro de OnChange no sé si debo usar esta llamada o esperar la siguiente. En ocasiones, el conjunto de nodos seleccionado es intermedio y no es útil, y al procesarlo se producirán parpadeos en la GUI y/o cálculos pesados ​​no deseados.

Solo necesito las llamadas marcadas con "!" en la tabla de arriba. Pero no hay forma de distinguirlos del interior. Por ejemplo: si estoy en "C-" (OnChange, Node = nil, SelectedCount = 0) podría significar que el usuario eliminó la selección (entonces tengo que manejarlo) o que hicieron clic en otro nodo (entonces tengo que esperar la próxima llamada OnChange cuando se forma una nueva selección).


De todos modos, espero que mi investigación no sea necesaria. Espero que me esté perdiendo algo que haga que la solución sea simple y clara, y que ustedes, chicos, me lo dirán. Resolver este rompecabezas usando lo que tengo hasta ahora generaría una lógica terriblemente poco fiable y compleja.

¡Gracias de antemano!

Respuesta

12

Establezca la propiedad ChangeDelay en un valor adecuado, mayor que cero en milisegundos, p. Ej. 100. Esto implementa el temporizador de una sola toma que Rob Kennedy sugiere en su respuesta.

+0

Gracias, @TOndrej! Nunca antes había notado esta propiedad. Y no esperaría que existiera tal cosa, para ser honesto. Pero esta parece ser la forma "oficial" de resolver mi problema. Lo probé y funciona, pero me siento un poco incómodo ... resolver esos problemas con temporizadores me parece una muy mala idea. Pero si no surge una solución mejor con el tiempo, tendré que apegarme a esta. – 13x666

+0

@ 13x666, si lo piensas bien, evitar el parpadeo en este caso significa suprimir las actualizaciones de la pantalla si siguen una tras otra "demasiado rápido" ... en cambio, diferir hasta que las cosas (entrada del usuario) "se calmen". –

+1

+1. @ 13x666, un temporizador es en realidad una * solución * muy liviana para esperar a que los usuarios ingresen a "calmarse", como lo expresa TOndrej. Básicamente es solo una llamada a la API de SetTimer. He usado explícitamente temporizadores para este propósito muchas veces, con gran éxito.El usuario no notará la demora de menos de 200 ms, pero el usuario notará parpadeos y demoras en el procesamiento de los comandos posteriores causados ​​por pintar innecesariamente la GUI. –

3

Utilice un temporizador de una sola toma. Cuando el temporizador se dispara, verifique si la selección es diferente, actualice su pantalla si es así, y deshabilite el temporizador. Cada vez que recibe un posible evento de cambio de selección (que creo que siempre es OnChange), restablezca el temporizador.

Esto le ofrece una manera de esperar el evento que realmente desea y evitar el parpadeo. El costo es una interfaz de usuario ligeramente retrasada.

+0

Gracias por la respuesta, Rob. Consideré esta solución en algún momento, pero el precio es demasiado alto. En realidad, si no se muestra una solución limpia, el uso de cada OnChange costará menos: el parpadeo es más fácil de tolerar que el retraso. Aún así, ambos intercambios son feos. – 13x666

+0

Para controles sin una propiedad ChangeDelay, este es el camino a seguir. –

0

supongo que podría haber utilizado las respuestas dadas aquí o incluso encontrado otra solución, pero me gustaría contribuir un poco aquí ...

en un entorno no-selección múltiple (no he probado en una entorno de selección múltiple) He encontrado una solución bastante simple sin el retraso:

Mantenga un puntero PVirtualNode global (Permite llamarlo FSelectedTreeNode). En el arranque obviamente le asignarás nada.

Ahora cada vez que utilice las teclas del teclado de flecha para seleccionar el siguiente nodo, el Cambio de actividad ocurrirá dos veces. Una vez para el nodo que se deseleccionará y una vez para el nodo recién seleccionado. En su caso OnTreeChange hace lo siguiente:

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; 

Esto funciona bastante bien con mi código y no tiene ningún parpadeo y al menos sin demora.

El truco es que el nodo recién seleccionado se asignará al puntero global y pasará al último. Por lo tanto, cuando selecciona otro nodo posteriormente, no hará nada en el primer OnTreeChange porque el puntero global será el mismo que el que se deselecciona.

Cuestiones relacionadas