Delphi 如何保持多个虚拟树视图节点的检查状态同步?
我的树有两个级别的节点-它是一个联系人列表样式的树 我的问题是,我想检查所有联系人类别中的每个联系人。这是我的联系人列表的屏幕截图,现在看起来,是的,我有权发布它 如您所见,Todd Hirsch在类别测试类别中被选中,但不是在所有联系人中都被选中。我试图实现的是,让联系人在每个类别中都具有相同的检查状态 示例:我在测试类别中检查Todd Hirsch-Todd Hirsch在所有联系人和其他类别中自动检查。如果我在所有联系人中检查Todd Hirsch,他也将在测试类别中检查。若我在所有联系人中取消选中Todd Hirsch,他也将在测试类别中取消选中 我试着通过VirtualStringtree的OnChecking事件来完成,通过循环遍历树中每个节点的整个树,但是当联系人列表是大2000+时,速度非常慢,当有5000+时,它甚至可能会崩溃。我的程序应用程序已经停止工作 你有什么建议 下面是我用来确保联系人只检查一次的代码。这不是我现在想要的,而是我现在正在使用的Delphi 如何保持多个虚拟树视图节点的检查状态同步?,delphi,checkbox,duplicates,virtualtreeview,Delphi,Checkbox,Duplicates,Virtualtreeview,我的树有两个级别的节点-它是一个联系人列表样式的树 我的问题是,我想检查所有联系人类别中的每个联系人。这是我的联系人列表的屏幕截图,现在看起来,是的,我有权发布它 如您所见,Todd Hirsch在类别测试类别中被选中,但不是在所有联系人中都被选中。我试图实现的是,让联系人在每个类别中都具有相同的检查状态 示例:我在测试类别中检查Todd Hirsch-Todd Hirsch在所有联系人和其他类别中自动检查。如果我在所有联系人中检查Todd Hirsch,他也将在测试类别中检查。若我在所有联系人
////////////////////////////////////////////////////////////////////////////////
/// 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;
为了简单起见,我假设Todd包含在TreeView用来创建条目的类中。 只要您的树视图从该类请求它的信息,您就可以通过在类本身中添加布尔检查并使树视图无效来逃脱。 当树重新绘制自身时,它将使用您的类相应地设置复选框 我知道VirtualTreeView的速度足够快,可以在一瞬间对数千个条目执行此操作。首先,OnChecking是要处理的错误事件。你想再检查一次。一旦检查真的只是问,这个节点的检查状态允许改变吗?它并不意味着关闭并检查其他节点。使用一次检查 其次,不需要处理类别节点的检查状态。启用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;
即使您继续将所有数据存储在树控件本身中,这是一个坏主意,但您仍然可以使用辅助数据结构作为树节点的索引,并键入用户ID。如果您有足够新的Delphi版本,则可以使用TDictionary。然后,状态可以如下所示:
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索引。对于遍历所有节点,函数GetNextVirtualNode、bool childs可能会很有用 类似C++代码< /P>的借口
TVirtualNode* parent=NULL;
TVirtualNode* node=VST->GetFirst();
while (node)
{
data=static_cast<TreeItemData*>(VST->GetNodeData(node));
//doo something with node, data
node=VST->GetNext(node, true);
}
问题是,我的数据绑定到Treeview,我将其存储在PVirtualNode中。我知道这很糟糕,但我就是这样开始的,我不想为这个项目重写5000行代码——至少现在不行:P@David-我当然想解决它,但是仅仅为了这个重写整个项目会花费太多时间,主要是因为我的目标是在周一发布。@Jeff使用虚拟控件是在有大量数据时解决性能问题的方法。你选择了不使用虚拟控件,并为此付出了代价。@David-我相信你看到了我的另一个问题,即如何制作虚拟控件,对吗?嗯,还是没有弄明白。@Jeff我读了你所有的问题,我认为你真正的问题是你的设计,也许我的建议对这个项目没有帮助,但将来会帮助你开发出更清晰、更易于维护的软件。不要让您的域对象与此演示文稿配合使用。使用更多的OOD是底线是否可能
您遇到了什么递归问题?当您在OnCheckedChanged事件处理程序中更改checked属性时,是否会再次遍历该列表?您可以尝试快速破解并阻止对OnCheckedChanged事件处理程序或其任何调用的重入调用。@David我正在使用OnChecking事件。我现在要做的是,我阻止第二次检查,这样每个联系人只能检查一次,我通过遍历列表来做到这一点,对于每个节点,我再次遍历列表以找到重复项,并确保没有检查重复项。这显然是错误的做法,但这是我到目前为止唯一的方法。@Jeff我不知道你在说什么。@David-让我发布代码Jeff-你应该研究递归。+1-谢谢,但是我遇到了这个编译器错误:无法访问私有符号TBaseVirtualTree.GetNodeParent at if Parent Tree.GetNodeParent.Oh,很抱歉改为使用NodeParent属性。它可以工作,但当我单击Category节点中的复选框时,它会冻结3秒钟-为什么?我的方法没有做到这一点?事实上,当我使用包含400个子节点的All Contacts节点时,需要6秒或更长时间。请使用更适合此任务的数据结构。即使它不是主要的数据结构,您显然需要查找所有相关的节点,因此设置一个索引,让您可以查找节点列表。如果输入的是用户ID,您可以使用类似TDictionary的内容。每次在类别中添加或删除用户时更新索引。
TVirtualNode* parent=NULL;
TVirtualNode* node=VST->GetFirst();
while (node)
{
data=static_cast<TreeItemData*>(VST->GetNodeData(node));
//doo something with node, data
node=VST->GetNext(node, true);
}