树状数据结构(用于VirtualTreeview) [英] Tree-like Datastructure (for use with VirtualTreeview)

查看:144
本文介绍了树状数据结构(用于VirtualTreeview)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经到了我需要停止将数据存储在VCL组件中的位置,并且具有底层数据结构,如先生。 Rob Kennedy建议。



首先,这个问题是关于如何进行底层数据结构。 :)



我的等级由2个节点组成。



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



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



我想说,我想使用2个级别。



将是父母:

  TRoot = Record 
RootName:String;
RootId:Integer;
孩子们:TList; //(TKid)
结束;

孩子:

  TKid = Record 
KidName:String;
KidId:Integer;
结束;

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



希望您了解我的问题。 :



谢谢!

解决方案

请求很简单,很简单,我建议使用Windows提供的 TTreeView :它允许将文本和ID直接存储在树的节点中,无需额外的工作。






尽管我建议使用更简单的 TTreeView 提供我的数据结构问题。首先我要使用,而不是记录。在您的很短的代码示例中,您以非常不正确的方式混合记录和类:当您制作 TRoot 记录的副本(分配记录创建完整副本,因为记录被视为价值观),您不是树的深层复制: TRoot 的完整副本将包含相同的孩子:TList 作为原始的,因为与记录不同的类是参考:你正在应对引用的价值。



当你有一个对象字段的记录时,其他问题是生命周期管理:一个记录没有一个析构函数,所以你需要一个其他机制来释放所有的对象(孩子:TList )。您可以用 Tkid 数组替换 TList ,但您需要非常<强化>小心,因为当你最不希望的时候,你可能会收到巨大的记录的深刻副本。



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



基类将如下所示。您会注意到它可以用作根或孩子,因为Root和Kid共享数据:两者都有一个名称和一个ID:

  TNodeClass = class 
public
名称:string;
ID:整数;
结束

如果此类用作根,则需要一种存储孩子的方法。我假设你在Delphi 2010+,所以你有泛型。这个课程,包含一个列表,如下所示:

 键入
TNode = class
public
ID:integer;
名称:string;
VTNode:PVirtualNode;
子:TObjectList< TNode> ;;

构造函数Create(aName:string =''; anID:integer = 0);
析构函数覆盖
结束

构造函数TNode.Create(aName:string; anID:Integer);
begin
名称:= aName;
ID:= anID;

Sub:= TObjectList< TNode> .Create;
结束

析构函数TNode.Destroy;
begin
Sub.Free;
结束

您可能不会立即意识到这一点,但是这个类本身就足以实现一个多层次的树!以下是一些使用一些数据填充树的代码:

 根:= TNode.Create; 

//创建联系人叶
Root.Sub.Add(TNode.Create('Contacts',-1));
//添加一些联系人
Root.Sub [0] .Sub.Add(TNode.Create('Abraham',1));
Root.Sub [0] .Sub.Add(TNode.Create('Lincoln',2));

//创建Recent Calls叶
Root.Sub.Add(TNode.Create('Recent Calls',-1));
//添加一些最近的调用
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));

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

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

begin
ThisNode:= VT.AddChild(ParentNode,Node); //此调用向VT添加一个新的虚拟节点,并将节点保存为有效负载

Node.VTNode:= ThisNode; //保存PVirtualNode以备将来参考。这只是一个例子,
//相同的TNode可能在同一个VT中多次注册,
//所以它将与多个PVirtualNode相关联。

为Node.Sub中的SubNode
AddNodestoTree(ThisNode,SubNode);
结束

//开始处理如下:
VT.NodeDataSize:= SizeOf(Pointer); //确保我们指定节点的有效载荷的大小。
//在Delphi中持有对象引用的变量实际上是
//一个指针,因此节点需要足够的空间来保存1个指针。
AddNodesToTree(nil,Root);

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

  procedure TForm1.VTGetText(Sender:TBaseVirtualTree; Node:PVirtualNode; 
Column:TColumnIndex; TextType:TVSTTextType; var CellText:string);
var PayloadObject:TObject;
节点:TNode;
联系人:TCcontact;
联系人类别:TContactCategory;
begin
PayloadObject:= TObject(VT.GetNodeData(Node)^); //将节点的有效内容提取为TObject,以便
//我们可以在继续之前检查它的类型。
如果没有分配(PayloadObject)然后
CellText:='错误:节点有效负载未分配'
否如果PayloadObject是TNode然后
开始
节点:= TNode PayloadObject); //我们知道这是一个TNode,将其分配给适当的var,以便我们可以轻松地使用它
CellText:= Node.Name;
end
如果PayloadObject为TContact,则
begin
联系人:= TContact(PayloadObject);
CellText:= Contact.FirstName +''+ Contact.LastName +'('+ Contact.PhoneNumber +')';
end
如果PayloadObject为TContactCategory,则
begin
ContactCategory:= TContactCategory(PayloadObject);
CellText:= ContactCategory.CategoryName +'('+ IntToStr(ContactCategory.Contacts.Count)+'contacts)';
end
else
CellText:='Bug:不知道如何从'+ PayloadObject.ClassName提取CellText;
结束

