如何保持多个虚拟树视图节点的检查状态同步?

发布于 2024-10-31 14:38:26 字数 3758 浏览 1 评论 0 原文

我的树有 2 层节点 - 它是一个联系人列表样式树。

我的问题是,我想检查所有“联系人类别”中的每个联系人。这是我的联系人列表现在的屏幕截图(是的,我有权发布它)

Contact List

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;

My tree has 2 levels of nodes - it's a Contact List style tree.

My problem is, that I would like to have every contact checked, in all the "Contact Categories". Here is a screenshot of my contact list as it looks now (And yes, I have permission to post it)

Contact List

As you see, Todd Hirsch is checked in the Category Test Category, but not in All Contacts. What I am trying to achieve, is to have a contact have the same checked status in every category.

Example: I check Todd Hirsch in the Test Category - Todd Hirsch is automatically checked in All Contacts (And every other category). If I check Todd Hirsch in the All Contacts, he will also get checked in Test Category. If I Uncheck Todd Hirsch in All Contacts, he will also get unchecked in Test Category.

I tried doing it through the VirtualStringtree's OnChecking events, by looping thru the whole tree for each node in the tree, however when the contact list is big (2000 +), it is very slow, and when there's like 5000+, it might even crash my program (Application has stopped working)

What do you suggest?

Here is the code that I use to make sure that a contact is only checked once. (That is not what I want now, but it's what I am using right now.)

////////////////////////////////////////////////////////////////////////////////
/// 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

丶视觉 2024-11-07 14:38:26

首先,OnChecking 是要处理的错误事件。您需要OnCheckedOnChecking 实际上只是问,“这个节点的检查状态是否允许改变?”它意味着去检查其他节点。使用 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;

如果您的数据结构具有与给定用户 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;

即使您继续将所有数据存储在树控件本身中(您已经多次被告知这是一个坏主意),您仍然可以使用辅助数据结构作为树节点的索引,并锁定用户 ID。如果您有足够新的 Delphi 版本,则可以使用 TDictionary>。那么 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 索引。

First, OnChecking is the wrong event to handle. You want OnChecked. OnChecking really justs ask, "Is this node's check state allowed to change?" It's not meant to go off and check other nodes. Use OnChecked 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.

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;

If your data structure had a list of all the nodes associated with a given user ID, it would be much more straightforward:

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;

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>>. Then PropagateCheckState could look like this:

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;

Make sure to update the UserNodes index whenever you add or remove a user in a category.

青朷 2024-11-07 14:38:26

为了简单起见,我假设“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.

可是我不能没有你 2024-11-07 14:38:26

对于遍历所有节点可能有用的函数 GetNext(PVirtualNode, bool childs)
就像(请原谅 C++ 代码)

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);
    } 

For traverse over all nodes may be usefull function GetNext(PVirtualNode, bool childs)
like (excuse for C++ code)

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);
    } 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文