Во-первых, OnChecking
- это неправильное событие для обработки.Вы хотите OnChecked
.OnChecking
на самом деле справедливо спросить: "Разрешено ли изменять состояние проверки этого узла?"Это не , предназначенное для отключения и проверки других узлов.Для этого используйте OnChecked
.
Во-вторых, вам не нужно обрабатывать состояние проверки узлов категории.Включите параметр toAutoTristateTracking
, и элемент управления автоматически настроит состояния всех связанных дочерних и родительских узлов.(Измените родителя, и все дочерние элементы изменятся. Измените дочерний элемент, и родительский изменится на «неопределенный».)
В противном случае ваш код, кажется, находится на правильном пути.Когда дочерний узел изменяется, вам нужно найти все другие копии этого узла в остальной части дерева и изменить их состояния проверки, чтобы соответствовать новому состоянию только что измененного узла.Время, необходимое для выполнения этой операции, должно быть линейным по количеству узлов в дереве - удвоить количество узлов, и это займет примерно вдвое больше времени, чтобы найти все дубликаты.Но даже с несколькими тысячами узлов это должно закончиться в мгновение ока.Если это занимает больше времени, есть другая длительная операция, которую вы здесь не показывали.Попробуйте использовать профилировщик, чтобы обнаружить узкое место.
Приведенный ниже код проходит один раз через все узлы в дереве.Он временно отключает обработчик события OnChecked
, так как в противном случае каждый раз, когда он изменяет состояние одного из дубликатов, событие запускается снова.Если новое состояние проверки совпадает с текущим, событие не запускается, поэтому нет опасности бесконечной рекурсии, но отключение события не позволяет ему выполнять множество избыточных обходов по дереву.
procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
Data: PNodeData;
TargetID: string;
Parent: PVirtualNode;
FoundOne: Boolean;
begin
Data := Tree.GetNodeData(Node);
TargetID := Data.SkypeID;
Parent := Tree.GetFirst;
while Assigned(Parent) do begin
// Assume no user appears twice in the same category
if Parent <> Tree.NodeParent[Node] then begin
FoundOne := False;
Child := Tree.GetFirstChild(Parent);
while Assigned(Child) and not FoundOne do begin
Data := Tree.GetNodeData(Child);
if Data.SkypeID = TargetID then begin
// Found a duplicate. Sync it with Node.
Tree.CheckState[Child] := Tree.CheckState[Node];
FoundOne := True;
end;
Child := Tree.GetNextSibling(Child);
end;
end;
Parent := Tree.GetNextSibling(Parent);
end;
end;
procedure TSkypeListEventHandler.vtSkypeChecked(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
CheckedEvent: TVTChangeEvent;
begin
if Sender.GetNodeLevel(Node) = 0 then
exit; // The tree cascades changes automatically
Assert(Sender.GetNodeLevel(Node) = 1, 'Unexpected node level');
// We'll be accessing members that are protected in TBaseVirtualTree, but
// they're public in TVirtualStringTree, so make sure we're still operating
// on the same tree.
Assert(Sender = vtSkype);
CheckedEvent := vtSkype.OnChecked;
vtSkype.OnChecked := nil;
try
PropagateCheckState(vtSkype, Node);
finally
vtSkype.OnChecked := CheckedEvent;
end;
end;
Если бы в вашей структуре данных был список всех узлов, связанных с данным идентификатором пользователя, это было бы гораздо проще:
procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
Data: PNodeData;
i: Integer;
begin
Data := Tree.GetNodeData(Node);
for i := 0 to Pred(Data.User.Nodes.Count) do
Tree.CheckState[Data.User.Nodes[i]] := Tree.CheckState[Node];
end;
Даже если вы продолжите хранить все свои данные в деревеСам по себе контроль (который вам неоднократно советовали - плохая идея), вы все равно можете использовать вторичную структуру данных, чтобы действовать как index для узлов дерева, отключенных от идентификатора пользователя.Если у вас достаточно свежая версия Delphi, вы можете использовать TDictionary<string, TList<PVirtualNode>>
.Тогда PropagateCheckState
может выглядеть так:
uses Generics.Collections;
var
UserNodes: TDictionary<string, TList<PVirtualNode>>;
procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
Data: PNodeData;
Nodes: TList<PVirtualNode>;
i: Integer;
begin
Data := Tree.GetNodeData(Node);
if not UserNodes.TryGetValue(Data.SkypeID, Nodes) then
exit; // Weird. The node's ID isn't in the index at all.
for i := 0 to Pred(Nodes.Count) do
Tree.CheckState[Nodes[i]] := Tree.CheckState[Node];
end;
Обязательно обновляйте индекс UserNodes
всякий раз, когда вы добавляете или удаляете пользователя из категории.