您请求的数据结构非常简单,我建议使用 windows-provided TTreeView
:它允许将文本和 ID 直接存储到树的节点中,无需额外工作。
尽管我建议使用更简单的TTreeView
,但我将提供我对数据结构问题的看法。首先,我将使用类,而不是记录。在您非常短的代码示例中,您以一种非常不幸的方式混合记录和类:当您制作记录的副本时TRoot
(分配记录会产生完整的副本,因为记录总是被视为“值”),您没有制作树的“深层副本”: 的完整副本TRoot
将包含与Kids:TList
原始副本相同的内容,因为类与记录不同,是引用:您正在处理引用的值。
当您拥有带有对象字段的记录时,另一个问题是生命周期管理:记录没有析构函数,因此您需要其他机制来释放拥有的对象(Kids:TList
)。您可以TList
用 an替换,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
类型节点,但在现实世界中,您可能在一个 VT 中拥有TContact
、TContactCategory
、 、 类型的节点。TRecentCall
您将使用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);
——即自动创建所有中间节点到最终节点的方法,以允许轻松创建树。
- 将 an 包含
PVirtualNode
在TNode
自身中,以便您可以检查节点是否在虚拟树中“检查”。这将是数据-GUI 分离的桥梁。