我应该如何更新 Virtual TreeView 中的节点?

ala*_*ncc 1 delphi virtualtreeview

我正在使用 Delphi XE3 和 Virtual TreeView。

我想用Virtual TreeView来实现一棵树,当点击“开始”按钮时,程序会递归搜索一个驱动器下的所有文件和文件夹,然后将它们一一添加到树中,就像Windows资源管理器一样。此外,应该有一个数字指示文件夹下的文件和子文件夹的数量,使用静态文本如下:

VirtualTreeView - 同一节点中不同颜色的文本

在我看来,我发现有时数字没有正确更新。

因此,每当文件/子文件夹的数量发生变化时,我认为可以通过以下方式刷新节点:

  1. 调用 tvItems.Change(PNode) 来更新节点。

  2. 调用 tvItems.InvalidateNode(PNode)。

  3. 调用 tvItems.RepaintNode(PNode)。

  4. 调用 tvItems.UpdateAction。

但是,1 是无法调用的受保护方法。2 和 3 都可以,但不知道哪个更适合更新。4 没有记录,不知道如何称呼它。

Ian*_*oyd 5

基本思想是,如果幕后发生某些变化,则需要重新绘制树。这意味着下次树绘制自身时,它将使用新的基础值。

如果您的树坐在屏幕上:

在此处输入图片说明

您可以简单地调用:

tvItems.Invalidate;
Run Code Online (Sandbox Code Playgroud)

这告诉 Windows整个树现在“无效”并且需要重新绘制。我可以表示这个“无效”区域,它将在下次树绘制自身时更新:

在此处输入图片说明

这很好,正确,并且可以完美地工作。

性能改进

很多时候,这是完全合理的,迫使整个树重绘所有的本身。

但也可以开始优化事物。如果您知道只有 Windows 控件的某个区域是"invalid",那么您可以使该部分无效。

执行此操作的 Windows 函数是InvalidateRect

InvalidateRect(tvItems.Handle, Rect(13, 18, 30, 38), True); 
Run Code Online (Sandbox Code Playgroud)

这将使 13,18 处的 30x38 正方形无效:

在此处输入图片说明

事实上TWinControl.Invalidate,所做的就是转身并调用 Windows InvalidateRect函数:

//true means to also trigger an erase of the background
InvalidateRect(Self.Handle, Self.BoundsRect, True); 
Run Code Online (Sandbox Code Playgroud)

这样一个奇怪的矩形无效可能没有多大用处。但是您可能可以想象其他您希望无效的矩形。

使节点失效

Windows 没有意识到这一点,但您的控件代表一棵树,以及树和节点。有时您可能希望使“节点”的矩形无效:

在此处输入图片说明

您不必弄清楚节点的坐标和大小, TVirtualTree已经为您提供了一个方便的方法来使节点无效

function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
Run Code Online (Sandbox Code Playgroud)

所以:

tvItems.InvalidateNode(someNode);
Run Code Online (Sandbox Code Playgroud)

它还提供了一种使节点及其所有子节点无效的方法:

procedure TBaseVirtualTree.InvalidateChildren(Node: PVirtualNode; Recursive: Boolean);
// Invalidates Node and its immediate children.
// If Recursive is True then all grandchildren are invalidated as well.
// The node itself is initialized if necessary and its child nodes are created (and initialized too if
// Recursive is True).
Run Code Online (Sandbox Code Playgroud)

当您的树孩子时,这很有用:

在此处输入图片说明

您可以使父节点和现在需要使用新数字更新的所有子节点无效:

tvItems.InvalidateChildren(someNode, True);
Run Code Online (Sandbox Code Playgroud)

和其他辅助方法

虚拟树还有其他有用的方法:

  • 获取某个有趣的矩形以使其无效
  • 调用 Windows.InvalidateRect

那是:

  • InvalidateToBottom(Node: PVirtualNode); 从给定节点开始重新绘制客户区。如果此节点不可见或尚未初始化,则什么也不会发生。
  • TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex); 使列的客户区部分无效。

无效与重绘

您的另一个问题是关于以下区别的混淆:

  • 无效
  • 重新粉刷

当您使一个矩形(例如整个窗体、整个控件或一些较小的矩形,如节点、节点及其子项或列)无效时,您是在告诉 Windows 它需要要求控件绘制自身。那是:

屏幕上的像素现在无效,必须重新绘制

这将在下次要求树自绘时发生。Windows 是基于消息的。当您的应用程序运行时,它会处理消息,包括一条WM_PAINT消息。当VirtualTree收到WM_PAINT消息时,它会绘制要求重新绘制的部分。

这意味着对于任何和所有绘画的发生,您必须处理消息——即您必须让您的程序“空闲”

如果你坐在那里是一个繁忙的循环,永远不要让你的代码退出:

procedure TForm1.Button1Click(Sender: TObject);
begin
   while (true) do 
   begin
      tvItems.Invalidate;
      Sleep(100);
   end;
end;
Run Code Online (Sandbox Code Playgroud)

循环永远不会结束,树也永远不会有机会实际绘制自己。

德尔福可怕的重绘黑客

Delphi 有一个可怕的 hack,它强制绘制控件。

  • 它假装是 Windows 要求控件绘制自己
  • 然后直接跳转到控件的绘制例程

这意味着控件将自行绘制,即使它没有收到WM_PAINT来自 Windows的消息 - 它只是进行涂鸦。

procedure TForm1.Button1Click(Sender: TObject);
begin
   while (true) do 
   begin
      tvItems.Repaint; //force the control to repaint itself
      Sleep(100);
   end;
end;
Run Code Online (Sandbox Code Playgroud)

这是一个丑陋的黑客,因为:

  • 在第一种情况下,我们的代码没有处理 Windows 消息
  • 在修改后的情况下,我们仍然没有做正确的事情,并试图用锤子拧螺丝

在这些情况下,正确的解决方案是拥有一个后台线程。并让后台线程通知主应用程序它需要使树无效。然后主程序将收到一条WM_PAINT消息并照常绘制自己。