【问题标题】:Fast scrolling in Delphi's Virtual Treeview在 Delphi 的 Virtual Treeview 中快速滚动
【发布时间】:2010-02-06 23:04:56
【问题描述】:

[这是之前发布的问题的更新版本,之前的标题是在 Delphi 的虚拟树视图中按索引选择节点。]

在一天的大部分时间之后,我相信我已经让 Virtual Treeview 组件(功能强大但复杂)以简单的两表数据感知方式工作。

现在,我尝试简单地选择第 1,512 个(例如)顶级节点。除了获取第一个顶级节点然后在循环中调用 GetNextSibling 1,511 之外,我看不到任何其他方法。

这似乎是不必要的。有没有更简单的方法?

更新

因为初始化我的树中的节点需要访问数据库,所以在启动时初始化所有节点是不可行的。当用户从没有选择记录的表单开始时,这很好。当用户在树中滚动时,会填充足够的节点以将当前窗口显示到树中,并且性能很好。

当用户以对话模式启动表单并选择了数据库记录时,我必须在用户看到表单之前将树推进到该节点。这是一个问题,因为如果记录在树的末尾,我从第一个节点遍历树可能需要十秒钟。每次我可以 GetNextSibling() 时,都会初始化一个节点,即使这些节点中的绝大多数都没有显示给用户。我宁愿将这些节点的初始化推迟到它们对用户可见的时候。

我知道一定有更好的方法,因为如果我打开没有选择记录的树并使用垂直滚动条在一次操作中移动到树的中间,那么会显示正确的节点 无需初始化我跳过的节点。

这是我在打开选择记录的树时想要达到的效果。我知道我想去的节点的索引,但是如果我不能通过索引到达那里,我可以在树上进行二进制搜索,假设我可以前后跳转一些节点(类似于直接滚动到树的中间)。

或者,也许我可以对树视图进行一些状态设置,这将使我在遍历网格时未初始化中间节点。我已经尝试过开始/结束更新,但似乎没有奏效。

【问题讨论】:

    标签: delphi virtualtreeview


    【解决方案1】:

    要在不初始化的情况下获取节点的兄弟节点,只需使用NextSibling 指针(参见TVirtualNode 的声明)。

    【讨论】:

    • 完美运行,迭代 9,000 多个节点的明显时间为零。
    【解决方案2】:

    树控件的结构就像您在计算机科学课程中学习的经典树一样。从树根到第 1512 个孩子的唯一方法是逐个遍历链接。不管是自己做还是使用树形控件的方法,都还是要那样做。我没有看到控件本身提供任何内容,因此您可以使用此功能:

    function GetNthNextSibling(Node: PVirtualNode; N: Cardinal;
      Tree: TBaseVirtualTree = nil): PVirtualNode;
    begin
      if not Assigned(Tree) then
        Tree := TreeFromNode(Node);
      Result := Node;
      while Assigned(Result) and (N > 0) do begin
        Dec(N);
        Result := Tree.GetNextSibling(Result);
      end;
    end;
    

    如果您发现自己经常这样做,您可能希望将自己设为索引。它可以像创建一个 PVirtualNode 指针数组并将所有顶级值存储在其中一样简单,因此您可以从中读取第 1512 个值。树控件本身不需要这样的数据结构,所以它不维护一个。

    你也可以重新考虑是否需要这样的数据结构。你真的需要像那样通过索引访问节点吗?或者可以改为维护一个PVirtualNode 指针,因此它相对于树中其余节点的位置不再重要(这意味着您可以对它们进行排序而不会丢失对所需节点的引用)?

    【讨论】:

    • 我确实需要按索引访问树。这是一个从一对相关数据集中选择记录的对话框。如果在已选择值的情况下打开对话框,我希望对话框显示当前选择的节点。我通过对数据集进行定位来找到该记录,记录号为我提供了我所追求的节点的索引。我可以按照您的建议构建自己的索引,但我有点希望将它内置到组件中(我正在遭受旧思想的困扰,直接来自 Delphis TTreeView.Nodes 范例)。
    【解决方案3】:

    您在更新中写道:

    我知道一定有更好的方法,因为如果我打开没有选择记录的树并使用垂直滚动条在一次操作中移动到树的中间,那么会显示正确的节点 无需初始化我跳过的节点。

    这里有区别,因为垂直滚动会改变显示在客户端位置0的逻辑Y坐标。控件计算滚动条位置和滚动范围的偏移量,然后计算在控件顶部哪个节点可见.只有当滚动到视图中的区域需要绘制时,节点才会再次初始化。

    如果你有一个节点的Y坐标,你可以通过调用得到节点指针

    function TBaseVirtualTree.GetNodeAt(X, Y: Integer; Relative: Boolean;
      var NodeTop: Integer): PVirtualNode;
    

    节点的 Y 坐标是之前所有可见节点的高度之和。假设您没有折叠节点(因此它是一个平面记录列表,或者所有具有子节点的节点都已展开)并且它们都具有默认高度,这很容易。这段代码应该是一个很好的起点:

    procedure TForm1.SelectTreeNode(AIndex: integer; ACenterNodeInTree: boolean);
    var
      Y, Dummy: integer;
      Node: PVirtualNode;
    begin
      Y := Round((AIndex + 0.5) * VirtualStringTree1.DefaultNodeHeight);
      Node := VirtualStringTree1.GetNodeAt(0, Y, False, Dummy);
      if Node <> nil then begin
        Assert(Node.Index = AIndex);
        VirtualStringTree1.ScrollIntoView(Node, ACenterNodeInTree);
        VirtualStringTree1.Selected[Node] := True;
        VirtualStringTree1.FocusedNode := Node;
      end;
    end;
    

    【讨论】:

    • 谢谢。我认为这不会那么容易,因为我不能确定中间节点的展开状态,但我确信我可以使用这种技术作为通过树进行增量或二分搜索的基础。缺少的元素是按像素而不是节点向前滚动的想法。
    猜你喜欢
    • 2012-03-09
    • 2017-04-04
    • 2017-05-30
    • 2010-10-30
    • 1970-01-01
    • 2011-02-23
    • 2020-12-17
    • 1970-01-01
    • 2014-12-14
    相关资源
    最近更新 更多