如何保持多个虚拟树视图节点的检查状态同步? [英] How can I keep the check state of multiple Virtual Tree View nodes in sync?

查看:190
本文介绍了如何保持多个虚拟树视图节点的检查状态同步?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我的问题是,每个联系人检查,在所有联系类别。这是我的联系人列表的屏幕截图,因为它看起来现在(是的,我有权限发布)





如您所见, Todd Hirsch strong>测试类别,但不在所有联系人中。我想要实现的是让每个类别的联系人都具有相同的已检查状态



示例:我检查Todd Hirsch测试类别 - Todd Hirsch将自动在所有联系人(以及所有其他类别)中选中。如果我在所有联系人中查看Todd Hirsch,他还将获得签入测试类别。如果我在所有联系人中取消选中Todd Hirsch,他也将在测试类别中取消选中。



我尝试通过VirtualStringtree的OnChecking事件,每个节点在树中,然而当联系人列表是大(2000 +),它是非常缓慢,当有像5000+,它可能甚至崩溃我的程序(应用程序已停止工作) / p>

您建议什么?



这里是我使用的代码,一旦。 (这不是我现在想要的,但现在是我正在使用的)

  ////////////////////////////////////////////////// ////////////////////////////// 
/// HasDuplicateChecked
//////// ////////////////////////////////////////////////// //////////////////////
函数HasDuplicateChecked(Node:PVirtualNode):PVirtualNode;
Var
ParentNode,ChildNode:PVirtualNode;
I,J:Integer;
Begin

// IHCW
结果:= Nil;

//获取树的第一个节点。
ParentNode:= VT.GetFirst;

//通过父节点循环。
for I:= 0到VT.RootNodeCount - 1 do
begin
//获取第一个子节点。
ChildNode:= ParentNode.FirstChild;
//循环通过子项..
for J:= 0到ParentNode.ChildCount - 1 do
begin
//如果ChildNode被选中...
如果NodeIsChecked(ChildNode)then
//并且它不是传递的节点..
如果ChildNode<>节点然后
//但数据匹配..
如果GetData(ChildNode).SkypeID = GetData(Node).SkypeID然后
begin
//然后传递Childnode作为结果和EXIT!
结果:= ChildNode;
退出;
end;
//下一个child ..
ChildNode:= ChildNode.NextSibling;
end;
//下一个父...
ParentNode:= ParentNode.NextSibling;
end;

结束;


//////////////////////////////////////// ////////////////////////////////////////////
/// vtSkypeChecking
//////////////////////////////////////////////// ////////////////////////////////////
程序TSkypeListEventHandler.vtSkypeChecking(Sender:TBaseVirtualTree;
Node:PVirtualNode; var NewState:TCheckState; var Allowed:Boolean);
Var
级别:整数;
I:Integer;
孩子:PVirtualNode;
begin
//允许检查..
Allowed:= True;
//获取级别..
级别:= Sender.GetNodeLevel(Node);

//如果级别为0(类别级别)
如果Level = 0,则
开始
//如果节点的Childcount大于0
if Node.ChildCount> 0 then
Begin
//获取第一个子节点..
子节点:= Node.FirstChild;
//循环通过子项..
for I:= 0到Node.ChildCount - 1 do
begin
//设置checkstate,然后转到下一个..
Child.CheckState:= NewState;
Child:= Child.NextSibling;
end;
End;
end;


//如果级别为1(用户级别)
如果Level = 1,则
开始
//如果Node的父级不是Nil ..
if Node.Parent<> nil then
begin
// aaand如果新状态为Unchecked ...
if(NewState = csUncheckedNormal)或(NewState = csUncheckedPressed)then
begin
// ..如果节点checkstate被检查..
如果NodeIsChecked(Node)然后
Begin
//将PARENT节点的checkstate设置为Unchecked!
Node.Parent.CheckState:= csUncheckedNormal;
End;

end;
//但是,如果有一个DUPLICATE的节点,拧上面的,和
//禁止检查!
如果HasDuplicateChecked(Node)<> nil then
Allowed:= False;

end;
end;

//取消选中所有重复项。
UncheckDuplicates;

//刷新树
Sender.Refresh;

end;


解决方案

