delphi - 我应该如何更新 Virtual TreeView 中的节点?
问题描述
我正在使用 Delphi XE3 和 Virtual TreeView。
我想用Virtual TreeView来实现一棵树,当点击“开始”按钮时,程序会递归搜索一个驱动器下的所有文件和文件夹,然后将它们一一添加到树中,就像Windows资源管理器一样。此外,应该有一个数字表示文件夹下的文件和子文件夹的数量,使用如下静态文本:
VirtualTreeView - 同一节点中不同颜色的文本
在我的实施中,我发现有时数字没有正确更新。
因此,每当文件/子文件夹的数量发生变化时,我认为刷新节点的方式如下:
调用 tvItems.Change(PNode) 来更新节点。
调用 tvItems.InvalidateNode(PNode)。
调用 tvItems.RepaintNode(PNode)。
调用 tvItems.UpdateAction。
但是,1 是无法调用的受保护方法。2和3都可以,但不知道哪个更适合更新。4 没有记录,不知道如何调用它。
解决方案
基本思想是,如果幕后发生变化,则需要重新绘制树。这意味着下一次树绘制自己时,它将使用新的基础值。
如果你的树在屏幕上:
您可以简单地调用:
tvItems.Invalidate;
这告诉 Windows整个树现在“无效”,需要重新绘制。我可以表示这个“无效”区域,该区域将在下次树绘制自身时更新:
这很好,正确,并且可以完美地工作。
性能改进
很多时候,强制整棵树重新绘制自己是完全合理的。
但也可以开始优化事物。如果您知道只有 Windows 控件的某个区域是“无效的”,那么您可以只使该部分无效。
执行此操作的 Windows 函数是InvalidateRect:
InvalidateRect(tvItems.Handle, Rect(13, 18, 30, 38), True);
这将使 13,18 处的 30x38 正方形无效:
事实上TWinControl.Invalidate
,所做的只是转身并调用 Windows InvalidateRect函数:
//true means to also trigger an erase of the background
InvalidateRect(Self.Handle, Self.BoundsRect, True);
这样一个奇怪的矩形失效可能没有多大用处。但是您可能可以想象其他您希望无效的矩形。
无效节点
Windows 没有意识到这一点,但您的控件代表一棵树,以及树和节点。有时您可能想要使“节点”的矩形无效:
TVirtualTree已经为您提供了一种使节点无效的便捷方法,而不必计算节点的坐标和大小 :
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
所以:
tvItems.InvalidateNode(someNode);
它还提供了一种使节点及其所有子节点无效的方法:
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).
当您的树有孩子时,这很有用:
您可以使父节点和现在需要使用新数字更新的所有子节点无效:
tvItems.InvalidateChildren(someNode, True);
和其他辅助方法
虚拟树还有其他有用的方法:
- 让某个有趣的矩形失效
- 调用 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;
循环永远不会结束,树也永远不会有机会实际绘制自己。
德尔福可怕的重绘黑客
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;
这是一个丑陋的黑客,因为:
- 在第一种情况下,我们的代码没有处理 Windows 消息
- 在修改后的情况下,我们仍然没有做正确的事情,并试图用锤子拧螺丝
在这些情况下,正确的解决方案是拥有一个后台线程。并让后台线程向主应用程序发出它需要使树无效的信号。然后主程序将收到一条WM_PAINT
消息并正常绘制自己。
推荐阅读
- c# - Azure.Messaging.ServiceBus:我应该在处置时从 ServiceBusProcessor 中删除 ProcessMessageAsync 处理程序吗?
- c# - post方法中的验证
- reactjs - 如何使用 Internet Explorer 11 访问本地网站?
- python - pywinauto 访问嵌套的子菜单
- hibernate - 将 Hibernate3 迁移到 Hibernate5,Formatter 类
- visual-studio-code - 带有“\”或“\\”的差异路径
- angular - 在移动设备上滚动时,角度触发器中的 ngx-slider 会不必要地触发
- jmx - 如何为咖啡因缓存激活 JMX
- python - 在 PySpark 中展开 json 列 - 架构问题 - AttributeError: 'tuple' object has no attribute 'name'
- c# - 如何停止 WPF 中的后台工作人员?