这里是一个示例,为了将VirtualNode指针存储到您的节点实例:

  procedure TForm1.ButtonModifyClick(Sender:TObject); 
begin
Root.Sub [0] .Sub [0] .Name:=别人; //我将修改节点本身
VT.InvalidateNode(Root.Sub [0] .Sub [0] .VTNode); //并使树无效;当再次显示时,它将
//显示更新的文本。
结束






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




  • 您可以转动 Name:string 进入虚拟方法 GetText:string; virtual 然后创建专门的后代 TNode ,覆盖 GetText 提供专门的行为。

  • 创建一个 TNode.AddPath(Path:string; ID:Integer)允许你做 Root.AddPath('Contacts\Abraham',1); - 这是一种自动创建所有中间节点到最终的方法节点,以便轻松创建树。

  • PVirtualNode 包含到 TNode 本身,所以你可以检查,而不是在虚拟树中检查节点。这将是数据 - GUI分离的桥梁。


I have come to the point where I need to stop storing my data in a VCL component, and have an "underlying datastructure", as Mr. Rob Kennedy suggested.

First of all, this question is about "how do I make an underlying datastructure". :)

My hierachy consists of 2 levels of nodes.

Right now, I go thru my stuff by looping rootnodes, wherein I loop thru the rootnode's childnodes, to get what I need (Data). I would love to be able to store all my data in a so-called Underlying Datastructure, so that I can easily modify the entries using threads (I suppose I am able to do that?)

However, when looping through my entries (right now), the results are depending on the node's Checkstate - if I am using an underlying data structure, how do I know if my node is checked or not, when its my datastructure I loop thru, and not my nodes?

Let's say I wanted to use 2 levels.

This would be the Parent:

TRoot = Record
  RootName : String;
  RootId : Integer;
  Kids : TList; //(of TKid)
End;

And the kid:

TKid = Record
  KidName : String;
  KidId : Integer;
End;

Thats basically what I do now. Comments state that this is not the best solution, so I am open to suggestions. :)

I hope you understand my question(s). :)

Thanks!

解决方案

The data structure you're requesting is very simple, it's so simple I'd recommend using the windows-provided TTreeView: it allows storing the text and an ID straight into the tree's node with no additional work.


Despite my recommendation to use the simpler TTreeView I'm going to provide my take on the data structure problem. First of all I'm going to use classes, not records. In your very short code sample you're mixing records and classes in a very unfrotunate way: When you make a copy of the TRoot record (assigning records makes complete copies, because records are allways treated as "values"), you're not making a "deep copy" of the tree: The complete copy of TRoot will contain the same Kids:TList as the original, because classes, unlike records, are references: you're coping the value of the reference.

An other problem when you have a record with an object field is life cycle management: A record doesn't have an destructor so you'll need an other mechanism to free the owned object (Kids:TList). You could replace the TList with an array of Tkid but then you'll need to be very careful when passing the monster record around, because you might end making deep copies of huge records when you least expect it.

In my opinion the most prudent thing to do is to base the data structure on classes, not records: class instances (objects) are passed around as references, so you can move them around all you want with no problems. You also get built-in life cycle management (the destructor)

The base class would look like this. You'll notice it can be used as either the Root or the Kid, because both Root and Kid share data: The both have a name and an ID:

TNodeClass = class
public
  Name: string;
  ID: Integer;
end;

If this class is used as an Root, it needs a way to store the Kids. I assume you're on Delphi 2010+, so you have generics. This class, complete with a list, looks like this:

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;

You might not immediately realize this, but this class alone is enough to implement a multi-level tree! Here's some code to fill up the tree with some data:

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

You need a recursive procedure to fill the virtual tree view using this type:

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

When using objects, different nodes in your Virtual Tree may have different types of objects associated with them. In our example we're only adding nodes of TNode type, but in the real world you might have nodes of types TContact, TContactCategory, TRecentCall, all in one VT. You'll use the is operator to check the actual type of the object in the VT node like this:

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;

And here's an example why to store VirtualNode pointer to your node instances:

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;


You know have an working example for a simple tree data structure. You'll need to "grow" this data structure to suite your needs: the possibilities are endless! To give you some ideas, directions to explore:

  • You can turn the Name:string into a virtual method GetText:string;virtual and then create specialized descendants of TNode that override GetText to provide specialized behavior.
  • Create a TNode.AddPath(Path:string; ID:Integer) that allows you to do Root.AddPath('Contacts\Abraham', 1); - that is, a method that automatically creates all intermediary nodes to the final node, to allow easy creation of the tree.
  • Include an PVirtualNode into TNode itself so you can check rather the Node is "checked" in the Virtual Tree. This would be a bridge of the data-GUI separation.

这篇关于树状数据结构(用于VirtualTreeview)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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