首先, OnChecking 是错误的事件处理。您需要 OnChecked OnChecking 真的只是问,这个节点的检查状态是否允许改变?这不是意味着离开和检查其他节点。



其次,你不需要处理类别节点的检查状态,因为 OnChecked 。打开 toAutoTristateTracking 选项,控件将自动调整所有相关子节点和父节点的状态。 (更改父项,并更改所有子项,更改子项,父项更改为不确定。)



您的代码似乎在正确的轨道上,但。当子节点更改时,您需要在树的其余部分找到该节点的所有其他副本,并更改​​其检查状态以匹配刚更改的节点的新状态。执行该操作所花费的时间应该是树中节点数量的线性 - 节点数量的两倍,并且它大概需要两倍的时间来找到所有重复的。但即使有几千个节点,它应该在眨眼之间完成。如果它需要更长的时间,还有一些其他耗时的操作,你没有在这里展示。尝试使用分析器来发现瓶颈。



下面的代码遍历树中的所有节点一次。它暂时禁用 OnChecked 事件处理程序,否则每次更改其中一个重复项的状态时,事件将再次运行。如果新的检查状态与当前的检查状态相同,则事件不会运行,因此没有无限递归的危险,但禁用事件会阻止它通过树进行大量冗余遍历。

 过程PropagateCheckState(Tree:TVirtualStringTree; Node:PVirtualNode); 
var
数据:PNodeData;
TargetID:string;
父级:PVirtualNode;
FoundOne:Boolean;
begin
数据:= Tree.GetNodeData(Node);
TargetID:= Data.SkypeID;

父级:= Tree.GetFirst;
while Assigned(Parent)do begin
//假设没有用户在同一类别中出现两次
如果父<> Tree.NodeParent [Node] then begin
FoundOne:= False;
Child:= Tree.GetFirstChild(Parent);
而Assigned(Child),而不是FoundOne do begin
Data:= Tree.GetNodeData(Child);
if Data.SkypeID = TargetID then begin
//找到重复的。与节点同步。
Tree.CheckState [Child]:= Tree.CheckState [Node];
FoundOne:= True;
end;
Child:= Tree.GetNextSibling(Child);
end;
end;
父级:= Tree.GetNextSibling(Parent);
end;
end;

过程TSkypeListEventHandler.vtSkypeChecked(Sender:TBaseVirtualTree; Node:PVirtualNode);
var
CheckedEvent:TVTChangeEvent;
begin
如果Sender.GetNodeLevel(Node)= 0,则
退出; //树级联自动更改

Assert(Sender.GetNodeLevel(Node)= 1,'Unexpected node level');
//我们将访问在TBaseVirtualTree中受保护的成员,但
//它们在TVirtualStringTree中是公开的,因此请确保我们仍在同一个树上运行
// 。
Assert(Sender = vtSkype);

CheckedEvent:= vtSkype.OnChecked;
vtSkype.OnChecked:= nil;
try
PropagateCheckState(vtSkype,Node);
finally
vtSkype.OnChecked:= CheckedEvent;
end;
end;

如果您的数据结构包含与给定用户ID相关联的所有节点的列表,更简单:

 过程PropagateCheckState(Tree:TVirtualStringTree; Node:PVirtualNode); 
var
数据:PNodeData;
i:Integer;
begin
数据:= 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< string,TList< PVirtualNode>> 。然后 PropagateCheckState 可能如下所示:

 使用Generics.Collections; 

var
UserNodes:TDictionary< string,TList< PVirtualNode>> ;;

过程PropagateCheckState(Tree:TVirtualStringTree; Node:PVirtualNode);
var
数据:PNodeData;
节点:TList< PVirtualNode> ;;
i:Integer;
begin
数据:= Tree.GetNodeData(Node);

如果不是UserNodes.TryGetValue(Data.SkypeID,Nodes)then
exit; // 奇怪的。节点的ID根本不在索引中。

for i:= 0 to Pred(Nodes.Count)do
Tree.CheckState [Nodes [i]]:= Tree.CheckState [Node];
end;

请务必每次更新 UserNodes 索引您可以在类别中添加或删除用户。


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)

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;

解决方案

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.

这篇关于如何保持多个虚拟树视图节点的检查状态同步?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