我应该如何更新 Virtual TreeView 中的节点?
How should I update the node in Virtual TreeView?
我正在使用 Delphi XE3 和 Virtual TreeView。
我想用Virtual TreeView实现一棵树,当点击"Start"按钮时,程序会递归搜索一个驱动器下的所有文件和文件夹,然后一个一个地添加到树中,就像Windows 探险家。此外,一个文件夹下应该有一个数字表示文件和子文件夹的数量,使用这样的静态文本:
VirtualTreeView - different color of text in the same node
在我的实现中,我发现有时数字没有正确更新。
因此,我想到了以下方式来刷新节点,每当files/subfolders的数量发生变化时:
调用tvItems.Change(PNode)更新节点
调用tvItems.InvalidateNode(PNode).
调用tvItems.RepaintNode(PNode).
呼叫tvItems.UpdateAction.
但是,1 是无法调用的受保护方法。 2 和 3 都可以,但不知道哪个更适合更新。 4 没有记录,不知道如何调用它。
基本思想是,如果幕后发生变化,则需要重新绘制树。这意味着下次树绘制自己时,它将使用新的基础值。
如果你的树就在屏幕上:
您只需拨打:
tvItems.Invalidate;
这告诉 Windows 整个 树现在是 "invalid" 并且需要重新绘制。我可以代表这个 "invalid" 区域,下次树绘制自己时会更新:
这很好,正确,并且可以很好地工作。
性能改进
很多时候强制整个树重绘所有本身是完全合理的。
但也有可能开始优化事情。如果您知道只有 Windows 控件的某个区域是 "invalid",那么您可以使该部分无效。
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没有意识到,但是你的控件代表了一棵树,树和节点。有时您可能想要使 "node":
的矩形无效
您不必计算节点的坐标和大小,
TVirtualTree 已经为您提供了一个方便的方法来 使 节点无效:
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
所以:
tvItems.InvalidateNode(someNode);
它还提供了一种使节点及其所有children无效的方法:
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).
当你的树 有 children:
时这很有用
您可以使 parent 节点和现在需要用新数字更新的所有 children 节点无效:
tvItems.InvalidateChildren(someNode, True);
和其他辅助方法
虚拟树还有其他有用的方法:
- 获取某个有趣的矩形以使其无效
- 致电Windows.InvalidateRect
即:
InvalidateToBottom(Node: PVirtualNode);
从给定节点开始重新绘制客户区。如果此节点不可见或尚未初始化,则什么也不会发生。
TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex);
使列的客户区部分无效。
无效与重绘
你的另一个问题是关于混淆之间的区别:
- 正在失效
- 正在重绘
当您使一个矩形无效时(例如整个表单、整个控件或一些较小的矩形,例如节点、节点及其 children,或列)你告诉 Windows 它需要让控件来绘制自己。即:
the pixels on screen are now invalid and must be repainted
这将在下次要求树自己绘画时发生。 Windows 是 message-based。当您的应用程序运行时,它会处理消息,包括 WM_PAINT
消息。当 VirtualTree
收到 WM_PAINT
消息时,它会绘制要求重新绘制的部分。
这意味着要进行任何绘制,您必须处理消息 - 即您必须让程序运行 "idle".
如果你坐在那里是一个繁忙的循环,永远不要让你的代码退出:
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Invalidate;
Sleep(100);
end;
end;
循环永远不会结束,并且树永远不会有机会实际上画自己。
Delphi 可怕的重绘技巧
Delphi 有一个可怕的 hack,强制 绘制控件。
- 它假装它是Windows要求控件绘制自己
- 然后直接跳转到控件的绘制例程
这意味着控件将自行绘制,即使它没有收到来自 Windows 的 WM_PAINT
消息 - 它只是进行涂鸦。
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Repaint; //force the control to repaint itself
Sleep(100);
end;
end;
这是一个丑陋的 hack,因为:
- 在第一种情况下,我们的代码没有处理 Windows 消息
- 修改后的情况下还是没做好,想用锤子拧螺丝
在这些情况下正确的解决方案是拥有一个后台线程。并让后台线程向主应用程序发出信号,告知它需要使树无效。然后主程序将收到 WM_PAINT
消息并正常绘制自己。
我正在使用 Delphi XE3 和 Virtual TreeView。
我想用Virtual TreeView实现一棵树,当点击"Start"按钮时,程序会递归搜索一个驱动器下的所有文件和文件夹,然后一个一个地添加到树中,就像Windows 探险家。此外,一个文件夹下应该有一个数字表示文件和子文件夹的数量,使用这样的静态文本:
VirtualTreeView - different color of text in the same node
在我的实现中,我发现有时数字没有正确更新。
因此,我想到了以下方式来刷新节点,每当files/subfolders的数量发生变化时:
调用tvItems.Change(PNode)更新节点
调用tvItems.InvalidateNode(PNode).
调用tvItems.RepaintNode(PNode).
呼叫tvItems.UpdateAction.
但是,1 是无法调用的受保护方法。 2 和 3 都可以,但不知道哪个更适合更新。 4 没有记录,不知道如何调用它。
基本思想是,如果幕后发生变化,则需要重新绘制树。这意味着下次树绘制自己时,它将使用新的基础值。
如果你的树就在屏幕上:
您只需拨打:
tvItems.Invalidate;
这告诉 Windows 整个 树现在是 "invalid" 并且需要重新绘制。我可以代表这个 "invalid" 区域,下次树绘制自己时会更新:
这很好,正确,并且可以很好地工作。
性能改进
很多时候强制整个树重绘所有本身是完全合理的。
但也有可能开始优化事情。如果您知道只有 Windows 控件的某个区域是 "invalid",那么您可以使该部分无效。
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没有意识到,但是你的控件代表了一棵树,树和节点。有时您可能想要使 "node":
的矩形无效您不必计算节点的坐标和大小, TVirtualTree 已经为您提供了一个方便的方法来 使 节点无效:
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
所以:
tvItems.InvalidateNode(someNode);
它还提供了一种使节点及其所有children无效的方法:
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).
当你的树 有 children:
时这很有用您可以使 parent 节点和现在需要用新数字更新的所有 children 节点无效:
tvItems.InvalidateChildren(someNode, True);
和其他辅助方法
虚拟树还有其他有用的方法:
- 获取某个有趣的矩形以使其无效
- 致电Windows.InvalidateRect
即:
InvalidateToBottom(Node: PVirtualNode);
从给定节点开始重新绘制客户区。如果此节点不可见或尚未初始化,则什么也不会发生。TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex);
使列的客户区部分无效。
无效与重绘
你的另一个问题是关于混淆之间的区别:
- 正在失效
- 正在重绘
当您使一个矩形无效时(例如整个表单、整个控件或一些较小的矩形,例如节点、节点及其 children,或列)你告诉 Windows 它需要让控件来绘制自己。即:
the pixels on screen are now invalid and must be repainted
这将在下次要求树自己绘画时发生。 Windows 是 message-based。当您的应用程序运行时,它会处理消息,包括 WM_PAINT
消息。当 VirtualTree
收到 WM_PAINT
消息时,它会绘制要求重新绘制的部分。
这意味着要进行任何绘制,您必须处理消息 - 即您必须让程序运行 "idle".
如果你坐在那里是一个繁忙的循环,永远不要让你的代码退出:
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Invalidate;
Sleep(100);
end;
end;
循环永远不会结束,并且树永远不会有机会实际上画自己。
Delphi 可怕的重绘技巧
Delphi 有一个可怕的 hack,强制 绘制控件。
- 它假装它是Windows要求控件绘制自己
- 然后直接跳转到控件的绘制例程
这意味着控件将自行绘制,即使它没有收到来自 Windows 的 WM_PAINT
消息 - 它只是进行涂鸦。
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Repaint; //force the control to repaint itself
Sleep(100);
end;
end;
这是一个丑陋的 hack,因为:
- 在第一种情况下,我们的代码没有处理 Windows 消息
- 修改后的情况下还是没做好,想用锤子拧螺丝
在这些情况下正确的解决方案是拥有一个后台线程。并让后台线程向主应用程序发出信号,告知它需要使树无效。然后主程序将收到 WM_PAINT
消息并正常绘制自己。