delphivirtualtreeview

How should I update the node in Virtual TreeView?


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:

  1. Call tvItems.Change(PNode) to update the node.

  2. Call tvItems.InvalidateNode(PNode).

  3. Call tvItems.RepaintNode(PNode).

  4. 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.


Solution

  • 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:

    enter image description here

    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:

    enter image description here

    And that's fine, correct, and will work perfectly adequately.

    Performance improvements

    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:

    enter image description here

    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.

    Invalidating nodes

    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":

    enter image description here

    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:

    enter image description here

    You can invalidate the parent node and all the children that now need to be updated with the new numbers:

    tvItems.InvalidateChildren(someNode, True);
    

    And other helper methods

    The Virtual Tree has other helpful methods that:

    That is:

    Invalidating vs Repaint

    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's horrible Repaint hack

    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.