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

问题描述

我已经到了需要停止将数据存储在 VCL 组件中的地步,并且有一个底层数据结构",如 先生.罗伯·肯尼迪建议.

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". :)

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

My hierachy consists of 2 levels of nodes.

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

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

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

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?

假设我想使用 2 个级别.

Let's say I wanted to use 2 levels.

这将是父级:

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

还有孩子:

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). :)

谢谢!

推荐答案

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

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.

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

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.

当您拥有带有对象字段的记录时,另一个问题是生命周期管理:记录没有析构函数,因此您需要其他机制来释放拥有的对象().您可以将 TList 替换为 Tkid 数组,但是在传递怪物记录时需要非常小心,因为您可能会在您最意想不到的情况下制作大量记录的深层副本.

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)

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

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;

如果这个类用作根,它需要一种方法来存储孩子.我假设您使用的是 Delphi 2010+,因此您拥有泛型.这个类,带有一个列表,看起来像这样:

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

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

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;

这是为什么要存储指向节点实例的 VirtualNode 指针的示例:

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:

  • 您可以将 Name:string 转换为虚方法 GetText:string;virtual,然后创建覆盖 TNode 的专门后代code>GetText 以提供专门的行为.
  • 创建一个 TNode.AddPath(Path:string; ID:Integer) 允许你做 Root.AddPath('ContactsAbraham', 1); -也就是说,一种自动创建所有中间节点到最终节点的方法,以允许轻松创建树.
  • PVirtualNode 包含到 TNode 本身中,这样您就可以检查节点是否在虚拟树中被检查".这将是数据-GUI 分离的桥梁.
  • 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('ContactsAbraham', 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)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-31 19:32