Ответ 1
Задайте для свойства ChangeDelay
соответствующее, большее, чем нулевое значение в миллисекундах, например. 100
. Это реализует таймер с одним выстрелом Роб Кеннеди предлагает в своем ответе.
Этот вопрос будет казаться очевидным для тех, кто сам не сталкивался с проблемой.
Мне нужно обрабатывать изменения выбора в VTV. У меня есть плоский список узлов. Мне нужно делать вещи со всеми выбранными в данный момент узлами всякий раз, когда
и т.д.. Это наиболее распространенное и ожидаемое поведение, как и проводник Windows: при выборе файлов с помощью мыши и/или клавиатуры информационная панель показывает свои свойства. Мне не нужно ничего больше. И здесь я застреваю.
Некоторые из моих исследований следует.
Сначала я использовал OnChange. Казалось, что это хорошо работает, но я заметил странное мерцание, и я обнаружил, что в наиболее распространенном сценарии (один node выбран, пользователь нажимает другой) OnChange запускается дважды:
Эта проблема была googleable, поэтому я обнаружил, что люди используют OnFocusChange и OnFocusChanging вместо OnChange. Но этот способ работает только для одного выбора. С несколькими выборами, перетаскиванием и навигационными клавишами это не работает. В некоторых случаях фокус-события даже не срабатывают (например, когда выбор удаляется путем нажатия пустого места).
Я провел некоторое отладочное исследование, чтобы узнать, как эти обработчики запускаются в разных сценариях. То, что я узнал, представляет собой полный беспорядок без видимого смысла или картины.
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*!
Это довольно трудно прочитать. В двух словах говорится, что в зависимости от конкретного действия пользователя три обработчика (OnChange, OnFocusChange и OnFocusChanging) вызываются в случайном порядке со случайными параметрами. FC и FCg иногда не вызывают, когда мне все еще нужно обработать событие, поэтому очевидно, что я должен использовать OnChange.
Но следующая задача: внутри OnChange я не могу знать, должен ли я использовать этот вызов или ждать следующего. Иногда набор выбранных узлов является промежуточным и непригодным, и обработка его вызовет мерцание графического интерфейса и/или нежелательные тяжелые вычисления.
Мне нужны только вызовы, отмеченные знаком "!". в таблице выше. Но нет способа отличить их отнутри. Например: если я нахожусь в "C-" (OnChange, node= nil, SelectedCount = 0), это может означать, что пользователь удалил выделение (тогда мне нужно его обработать) или что они нажали на другой node (тогда я необходимо подождать следующего вызова OnChange при создании нового выбора).
В любом случае, я надеюсь, что мои исследования не нужны. Я надеюсь, что я упустил что-то, что сделало бы решение простым и понятным, и что вы, ребята, захотите указать мне на это. Решение этой головоломки с использованием того, что у меня было до сих пор, создало бы ужасно ненадежную и сложную логику.
Спасибо заранее!
Задайте для свойства ChangeDelay
соответствующее, большее, чем нулевое значение в миллисекундах, например. 100
. Это реализует таймер с одним выстрелом Роб Кеннеди предлагает в своем ответе.
Используйте таймер с одним выстрелом. Когда срабатывает таймер, проверьте, отличается ли выбор, обновите дисплей, если он есть, и отключите таймер. Каждый раз, когда вы получаете потенциальное событие смены выбора (которое, я думаю, всегда OnChange), reset таймер.
Это дает вам возможность ждать события, которое вы действительно хотите, и избегать мерцания. Стоимость представляет собой слегка задержанный пользовательский интерфейс.
Я предполагаю, что вы, возможно, использовали ответы, приведенные здесь, или даже нашли другое решение, но я хотел бы немного поработать здесь...
В среде NON-Multiselect (я не тестировал ее в среде с несколькими выборами) я нашел довольно простое решение без задержки:
Сохранить глобальный указатель PVirtualNode (Позволяет называть его FSelectedTreeNode). Очевидно, что при запуске вы назначите ему nil.
Теперь, когда вы используете клавиши со стрелками для выбора следующего node, OnTreeChange произойдет дважды. Один раз для node, который будет отменен, и один раз для вновь выбранного node. В вашем событии OnTreeChange вы делаете следующее:
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;
Это хорошо работает с моим кодом, и он не имеет мерцания и, по крайней мере, без задержки.
Фокус в том, что вновь выбранный node будет присвоен глобальному указателю, и это произойдет последним. Поэтому, когда вы выбираете другой node после этого, он не будет ничего делать в первом OnTreeChange, потому что тогда глобальный указатель будет таким же, как и node, который будет отменен.
Вы забыли событие OnStateChange. Это событие будет запущено сразу после любого изменения выбора, и вы сможете обработать все выбранные узлы.
procedure TForm1.vstStateChange(Sender: TBaseVirtualTree; Enter,
Leave: TVirtualTreeStates);
begin
if tsChangePending in Leave then
DoSomething;
end;