您请求的数据结构非常简单,我建议使用 windows 提供的 TTreeView:它允许将文本和 ID 直接存储到树的节点中,而无需额外工作。
尽管我建议使用更简单的TTreeView,但我将提供我对数据结构问题的看法。首先,我将使用 classes,而不是 records。在您非常短的代码示例中,您正在以一种非常不幸的方式混合记录和类:当您复制 TRoot 记录时(分配记录会生成完整的副本,因为记录总是被视为“值”),您'不要制作树的“深拷贝”:TRoot 的完整副本将包含与原始相同的 Kids:TList,因为类与记录不同,是引用:您正在处理引用的值。
当您拥有带有对象字段的记录时,另一个问题是生命周期管理:记录没有析构函数,因此您需要其他机制来释放拥有的对象(@ 987654326@)。您可以将TList 替换为array of Tkid,但是在传递怪物记录时您需要非常小心,因为您可能会在您最不希望的时候结束对大量记录的深度复制它。
在我看来,最谨慎的做法是将数据结构基于类,而不是记录:类实例(对象)作为引用传递,因此您可以随意移动它们想要没有问题。您还可以获得内置的生命周期管理(析构函数)
基类看起来像这样。您会注意到它可以用作 Root 或 Kid,因为 Root 和 Kid 共享数据:两者都有名称和 ID:
TNodeClass = class
public
Name: string;
ID: Integer;
end;
如果这个类被用作一个根,它需要一种方法来存储孩子。我假设您使用的是 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;
您可能不会立即意识到这一点,但仅这个类就足以实现多级树!下面是一些用一些数据填充树的代码:
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));
您需要一个递归过程来使用这种类型填充虚拟树视图:
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 类型的节点,但在现实世界中,您可能拥有 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;
下面是一个为什么将 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;
您知道有一个简单树数据结构的工作示例。您需要“增长”这个数据结构以满足您的需求:可能性是无穷无尽的!为您提供一些想法和探索方向:
- 您可以将
Name:string 转换为虚拟方法GetText:string;virtual,然后创建TNode 的专门后代,覆盖GetText 以提供专门的行为。
- 创建一个
TNode.AddPath(Path:string; ID:Integer),允许您执行Root.AddPath('Contacts\Abraham', 1); - 即自动创建所有中间节点到最终节点的方法,以便轻松创建树。
- 将
PVirtualNode 包含到TNode 本身中,这样您就可以在虚拟树中检查节点是否已“检查”。这将是数据-GUI 分离的桥梁。