I've mentioned before 你做错事了,现在你就会明白为什么了。
您正在使用树控件来存储数据。它用于显示数据。您应该有一个单独的数据结构,其唯一工作是存储您的数据。它可能是tree,但不是树控件。您可以将这种树形数据结构提供给处理表单,因为它不需要显示节点。
当您想要显示您的数据时,您需要了解树的第一级中有多少个节点,然后将树控件的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数组,而不是树形控件。