树状数据结构(用于VirtualTreeview)

Jef*_*eff 2 delphi tree virtualtreeview data-structures

正如Rob Kennedy先生所建议的那样,我已经到了需要停止将数据存储在VCL组件中并具有"基础数据结构"的地步.

首先,这个问题是关于"我如何建立基础数据结构".:)

我的层次结构由2级节点组成.

现在,我通过循环根节点来完成我的东西,其中我循环通过rootnode的子节点,以获得我需要的东西(数据).我希望能够将所有数据存储在所谓的底层数据结构中,以便我可以使用线程轻松修改条目(我想我能够做到这一点?)

但是,当循环遍历我的条目(现在)时,结果取决于节点的Checkstate - 如果我使用的是底层数据结构,我怎么知道我的节点是否被检查,当我的数据结构循环通过时,而不是我的节点?

假设我想使用2个级别.

这将是父母:

TRoot = Record
  RootName : String;
  RootId : Integer;
  Kids : TList; //(of TKid)
End;
Run Code Online (Sandbox Code Playgroud)

那孩子:

TKid = Record
  KidName : String;
  KidId : Integer;
End;
Run Code Online (Sandbox Code Playgroud)

这基本上就是我现在所做的.评论说这不是最好的解决方案,所以我愿意接受建议.:)

我希望你理解我的问题.:)

谢谢!

Cos*_*und 8

您要求的数据结构非常简单,它非常简单我建议使用提供的窗口TTreeView:它允许将文本和ID直接存储到树的节点中而无需额外的工作.


尽管我建议使用更简单的TTreeView我将提供我对数据结构问题的看法.首先,我要使用课程,而不是记录.在非常简短的代码示例中,您将以非常不顺畅的方式混合记录和类:当您复制TRoot记录(分配记录制作完整副本,因为记录总是被视为"值")时,您不会制作树的"深层复制":完整的副本TRoot将包含与Kids:TList原始相同的内容,因为类与记录不同,是引用:您正在处理引用的值.

当您拥有包含对象字段的记录时,另一个问题是生命周期管理:记录没有析构函数,因此您需要其他机制来释放拥有的对象(Kids:TList).你可以用a代替TList,array of Tkid但是当你传递怪物记录时你需要非常小心,因为你可能会在最不期望的时候制作大量记录的深层副本.

在我看来,最谨慎的做法是将数据结构基于,而不是记录:类实例(对象)作为引用传递,因此您可以无需任何问题地移动它们.您还可以获得内置的生命周期管理(析构函数)

基类看起来像这样.您会注意到它可以用作Root或Kid,因为Root和Kid共享数据:两者都有一个名称和一个ID:

TNodeClass = class
public
  Name: string;
  ID: Integer;
end;
Run Code Online (Sandbox Code Playgroud)

如果此类用作Root,则需要一种方法来存储Kids.我假设您使用的是Delphi 2010+,因此您拥有泛型.这个类有一个列表,如下所示:

type
  TNode = class
  public
    ID: integer;
    Name: string;
    VTNode: PVirtualNode;
    Sub: TObjectList<TNode>;

    constructor Create(aName: string = ''; anID: integer = 0);
    destructor Destroy; override;
  end;

constructor TNode.Create(aName:string; anID: Integer);
begin
  Name := aName;
  ID := anID;

  Sub := TObjectList<TNode>.Create;
end;

destructor TNode.Destroy;
begin
  Sub.Free;
end;
Run Code Online (Sandbox Code Playgroud)

您可能没有立即意识到这一点,但仅此类就足以实现多级树!这里有一些代码用一些数据填充树:

Root := TNode.Create;

// Create the Contacts leaf
Root.Sub.Add(TNode.Create('Contacts', -1));
// Add some contacts
Root.Sub[0].Sub.Add(TNode.Create('Abraham', 1));
Root.Sub[0].Sub.Add(TNode.Create('Lincoln', 2));

