您请求的数据结构非常简单,它非常简单,我建议使用Windows提供的
TTreeView
:它允许在树节点中直接存储文本和ID,无需进行其他操作。
尽管我建议使用更简单的
TTreeView
,但我将提供我的解决方案。首先,我将使用类而不是记录。在您非常短的代码示例中,您以非常不幸的方式混合了记录和类:当您复制
TRoot
记录时(分配记录会完全复制,因为记录始终被视为“值”),您并未制作树的“深层复制”:完整的
TRoot
副本将包含与原始记录相同的
Kids:TList
,因为类不同于记录,是引用:您正在复制引用的值。
当记录具有对象字段时,另一个问题是生命周期管理:记录没有
析构函数,因此您需要另一种机制来释放所拥有的对象(
Kids:TList
)。您可以将
TList
替换为
array of Tkid
,但是这时候在传递大型记录时需要非常小心,因为您可能会在最不希望出现的时候进行巨大记录的深层复制。
在我看来,最谨慎的做法是基于类而不是记录构建数据结构:类实例(对象)作为引用传递,因此您可以尽情地移动它们而没有问题。您还获得了内置的生命周期管理(
析构函数)。
基类如下所示。您会注意到它既可以用作根也可以用作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;
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));
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));
您需要一个递归程序来填充虚拟树视图,使用此类型:
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);
当使用对象时,您的Virtual Tree中的不同节点可能与不同类型的对象相关联。在我们的示例中,我们只添加了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)^);
if not Assigned(PayloadObject) then
CellText := 'Bug: Node payload not assigned'
else if PayloadObject is TNode then
begin
Node := TNode(PayloadObject);
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';
VT.InvalidateNode(Root.Sub[0].Sub[0].VTNode);
end;
你现在拥有了一个简单的树形数据结构的工作示例。你需要“扩展”这个数据结构以适应你的需求:可能性是无限的!为了给你一些想法,可以探索以下方向:
- 你可以将
Name:string
转换为虚方法GetText:string;virtual
,然后创建TNode
的特殊后代来覆盖GetText
以提供专门的行为。
- 创建
TNode.AddPath(Path:string; ID:Integer)
,允许您执行Root.AddPath('Contacts\Abraham', 1);
- 也就是说,自动创建所有中间节点到最终节点的方法,以便轻松创建树。
- 将
PVirtualNode
包含到TNode
本身中,这样您就可以检查在Virtual Tree中节点是否已“选中”。这将是数据GUI分离的桥梁。
TListBox
吗?我的意思是,如果你需要一个更高级的控件来显示你的数据,比如 Virtual TreeView,你应该首先在某些“底层数据结构”中拥有数据。 - Andreas Rejbrand