我之前提到过你做错了,现在你会明白为什么。
您正在使用树控件来存储数据。它用于显示数据。您应该有一个单独的数据结构,其唯一的工作就是存储您的数据。它可能是树,但不是树控件。这是您提供给处理表单的树数据结构,因为它不需要显示节点。
当你想显示你的数据时,你会发现在你的树的第一层有多少个节点,然后你将你的树控件的RootNodeCount
属性设置为那个数字。控件将分配那么多节点——不要调用AddNewNode
像填充控件这样的批量操作。当树要在屏幕上显示一个之前未显示的节点时,它将触发OnInitNode
事件处理程序。这是您初始化节点并将其与数据结构中的值相关联的地方。树控件会告诉你它正在初始化哪个节点——通过一个PVirtualNode
指针和一个索引来告诉你它是哪个节点,相对于它的父节点。当你初始化节点时,你告诉树节点是否有任何子节点。你不需要告诉它有多少孩子们呢;如果控件想知道,它会问你另一个事件。
既然您已经将数据与数据的单纯呈现分开,您就不必再担心呈现者的生命周期与数据的生命周期不同。处理表单可以处理数据而无需考虑树视图控件是否仍然存在,因为树视图控件从一开始就没有拥有数据。
也可以看看:
你说过你只有一级节点。没关系。只有一层的树通常称为列表。您可以使用多种方法来跟踪列表。最简单的是数组。您也可以使用TList
,或者您可以构建自己的链表。这个例子将使用一个数组,因为我想专注于树控件。
让我们假设每个节点的数据由一条记录表示TData
,所以你有一个数组:
var
Data: array of TData;
在您为数组加载信息后,无论您拥有什么来源,您都可以准备填充树控件了。这就像两行代码一样简单(一行,如果控件开始为空):
Tree.ResetNode(nil); // remove all existing nodes from tree
Tree.RootNodeCount := Length(Data); // allocate new nodes for all data
当树确定它需要有关任何这些节点的更多信息时,它将通过触发OnInitNode
事件开始。您不需要为该事件做太多事情,因为节点的Index
字段足以让我们找到TData
与任何给定树节点对应的记录。
procedure TJeffForm.TreeInitNode(Sender: TBaseVirtualTree;
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
Assert(Node.Index < Length(Data), 'More nodes than data elements!?');
InitialStates := []; // If the node had children, or if it should be
// initially disabled, you'd set that here.
end;
当树想要绘制自己时,它会通过触发OnGetText
事件询问您为每个可见节点显示什么文本。节点的Index
字段告诉您它是哪个项目,相对于其父项。(由于您只有一个列表,因此该索引对应于列表中的索引。)
procedure TJeffForm.TreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: UnicodeString);
begin
if TextType = ttStatic then
exit;
case Column of
NoColumn,
0: CellText := Data[Node.Index].Name;
1: CellText := 'Second column';
else
Assert(False, 'Requested text for unexpected column');
end;
end;
上面我假设TData
有一个名为的字符串字段Name
,这就是我们应该在主列中显示的内容。如果树要求第二列之后的任何文本,我们将得到一个断言失败,表明我们还没有准备好发布产品。
请注意我们如何使用节点索引来查看完全独立的数组数据结构。我们可以完全销毁树控件,数据仍然存在。当你的处理表单需要处理数据时,给它Data
数组,而不是树控件。