如何保持多个虚拟树视图节点的检查状态同步?
我的树有 2 层节点 - 它是一个联系人列表样式树。
我的问题是,我想检查所有“联系人类别”中的每个联系人。这是我的联系人列表现在的屏幕截图(是的,我有权发布它)
As您会看到,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;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
首先,
OnChecking
是要处理的错误事件。您需要OnChecked
。OnChecking
实际上只是问,“这个节点的检查状态是否允许改变?”它不意味着去检查其他节点。使用OnChecked
来实现这一点。其次,您不需要处理类别节点的检查状态。打开
toAutoTristateTracking
选项,控件将自动调整所有相关子节点和父节点的状态。 (更改父级,所有子级都会更改。更改子级,父级将更改为“不确定”。)不过,否则您的代码似乎处于正确的轨道上。当子节点更改时,您需要在树的其余部分中找到该节点的所有其他副本,并更改它们的检查状态以匹配刚刚更改的节点的新状态。执行该操作所需的时间应该与树中节点的数量成线性关系——节点数量加倍,并且大约需要两倍的时间来查找所有重复项。但即使有几千个节点,它也应该在眨眼之间完成。如果需要更长的时间,则还有一些其他耗时的操作未在此处显示。尝试使用分析器来发现瓶颈。
下面的代码将遍历树中的所有节点。它暂时禁用
OnChecked
事件处理程序,因为否则,每次它更改重复项之一的状态时,该事件都会再次运行。如果新的检查状态与当前的检查状态相同,则该事件不会运行,因此不存在无限递归的危险,但禁用该事件确实可以防止它在树中进行大量冗余遍历。如果您的数据结构具有与给定用户 ID 关联的所有节点的列表,则情况会更加简单:
即使您继续将所有数据存储在树控件本身中(您已经多次被告知这是一个坏主意),您仍然可以使用辅助数据结构作为树节点的索引,并锁定用户 ID。如果您有足够新的 Delphi 版本,则可以使用
TDictionary>
。那么PropagateCheckState
可能如下所示:每当您在类别中添加或删除用户时,请确保更新
UserNodes
索引。First,
OnChecking
is the wrong event to handle. You wantOnChecked
.OnChecking
really justs ask, "Is this node's check state allowed to change?" It's not meant to go off and check other nodes. UseOnChecked
for that.Second, you shouldn't need to handle the check-state of the category nodes. Turn on the
toAutoTristateTracking
option and the control will automatically adjust the states of all related child and parent nodes. (Change a parent, and all the children change. Change a child, and the parent changes to "indeterminate.")Your code seems to be on the right track otherwise, though. When a child node changes, you need to find all the other copies of that node in the rest of the tree and change their check states to match the new state of the just-changed node. The time it takes to perform that operation should be linearly in the number of nodes in the tree — double the number of nodes, and it should take roughly twice the amount of time to find all the duplicates. But even with a few thousand nodes, it should finish in the blink of an eye. If it takes longer, there's some other time-consuming operation that you haven't shown here. Try using a profiler to discover the bottleneck.
The code below traverses once through all the nodes in the tree. It temporarily disables the
OnChecked
event handler because otherwise, each time it changes the state of one of the duplicates, the event would run again. If the new check state is the same as the current one, the event doesn't run, so there's no danger of infinite recursion, but disabling the event does prevent it from doing lots of redundant traversals through the tree.If your data structure had a list of all the nodes associated with a given user ID, it would be much more straightforward:
Even if you continue to store all your data in the tree control itself (which you've been advised many times is a bad idea), you can still use a secondary data structure to act as an index for tree nodes, keyed off the user ID. If you have a sufficiently recent Delphi version, you can use
TDictionary<string, TList<PVirtualNode>>
. ThenPropagateCheckState
could look like this:Make sure to update the
UserNodes
index whenever you add or remove a user in a category.为了简单起见,我假设“Todd”包含在 TreeView 用于创建条目的类中。
只要您的 TreeView 请求来自该类的信息,您就可以通过在类本身中添加布尔检查并使树视图无效来逃脱。
当树重新绘制自身时,它将使用您的类来相应地设置复选框。
我知道 VirtualTreeView 的速度足够快,可以在一瞬间对数千个条目执行此操作。
I'll assume for simplicity sake that "Todd" is contained in a class used by the TreeView to create the entries.
As long as your TreeView is requesting it's information from that class, you could get away by adding the boolean check in the class itself and invalidate the treeview.
When the tree repaints itself, it will use your class to set the checkboxes accordingly.
I know VirtualTreeView is fast enough to perform this on several thousands of entries in a split second.
对于遍历所有节点可能有用的函数 GetNext(PVirtualNode, bool childs)
就像(请原谅 C++ 代码)
For traverse over all nodes may be usefull function GetNext(PVirtualNode, bool childs)
like (excuse for C++ code)