I am using Delphi XE3 and Virtual TreeView.
I want to use Virtual TreeView to implement a tree, when clicking "Start" button, the program will search all files and folders under a drive recursively, then add them one by one to the tree, just like Windows Explorer. Moreover, there should be a number indicating number of files and subfolders under a folder, using static text like this:
VirtualTreeView - different color of text in the same node
In my impelemention, I find sometimes the number is not updated correctly.
Therefore, I think the following way to refresh the node, whenever the number of files/subfolders are changed:
Call tvItems.Change(PNode) to update the node.
Call tvItems.InvalidateNode(PNode).
Call tvItems.RepaintNode(PNode).
Call tvItems.UpdateAction.
However, 1 is protected method that cannot be invoked. 2 and 3 are both OK but don't know which is better for updating. 4 is not documented and don't know how to call it.
The fundamental idea is that if something behind the scenes changes, the tree need to repainted. This means that the next time the tree draws itself, it will use the new underlying values.
If your tree is sitting there on screen:
You can simply call:
tvItems.Invalidate;
This tells Windows that the entire tree is now "invalid" and needs to be redrawn. I can represent this "invalid" region that will be updated the next time the tree paints itself:
And that's fine, correct, and will work perfectly adequately.
A lot of times it's perfectly reasonable to force the entire tree to repaint all of itself.
But it's also possible to begin to optimize things. If you know that only a certain region of a Windows control is "invalid", then you could just invalidate that portion.
The Windows function to do that is InvalidateRect:
InvalidateRect(tvItems.Handle, Rect(13, 18, 30, 38), True);
That will invalidate a 30x38 square at 13,18:
In fact all TWinControl.Invalidate
is doing is turning around and calling the Windows InvalidateRect function:
//true means to also trigger an erase of the background
InvalidateRect(Self.Handle, Self.BoundsRect, True);
There may not be much use for such a strange rectangle to be invalidated. But you can probably imagine other rectangles you'd like to be invalidated.
Windows doesn't realize it, but your control represents a tree, and the tree and nodes. And there might be times when you want to invalidate the rectangle of a "node":
Rather than you having to figure out the coordinates and size of a node, the TVirtualTree already provides you a handy method to invalidate a node:
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
so:
tvItems.InvalidateNode(someNode);
It also provides a method to invalidate a node and all its 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).
This is useful when your tree has children:
You can invalidate the parent node and all the children that now need to be updated with the new numbers:
tvItems.InvalidateChildren(someNode, True);
The Virtual Tree has other helpful methods that:
That is:
InvalidateToBottom(Node: PVirtualNode);
Initiates repaint of client area starting at given node. If this node is not visible or not yet initialized then nothing happens.TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex);
Invalidates the client area part of a column.Your other question is in regards to the confusion over the difference between:
When you invalidate a rectangle (e.g. an entire form, an entire control, or some smaller rectangle such as a node, node and its children, or a column) you are telling Windows that it needs to ask the control to paint self. That is:
the pixels on screen are now invalid and must be repainted
This will happen the next time the tree is asked to paint itself. Windows is message-based. And as your application runs, it processes messages, including a WM_PAINT
message. When the VirtualTree
gets a WM_PAINT
message, it paints the portions it was asked to repaint.
This means that for any and all painting to happen, you have to be processing messages - i.e. you have to let your program go "idle".
If you sit there is a busy loop, never letting your code exit:
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Invalidate;
Sleep(100);
end;
end;
The loop never ends, and the tree is never given a chance to actually paint itself.
Delphi has a horrible hack that forces a control to be painted.
This means that the control will paint itself, even though it's not received a WM_PAINT
message from Windows - it just does the scribbling.
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Repaint; //force the control to repaint itself
Sleep(100);
end;
end;
It's an ugly hack because:
The correct solution in these cases is to have a background thread. And have the background thread signal the main application that it needs to invalidate the tree. The main program will then receive a WM_PAINT
message and draw itself as normal.