【问题标题】:VirtualStringTree Correct/recommended useVirtualStringTree 正确/推荐使用
【发布时间】:2011-11-29 06:33:55
【问题描述】:

我已经使用 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。网格

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 处于活动状态。以这种方式使用它会有任何内存优势/性能优势吗?

在上述情况下,我可以看到网格示例的差异远小于(如果有的话)层次结构 - 因为所有节点都需要在填充时进行初始化。

【问题讨论】:

  • 您使用这么多内存的主要原因之一是您将数据复制到每个节点;如果您只存储一个引用(例如 ID(在您的示例中为整数),您可以在 OnGetText 等事件的记录集中寻找正确的记录。这样您将节省 大量 内存和坚持虚拟范式。
  • 如果您认为我在完成记录集后释放了记录集,会是这种情况吗?所以要坚持虚拟范式,我需要随时准备好记录集。我当前的方法在填充树后完成并释放它。我需要用来在记录集中查找记录的 Locate 的性能如何?
  • 我个人使用内存数据集将数据保存在内存中,这对字符串非常有效(每个单元格没有单独的字符串对象;字符串的开销对于短字符串(例如 memmanager header ,字符串标题和零终止符))。性能完美;绘制网格时通常会消耗不到 1% 的额外 CPU 功率。

标签: performance delphi virtualtreeview tvirtualstringtree


【解决方案1】:

TQuery 提供对记录的类似数组的访问。

如果您查看文档,您可以使用RecNo 属性跳转到每条记录,也可以使用Fields[i](通过它的索引)跳转到每个字段。

使用TVirtualStringTree 作为虚拟模式连接数据库没有任何阻碍。

一旦您执行查询,内存中的数据就会以TDataSet 的形式出现,那么为什么不将它用作数据容器呢?

PS:使用索引访问字段比FieldByName更快。

【讨论】:

    【解决方案2】:

    不鼓励AddChild() 的原因是它打破了组件的虚拟范式——您创建所有节点,即使您可能永远不需要它们。假设查询返回 100 条记录,但树同时显示 10 个节点。现在,如果用户从不向下滚动,您就浪费了 90 个节点的资源,因为它们从不需要(不可见)。因此,如果您担心内存使用情况,AddChild() 是个坏主意。另一件事是,如果结果集很大,填充树需要时间,并且您的应用程序此时没有响应。使用虚拟方式(RootNodeCountOnInitNode)时,树会立即“准备就绪”,用户不会遇到任何延迟。

    再一次,如果(相对)较小的结果集使用AddChild() 可能是最好的选择 - 它允许您在一个短事务中加载数据。即在树结构的情况下,当用户展开父节点时,一次加载整个“级别”是有意义的。

    将 DB 与 VT 一起使用有点棘手,因为 DB 查询也是特殊资源,有其自身的限制(您希望事务保持简短,它相对较慢,DB 可能仅支持一种方式游标等)。
    所以我想说这是你必须根据用例和数据量来决定每个案例的事情,即

    • 小结果集可以使用AddChild() 一次性加载或加载到内部数据结构中,然后通过 VT 的事件使用它;
    • 一次加载一个关卡可能是树的良好折衷方案;
    • 如果批量加载非常大的结果集,可能会提供良好的性能和内存使用折衷,但会增加管理 VT 的代码的复杂性。

    【讨论】:

    • +1 感谢您的回答。似乎我的大部分树都应该没问题,因为它们只从数据库加载
    • 刚刚尝试使用 rootnodecount 和 TQuery。不像我想象的那么简单,因为 TQuery 不像数组那样提供对记录的随机访问。所以看起来我会坚持我现在得到的东西
    • 我有一个自定义的 TQuery 类控件,我编写了该控件来查询数据库并将记录集保存在内存中。我尝试使用 OnInit,但似乎无法使其工作。在跟踪对 OnInit 和 GetText 的调用时,我注意到 OnInit 被称为 2x 然后是 GetText。在我的 OnInit 中,我的 TQuery 类控件支持 ->Next();所以每次调用 OnInit 时,我都会用数据填充 VST Data * 并调用 ->Next()。我最终得到了一棵重复信息树。您也可以指出任何有效的 C++ 示例吗?文档谈到 OnInit 来调整单元格的大小,大多数东西都在 obj-Pascal 中。
    猜你喜欢
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    相关资源
    最近更新 更多