This question will seem obvious to those who haven't encountered the problem themselves.
I need to handle selection changes in VTV. I have a flat list of nodes. I need to do stuff with all currently selected nodes whenever
etc. It's the most common and expected behavior, just like Windows Explorer: when you select files with mouse and/or keyboard, the information panel shows their properties. I need nothing more than that. And this is where I get stuck.
Some of my research follows.
At first I used OnChange. It seemed to work well, but I noticed some strange flickering and I found that in the most common scenario (one node is selected, the user clicks another one) OnChange is fired twice:
This problem was googleable, so I found that people use OnFocusChange and OnFocusChanging instead of OnChange. But this way only works for single selection. With multiple selection, drag-selection and navigation keys this doesn't work. In some cases Focus events don't even fire at all (e.g. when selection is removed by clicking empty space).
I did some debug output study to learn how these handlers are fired in different scenarios. What I found out is a total mess without any visible sense or pattern.
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*!
This is quite hard to read. In the nutshell it says that depending on the specific user action, the three handlers (OnChange, OnFocusChange and OnFocusChanging) are called in random order with random parameters. FC and FCg are sometimes never called when I still need the event handled, so it is obvious I have to use OnChange.
But the next task is: inside OnChange I can't know if I should use this call or wait for the next one. Sometimes the set of selected nodes is intermediate and non-useful, and processing it will cause GUI flickering and/or unwanted heavy calculations.
I only need the calls that are marked with "!" in the table above. But there is no way to distinguish them from inside. E.g.: if I'm in "C-" (OnChange, Node = nil, SelectedCount = 0) it could mean that user removed selection (then I need to handle it) or that they clicked another node (then I need to wait for the next OnChange call when new selection is formed).
Anyway, I hope my research was unnecessary. I hope that I'm missing out something that would make the solution simple and clear, and that you, guys, are going to point it out for me. Solving this puzzle using what I have so far would generate some terribly unreliable and complex logic.
Thanks in advance!
You forgot OnStateChange event. This event will be fired right after any selection change and you can then handle all selected nodes.
procedure TForm1.vstStateChange(Sender: TBaseVirtualTree; Enter,
Leave: TVirtualTreeStates);
begin
if tsChangePending in Leave then
DoSomething;
end;
Set the ChangeDelay
property to an appropriate, greater than zero value in milliseconds, e.g. 100
. This implements the one-shot timer Rob Kennedy suggests in his answer.
Use a one-shot timer. When the timer fires, check whether the selection is different, update your display if it is, and disable the timer. Each time you receive a potential selection-changing event (which I think is always OnChange), reset the timer.
This gives you a way of waiting for the event you really want and avoid flickering. The cost is a slightly delayed UI.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With