Jef*_*eff 2 delphi checkbox duplicates virtualtreeview
我的树有两级节点 - 它是一个联系人列表样式树.
我的问题是,我希望在所有"联系人类别"中检查所有联系人.这是我现在看的联系人列表的截图(是的,我有权发布)
如您所见,Todd Hirsch在类别测试类别中进行了检查,但未在所有联系人中进行检查.我想要实现的目标是让联系人在每个类别中都具有相同的检查状态.
示例:我在测试类别中检查Todd Hirsch - Todd Hirsch会自动检入所有联系人(以及其他所有类别).如果我在所有联系人中查看Todd Hirsch,他也将在测试类别中进行检查.如果我在所有联系人中取消选中Todd Hirsch,他也将在测试类别中取消选中.
我尝试通过VirtualStringtree的OnChecking事件,通过循环遍历树中每个节点的整个树,但是当联系人列表很大(2000 +)时,它非常慢,当它有5000+时,它甚至可能崩溃我的程序(应用程序已停止工作)
你有什么建议?
这是我用来确保只检查一次联系人的代码.(这不是我现在想要的,但这就是我现在正在使用的.)
////////////////////////////////////////////////////////////////////////////////
/// HasDuplicateChecked
////////////////////////////////////////////////////////////////////////////////
Function HasDuplicateChecked(Node: PVirtualNode): PVirtualNode;
Var
ParentNode, ChildNode: PVirtualNode;
I, J: Integer;
Begin
// IHCW
Result := Nil;
// Get the first node of the tree..
ParentNode := VT.GetFirst;
// Loop thru the parent nodes.
for I := 0 to VT.RootNodeCount - 1 do
begin
// Get the first child node.
ChildNode := ParentNode.FirstChild;
// Loop thru the children..
for J := 0 to ParentNode.ChildCount - 1 do
begin
// If the ChildNode is checked...
if NodeIsChecked(ChildNode) then
// And it is NOT the passed node..
if ChildNode <> Node then
// but the data matches..
if GetData(ChildNode).SkypeID = GetData(Node).SkypeID then
begin
// Then pass the Childnode as a result, and EXIT!
Result := ChildNode;
Exit;
end;
// Next child..
ChildNode := ChildNode.NextSibling;
end;
// Next parent...
ParentNode := ParentNode.NextSibling;
end;
End;
////////////////////////////////////////////////////////////////////////////////
/// vtSkypeChecking
////////////////////////////////////////////////////////////////////////////////
procedure TSkypeListEventHandler.vtSkypeChecking(Sender: TBaseVirtualTree;
Node: PVirtualNode; var NewState: TCheckState; var Allowed: Boolean);
Var
Level: Integer;
I: Integer;
Child: PVirtualNode;
begin
// Allow the checking..
Allowed := True;
// Get the Level..
Level := Sender.GetNodeLevel(Node);
// If the level is 0 (Category Level)
if Level = 0 then
begin
// And if the Node's Childcount is more than 0
if Node.ChildCount > 0 then
Begin
// Get the first child..
Child := Node.FirstChild;
// Loop thru the children..
for I := 0 to Node.ChildCount - 1 do
begin
// Set the checkstate, and go next..
Child.CheckState := NewState;
Child := Child.NextSibling;
end;
End;
end;
// If the level is 1 (User Level)
if Level = 1 then
begin
// and if the Node's parent is not Nil..
if Node.Parent <> nil then
begin
// aaand, if the new state is Unchecked...
if (NewState = csUncheckedNormal) or (NewState = csUncheckedPressed) then
begin
// .. and if the node checkstate is checked..
if NodeIsChecked(Node) then
Begin
// Set the PARENT node's checkstate to Unchecked!
Node.Parent.CheckState := csUncheckedNormal;
End;
end;
// BUT, if there is a DUPLICATE of the node, screw the above, and
// forbid the checking!
if HasDuplicateChecked(Node) <> nil then
Allowed := False;
end;
end;
// Uncheck all the duplicates.
UncheckDuplicates;
// Refresh the Tree
Sender.Refresh;
end;
Run Code Online (Sandbox Code Playgroud)
首先,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;
Run Code Online (Sandbox Code Playgroud)
如果您的数据结构具有与给定用户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;
Run Code Online (Sandbox Code Playgroud)
即使您继续将所有数据存储在树控件本身(您多次被告知这是一个坏主意),您仍然可以使用辅助数据结构作为树节点的索引,键入用户ID.如果你有一个足够新的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;
Run Code Online (Sandbox Code Playgroud)
确保UserNodes
在添加或删除类别中的用户时更新索引.