VirtualTreeView: правильно обрабатывает изменения выбора - PullRequest
11 голосов
/ 03 ноября 2011

Этот вопрос покажется очевидным для тех, кто не сталкивался с проблемой самостоятельно.

Мне нужно обработать изменения выбора в VTV.У меня плоский список узлов.Мне нужно что-то делать со всеми выбранными в данный момент узлами всякий раз, когда

  1. Пользователь щелкает узел;
  2. Пользователь Shift / Ctrl-щелкает узел;
  3. Пользователь использует клавиши со стрелкамидля навигации по списку;
  4. Пользователь создает выделение, перетаскивая мышь
  5. Пользователь удаляет выделение, щелкая пустое место или щелкая Ctrl при щелчке по единственному выбранному узлу

и т. д.,Это наиболее распространенное и ожидаемое поведение, как и в Windows Explorer: когда вы выбираете файлы мышью и / или клавиатурой, на информационной панели отображаются их свойства.Мне не нужно ничего больше, чем это.И вот тут я застреваю.

Далее следуют некоторые мои исследования.


Сначала я использовал OnChange.Казалось, что это работает хорошо, но я заметил странное мерцание и обнаружил, что в наиболее распространенном сценарии (один узел выбран, пользователь нажимает другой), OnChange запускается дважды:

  1. Когда старыйузел не выбран.На данный момент выбор пуст.Я обновляю свой графический интерфейс, чтобы вместо всех свойств отображалась метка «ничего не выбрано».
  2. Когда выбран новый узел.Я снова обновляю свой графический интерфейс, чтобы показать свойства нового узла.Отсюда и мерцание.

Эта проблема была googleable, поэтому я обнаружил, что люди используют OnFocusChange и OnFocusChanging вместо OnChange.Но этот способ работает только для одного выбора.При множественном выборе, перетаскивании и навигационных клавишах это не работает.В некоторых случаях события Focus даже не запускаются вообще (например, когда выбор удаляется нажатием на пустое место).

Я провел некоторое отладочное исследование вывода, чтобы узнать, как эти обработчики запускаются в различных сценариях.Я обнаружил полный беспорядок без какого-либо видимого смысла или паттерна.

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), это может означать, что пользователь удалил выделение (тогда мне нужно его обработать) или что он щелкнул другой узел (тогда мне нужно ждатьследующий вызов OnChange при формировании новой выборки).


В любом случае, я надеюсь, что мое исследование было ненужным.Я надеюсь, что я упускаю что-то, что сделало бы решение простым и понятным, и что вы, ребята, собираетесь указать мне на это.Решение этой загадки с использованием того, что у меня есть, порождает ужасно ненадежную и сложную логику.

Заранее спасибо!

Ответы [ 4 ]

12 голосов
/ 03 ноября 2011

Установите для свойства ChangeDelay соответствующее значение больше нуля в миллисекундах, например, 100. Это реализует таймер с одним выстрелом, который предлагает Роб Кеннеди в своем ответе.

3 голосов
/ 03 ноября 2011

Используйте таймер одноразового использования. Когда таймер срабатывает, проверьте, отличается ли выбор, обновите свой дисплей, если он есть, и отключите таймер. Каждый раз, когда вы получаете потенциальное событие изменения выбора (которое, я думаю, всегда OnChange), сбрасывайте таймер.

Это дает вам возможность ждать события, которое вы действительно хотите, и избежать мерцания. Стоимость интерфейса немного задерживается.

0 голосов
/ 18 мая 2019

Вы забыли событие OnStateChange.Это событие будет запущено сразу после любого изменения выбора, и вы сможете обработать все выбранные узлы.

procedure TForm1.vstStateChange(Sender: TBaseVirtualTree; Enter,
  Leave: TVirtualTreeStates);
begin
  if tsChangePending in Leave then
    DoSomething;
end;
0 голосов
/ 27 декабря 2015

Я полагаю, что вы могли использовать ответы, приведенные здесь, или даже нашли другое решение, но я бы хотел немного рассказать здесь ...

В среде NON-Multiselect (я не тестировал ее в среде с несколькими вариантами выбора) я нашел довольно простое решение без задержки:

Сохранить глобальный указатель PVirtualNode (назовем его FSelectedTreeNode). При запуске, очевидно, вы назначите ему ноль.

Теперь каждый раз, когда вы используете клавиши со стрелками для выбора следующего узла, OnTreeChange будет происходить дважды. Один раз для узла, который будет отменен, и один раз для вновь выбранного узла. В вашем событии 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;

Это хорошо работает с моим кодом, не мерцает и не задерживается.

Хитрость в том, что вновь выбранный узел будет назначен глобальному указателю, и это произойдет последним. Поэтому, когда вы выбираете другой узел впоследствии, он ничего не будет делать с первым OnTreeChange, потому что тогда глобальный указатель будет таким же, как и отмена выбора узла.

...