First, OnChecking
是要处理的错误事件。你要OnChecked
. OnChecking
其实只是问,“这个节点的检查状态是否允许改变?”它是not意味着去检查其他节点。使用OnChecked
为了那个原因。
其次,您不需要处理类别节点的检查状态。打开toAutoTristateTracking
选项和控件将自动调整所有相关子节点和父节点的状态。 (更改父级,所有子级都会更改。更改子级,父级将更改为“不确定”。)
不过,您的代码似乎走在正确的轨道上。当子节点发生变化时,需要查找所有子节点other树的其余部分中该节点的副本,并更改其检查状态以匹配刚刚更改的节点的新状态。执行该操作所需的时间应该与树中节点的数量成线性关系——节点数量加倍,并且大约需要两倍的时间来查找所有重复项。但即使有几千个节点,它也应该在眨眼之间完成。如果需要更长的时间,则还有一些其他耗时的操作未在此处显示。尝试使用分析器来发现瓶颈。
下面的代码将遍历树中的所有节点。它暂时禁用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;
如果您的数据结构具有与给定用户 ID 关联的所有节点的列表,则情况会更加简单:
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对于树节点,关闭用户 ID。如果您有足够新的 Delphi 版本,您可以使用TDictionary<string, TList<PVirtualNode>>
. Then 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
每当您在类别中添加或删除用户时都会创建索引。