VirtualStringTree 正确/推荐使用
我已经使用 virtualstringtree 一段时间了。我将它用于两个不同的用途,首先作为普通树来选择、显示数据,其次作为网格来显示 SQL 语句的输出。
我加载到树中的所有数据都来自数据库。对于树示例,我有一个 ParentId 字段来区分层次结构,对于网格示例,我只需使用 SQL 语句以及每个树的自定义记录(这是唯一的)。
我的问题涉及填充树的首选/最佳方式。我从 VST 文档中了解到,您应该将 onInitNode 事件与 rootnodecount 一起使用。然而,我发现使用 AddChild() 方法非常相似,尽管不鼓励这样做。
让我展示一些(简化的)示例:
1.等级制度
type PData = ^rData;
rData = packed record
ID : Integer;
ParentID : Integer;
Text : WideString;
end;
procedure Loadtree;
var Node : PVirtualNode;
Data : PData;
begin
Q1 := TQuery.Create(Self);
try
Q1.SQL.Add('SELECT * FROM Table');
Q1.Open;
Q1.Filter := 'ParentID = -1'; //to get the root nodes
Q1.Filtered := True;
while not Q1.Eof do
begin
Node := VST.AddChild(nil);
Data := VST.GetNodeData(Node);
Data.ID := Q1.Fields[fldID].AsInteger;
Data.ParentID := Q1.Fields[fldParentID].AsInteger;
Data.Text := Q1.Fields[fldText].AsString;
//now filter the query again to get the children of this node
PopulateChildren(Data.ParentID,Node); //add children to this node and do it recursively
Q1.Next;
end;
finally
Q1.free;
end;
end;
2. Grid
procedure LoadGrid;
var Node : PVirtualNode;
Data : PData;
begin
Q1 := TQuery.Create(self);
try
Q1.SQL.Add('SELECT * FROM Table');
Q1.Open;
while not Q1.eof do
begin
Node := VST.AddChild(nil);
Data.ID := Q1.Fields[fldID].AsInteger;
Data.Text := Q1.Fields[fldText].AsString;
Q1.Next;
end;
finally
Q1.Free;
end;
end;
所以本质上我绕过了 RootNodeCount 和 OnInitNode 方法/属性,并使用老式的方法将节点添加到树中。看起来效果很好。请注意,在示例中我在运行时创建和销毁查询。
我开始以这种方式使用树的原因是我可以一次加载树中的所有数据,然后在使用完后释放 TQuery。我在想,无论保持 TQuery 处于活动状态/创建状态,我仍然需要使用我的 rData 记录来存储数据,因此如果我不销毁 TQuery,则会占用更多内存。目前,我的应用程序在完全加载时使用大约 250+MB,并且当我运行 SQL 报告并将其显示在 VST 中时可能会增加。当我运行包含 20000 多个节点和 50 多个列的 SQL 报告时,我发现它使用了大约 1GB 的内存。我想知道我使用 VST 的方式在最小化内存使用方面是否不正确?
我最好创建一个树的生命周期查询并使用 onInitNode 事件吗?这样,当树请求数据时,它会使用 onInitNode/OnInitChildren 事件从 TQuery 获取数据(即树的纯虚拟范例)?因此,我需要在表单持续时间内保持 TQuery 处于活动状态。以这种方式使用它会带来任何内存优势/性能优势吗?
在上述情况下,我可以看到网格示例的差异将远小于层次结构(如果有的话)——因为所有节点在填充时都需要初始化。
I have been using virtualstringtree for a while now. I use it for two different things, first a s a normal tree for selecting ,displaying data and secondly as a grid to show outputs from SQL statements.
All my data loaded in to the trees is from a database. For the tree example, i have a parentId field to distinguish the heirarchy, and for the grid examples i simply use an SQL statement with a customized record for each tree (that is unique).
My questions relates to the preferred/best way to populate the tree. I have read from the VST docs that you should use the onInitNode event along with rootnodecount. However i have found that using the AddChild() method to be very similar, even though it is not encouraged.
Let me show some (simplified) examples:
1. Heirarchy
type PData = ^rData;
rData = packed record
ID : Integer;
ParentID : Integer;
Text : WideString;
end;
procedure Loadtree;
var Node : PVirtualNode;
Data : PData;
begin
Q1 := TQuery.Create(Self);
try
Q1.SQL.Add('SELECT * FROM Table');
Q1.Open;
Q1.Filter := 'ParentID = -1'; //to get the root nodes
Q1.Filtered := True;
while not Q1.Eof do
begin
Node := VST.AddChild(nil);
Data := VST.GetNodeData(Node);
Data.ID := Q1.Fields[fldID].AsInteger;
Data.ParentID := Q1.Fields[fldParentID].AsInteger;
Data.Text := Q1.Fields[fldText].AsString;
//now filter the query again to get the children of this node
PopulateChildren(Data.ParentID,Node); //add children to this node and do it recursively
Q1.Next;
end;
finally
Q1.free;
end;
end;
2. Grid
procedure LoadGrid;
var Node : PVirtualNode;
Data : PData;
begin
Q1 := TQuery.Create(self);
try
Q1.SQL.Add('SELECT * FROM Table');
Q1.Open;
while not Q1.eof do
begin
Node := VST.AddChild(nil);
Data.ID := Q1.Fields[fldID].AsInteger;
Data.Text := Q1.Fields[fldText].AsString;
Q1.Next;
end;
finally
Q1.Free;
end;
end;
So essentially i am bypassing the RootNodeCount and OnInitNode methods/property and using the old fashioned way to add nodes to the tree. It seems to work fine. Note in the example i create and destroy my queries at runtime.
The reason i started using the tree in this manner is that i could load all the data in the tree once and then free the TQuery after i had finished using it. I was thinking that regardless of keeping the TQuery alive/created i would still need to use my rData record to store the data, hence using up more memory if i did not destroy the TQuery. Currently my application uses about 250+MB when fully loaded, and can increase when i run SQL reports and display them in the VST. I have seen it use about 1GB of ram when i have run a SQL report with 20000+ nodes and 50+ columns. What i would like to know if the way i am using the VST incorrect with regards to minimizing memory usage?
Would i be better off creating a query for the lifetime of the tree and using the onInitNode event? So that when data is requested by the tree it fetches it from the TQuery using the onInitNode/OnInitChildren event (i.e. the pure virtual paradigm of the tree)? Thus i would need to keep the TQuery alive for the duration of the form. Would there be any memory benefit/performance benefit in using it this way?
In the above situations the i can see the difference for the grid example would be far less (if any) than the heirarchy - due to the fact that all nodes need to be initialized when populated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不鼓励使用
AddChild()
的原因是它破坏了组件的虚拟范例 - 您创建所有节点,即使您可能永远不需要它们。假设查询返回 100 条记录,但树当时显示 10 个节点。现在,如果用户从不向下滚动,那么您就浪费了 90 个节点的资源,因为它们永远不需要(不可见)。因此,如果您担心内存使用情况,AddChild()
不是个好主意。另一件事是,如果结果集很大,填充树需要时间,并且您的应用程序当时不会响应。当使用虚拟方式(RootNodeCount
和OnInitNode
)时,树立即“准备就绪”,用户不会遇到任何延迟。话又说回来,如果结果集(相对)较小,则使用 AddChild() 可能是最好的选择 - 它允许您在一个短事务中加载数据。即,在树结构的情况下,当用户展开父节点时立即加载整个“级别”是有意义的。
将 DB 与 VT 一起使用有点棘手,因为 DB 查询也是特殊资源,有其自身的约束(您希望保持事务简短,它相对较慢,DB 可能只支持单向游标等)。
所以我想说,你必须根据用例和数据量来决定每个具体情况,即
AddChild()
一次性加载或加载到内部数据结构,然后通过VT的事件使用它;The reason why
AddChild()
is not encouraged is that it breaks the virtual paradigm of the component - you create all the nodes, even though you may never need them. Say the query returns 100 records but the tree shows 10 nodes at the time. Now if the user nevers scrolls down you have wasted resources for 90 nodes as they are never needed (don't became visible). So if you're worried about memory usage,AddChild()
is bad idea. Another thing is that if the resultset is big, populating the tree takes time and your app isn't responsive at that time. When using virtual way (RootNodeCount
andOnInitNode
) the tree is "ready" immediately and user doesn't experience any delay.Then again, in case of (relatively) small resultsets using
AddChild()
might be the best option - it would allow you to load data in one short transaction. Ie in case of tree structure it would make sense to load whole "level" at once when user expands the parent node.Using DB with VT is a bit tricky as the DB query is special resource too, with it's own constraints (you want to keep transactions short, it is relatively slow, the DB might only support one way cursor etc).
So I'd say it is something you have to decide on each individual case, depending on the use case and amount of data, ie
AddChild()
or into internal data structure and then using it throught VT's events;TQuery 提供类似数组的记录访问。
如果您检查文档,则可以使用
RecNo
属性跳转到每个记录,并且可以使用Fields[i]
(通过索引)跳转到每个字段。使用
TVirtualStringTree
作为与数据库连接的虚拟模式是没有阻碍的。一旦执行查询,数据就以
TDataSet
的形式在内存中可用,那么为什么不将其用作数据容器呢?PS:使用索引访问字段比
FieldByName
更快。TQuery provide array-like access to records.
If you check with the documentation you can jump to each record using the
RecNo
property and you can jump to each field usingFields[i]
(by it's index).There is no holding back using
TVirtualStringTree
as virtual mode connecting with database.Once you execute the query the data is available in memory as
TDataSet
anyway so why don't use it as a data container?PS: Accessing fields using their index is faster than
FieldByName
.