// Create the "Recent Calls" leaf
Root.Sub.Add(TNode.Create('Recent Calls', -1));
// Add some recent calls
Root.Sub[1].Sub.Add(TNode.Create('+00 (000) 00.00.00', 3));
Root.Sub[1].Sub.Add(TNode.Create('+00 (001) 12.34.56', 4));
Run Code Online (Sandbox Code Playgroud)

您需要一个递归过程来使用以下类型填充虚拟树视图:

procedure TForm1.AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
var SubNode: TNode;
    ThisNode: PVirtualNode;

begin
  ThisNode := VT.AddChild(ParentNode, Node); // This call adds a new TVirtualNode to the VT, and saves "Node" as the payload

  Node.VTNode := ThisNode; // Save the PVirtualNode for future reference. This is only an example,
                           // the same TNode might be registered multiple times in the same VT,
                           // so it would be associated with multiple PVirtualNode's.

  for SubNode in Node.Sub do
    AddNodestoTree(ThisNode, SubNode);
end;

// And start processing like this:
VT.NodeDataSize := SizeOf(Pointer); // Make sure we specify the size of the node's payload.
                                    // A variable holding an object reference in Delphi is actually
                                    // a pointer, so the node needs enough space to hold 1 pointer.
AddNodesToTree(nil, Root);
Run Code Online (Sandbox Code Playgroud)

使用对象时,虚拟树中的不同节点可能具有与之关联的不同类型的对象.在我们的例子中,我们只需要添加的节点TNode类型,但在现实世界中,你可能有类型的节点TContact,TContactCategory,TRecentCall,都在同一个VT.您将使用is运算符来检查VT节点中对象的实际类型,如下所示:

procedure TForm1.VTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var PayloadObject:TObject;
    Node: TNode;
    Contact : TContact;      
    ContactCategory : TContactCategory;
begin
  PayloadObject := TObject(VT.GetNodeData(Node)^); // Extract the payload of the node as a TObject so
                                                   // we can check it's type before proceeding.
  if not Assigned(PayloadObject) then
    CellText := 'Bug: Node payload not assigned'
  else if PayloadObject is TNode then
    begin
      Node := TNode(PayloadObject); // We know it's a TNode, assign it to the proper var so we can easily work with it
      CellText := Node.Name;
    end
  else if PayloadObject is TContact then
    begin
      Contact := TContact(PayloadObject);
      CellText := Contact.FirstName + ' ' + Contact.LastName + ' (' + Contact.PhoneNumber + ')';
    end
  else if PayloadObject is TContactCategory then
    begin
      ContactCategory := TContactCategory(PayloadObject);
      CellText := ContactCategory.CategoryName + ' (' + IntToStr(ContactCategory.Contacts.Count) + ' contacts)';
    end
  else
    CellText := 'Bug: don''t know how to extract CellText from ' + PayloadObject.ClassName;
end;
Run Code Online (Sandbox Code Playgroud)

以下是将VirtualNode指针存储到节点实例的示例:

procedure TForm1.ButtonModifyClick(Sender: TObject);
begin
  Root.Sub[0].Sub[0].Name := 'Someone else'; // I'll modify the node itself
  VT.InvalidateNode(Root.Sub[0].Sub[0].VTNode); // and invalidate the tree; when displayed again, it will
                                                // show the updated text.
end;
Run Code Online (Sandbox Code Playgroud)

您知道有一个简单的树数据结构的工作示例.您需要"增长"这个数据结构以满足您的需求:可能性是无穷无尽的!为了给你一些想法,探索方向:

  • 您可以将其Name:string转换为虚方法GetText:string;virtual,然后创建TNode该覆盖的专用后代GetText以提供专门的行为.
  • 创建一个TNode.AddPath(Path:string; ID:Integer)允许您执行的操作Root.AddPath('Contacts\Abraham', 1);- 即,自动创建到最终节点的所有中间节点的方法,以允许轻松创建树.
  • 包括PVirtualNode进入TNode本身,所以你可以检查,而该节点是在虚拟树"检查".这将是数据G​​UI分离的桥梁.