How should I update the node in Virtual TreeView? - delphi

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.
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:
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":
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);
And other helper methods
The Virtual Tree has other helpful methods that:
get a certain interesting rectangle to invalidate
call Windows.InvalidateRect
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.
Invalidating vs Repaint
Your other question is in regards to the confusion over the difference between:
invalidating
repainting
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.
it pretends that it's Windows asking a control to paint itself
and then jumps directly to the control's paint routine
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:
in the first case our code wasn't processing Windows messages
in the revised case we're still not doing the right thing, and trying to use a hammer to screw in a screw
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.

Related

How to Invalidate only part of a paintbox/bitmap to optimise its performance?

The question relates to: Drawing on a paintbox - How to keep up with mouse movements without delay?
I was going to at some point ask the question of how to repaint only part of a paintbox without invalidating the whole paintbox, which is slow when there is a lot of drawing going on or in my case when there are a lot of tiles drawn on screen.
From the link above Peter Kostov did touch on the subject briefly in one of his comments:
you can partly BitBlt the offscreen bitmap (only regions where it is changed). This will improve the performance dramatically.
I have limited graphic skills and have never really used BitBlt before but I will be reading more about it after I post this question.
With that said, I wanted to know how exactly could you determine if regions of a bitmap have changed? Does this involve something simple or is there more magic so to speak involved in determining which regions have changed?
Right now I am still painting directly to the paintbox but once I draw to the offscreen buffer bitmap my next step is to optimise the painting and the above comments sound exactly like what I need, only the determining regions that have changed has confused me slightly.
Of course if there are other ways of doing this please feel free to comment.
Thanks.
You don't have to use BitBlt() directly if you draw to an offscreen TBitmap, you can use TCanvas.CopyRect() instead (which uses StretchBlt() internally). But either way, when you need to invalidate just a portion of the TPaintBox (the portion corresponding to the section of the offscreen bitmap that you drew on), you can use InvalidateRect() directly to specify the appropriate rectangle of the TPaintBox, instead of calling TControl.Invalidate() (which calls InvalidateRect() with the lpRect parameter set to NULL). Whenever the TPaintBox.OnPaint event is triggered, InvalidateRect() will have established a clipping rectangle within the canvas, any drawing you do outside of that rectangle will be ignored. If you want to manually optimize your drawing beyond that, you can use the TCanvas.ClipRect property to determine the rectangle of the TPaintBox that needs to be drawn, and just copy that portion from your onscreen bitmap.
The only gotcha is that TPaintBox is a TGraphicControl descendant, so it does not have its own HWND that you can pass to InvalidateRect(). You would have to use its Parent.Handle HWND instead. Which means you would have to translate TPaintBox-relative coordinates into Parent-relative coordinates and vice versa when needed.
You need to take charge of the painting in order to do this:
Call InvalidateRect to invalidate portions of a window.
When handling WM_PAINT you call BeginPaint which yields a paint struct containing the rect to be painted.
All of this requires a window, and unfortunately for you, TPaintBox is not windowed. So you could use the parent control's window handle, but frankly it would be cleaner to use a windowed control.
You could use my windowed paint control from this question as a starting point: How could I fade in/out a TImage? Use the ClipRect of the control's canvas when painting to determine the part of the canvas that needs re-painting.

ddraw with delphix: pixel ploting how-to

I use delphi 7 and delphix so I can have ddraw and be able to create a surface to play with pixels.
so in a button trigger i use this:
for i:=1 to 100 do
for j:=1 to 100 do
dxdraw1.Surface.Pixels[i,j]:=250;
where dxdraw1 is a ddraw surface.
The problem is that it works, but the result shows after I hoover another window above my program, like it is not updating the rectangle area with the pixels.
(I press the button, the cpu usage is getting up for a short and the rectangle remains black until I hoover another window.)
Also its slow. I read somewhere this:
"...
DXDraw.Surface.Canvas.Pixels[X,Y]:=clBlue;
DXDraw.Surface.Canvas.Release;
..."
and after this:
"...
Just remember that this function is extremely slow .
It's locking and unlocking the surface on every pixel set.. not very usable.
PixelDX and turboPixel don't, you manually lock the surface, do all of your
pixel operations and then unlock it.. 1000 times faster.
..."
How to use these functions? I cant find them (and I have no idea since I am beginner on this)?
How to lock first and then unlock?
U P D A T E :
Ok, I used undelphix, but in the rectangle I cant see the result, it still remains black:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
i,j :integer;
begin
dxdraw1.Surface.Lock;
for i:=1 to 150 do
for j:=1 to 150 do
dxdraw1.Surface.Pixel[i,j]:=100;
dxdraw1.Surface.Unlock;
end;
U P D A T E 2 :
It works but this bizarre happens: to see the result I have to "hide" the app window under another window or program, and the minimize that window to see the result. If I move the app then the result is gone, and it stays blank. Any ideas?
I'm not up to date on Delphi-X, but I have used the raw DirectX libs a lot in my Life32 program.
Here's how it works behind the scenes:
You get a lock on the screen.
This returns a pointer to memory that you can write to, whatever to write in this memory block will appear on the screen.
The entire system will be frozen (!) until you call unlock.
The memory pointer in step 2 can be regarded as a pointer directly to videomemory.
It is up to you to know:
- bits per pixel;
- number of pixels in the x and y direction;
- padding bytes at the end of each scan-line;
- if the bits per pixel are less than 24, you will not be working with RGB values, but with a palette;
- if you write past the end of the screen unpredicatable stuff will happen(!)
DelphiX is obsolete
I recommend you use unDelphiX: http://www.micrel.cz/Dx/
It is much more advanced and has many more features.
You can simply use:
DXDraw1.Surface.Lock; //special version Lock without any parameters
try
for xy:= 0 to Min(DXDraw1.Height, DXDraw1.Width) - 1 do begin
//silly example, unDirectX has line drawing routines
DXDraw1.Surface.Pixel[xy, xy]:= TheColor;
end; {for xy}
finally
DXDraw1.Surface.Unlock;
end;
Because you've called lock prior to accessing the pixel array, it will not bracket the call in a lock - unlock pair.
Warning
Always spend the absolute minimum amount of time possible in between lock and unlock calls, because your entire system will be frozen while the display is locked.
Also make sure that Delphi does not break on exceptions when debugging the code inside the lock box, because you system will be frozen with no way to revive it.
Note
There is still some overhead in the call to pixel, because the color value gets translated into a format dictated by the BytesPerPixel for your display.
Also the [x,y] coordinates get translated into a memory address.
In Life32 I resolved to write 8 different drawing routines, for every possible pixel depth.
And doing my own drawing of 16x16 blocks, thereby avoiding address translation per individual pixel.
Don't draw until you have to
Because DirectX is so fast, it's easy to draw much faster than the eye can see, use a high resolution timer (included in unDirectX) to limit drawing to x frames per second.
Do not use Windows messages like WM_PAINT to do your main drawing, but do redraw when you're receiving an incoming WM_PAINT message.

Delphi - Paint text so it would not disappear after repainting the image under it

How should I paint text on canvas so that the text won't disappear after repaint/update/refresh without repainting it again and again? Like it was painted as image and not temporarily painted.
If you are talking about, for example, a TPaintBox control or something similar, then there is no persistent canvas to paint on. The system simply is not designed that way and the VCL controls reflect the underlying Windows framework.
The normal approach is as follows:
Paint first to an offscreen bitmap.
When the system asks for a repaint, draw the offscreen bitmap onto the screen canvas.
There are a variety of reasons that may lead you to this approach. Often performance is a factor. It may be expensive to paint and caching the image can help. Sometimes the information required in order to paint may be transient and again caching the output may be a solution.
You can't. Painting only shows the image on the screen once; if you want it to persist then you must repaint it each time the OS requests it.
Use a TLabel (or some derivative) and place it over the canvas. The TLabel will redraw itself whenever necessary.
You need to only draw when the system says you should. There's two things to know about with this subject...
Cache
You can also implement your own cache system. This can get a little tricky when working with many layers. You may have a particular area which is expected to change at a high rate. And then the background presumably isn't going to be changed unless it's been resized, or a color has changed, etc. Such as a needle moving on top of some photo. Just maintain two different image objects in the background and combine them, making sure at least the one(s) on top are transparent.
You can also tell Windows when your control's cache is invalidated (next subject...) by using the Invalidate command. This will tell Windows that something in your control has changed to the point where you need to redraw everything. Windows will then decide when it's ready to actually tell your control to be painted again by calling a Paint procedure.
System Paint
As David mentions in his answer, if you're working with a control, then you should repaint your background when the system says you should. This is accomplished by inheriting the Paint procedure from the TGraphicControl or the TCustomControl (and some others). This procedure is called every time the system says you need to refresh your control's contents. It's the system's way of telling you when your cache is invalidated.
procedure Paint; override;
...
procedure TMyCustomControl.Paint;
begin
DoSomeDrawingOnCanvas;
end;
On the other hand, you can tell Windows whenever you want it to call this Paint procedure too...
procedure TMyCustomControl.SetWidth(const Value: Integer);
begin
if Value <> FWidth then begin //Just a common check for performance reasons
FWidth:= Value;
Invalidate; //This tells Windows that you want to repaint your control
end;
end;

Is it possible to use animated icons with VirtualTreeView nodes?

I am using the awesome VirtualTreeView component with Delphi 2010. Currently, my nodes are setup with a standard image in a TImageList. What I'd like to do is to be able to show some type of spinning animated icon to show visually that a node is "busy".
Obviously the node won't be busy, but what the node represents to the user will actually be busy. Is there an easy way to do this?
The only way I can think of is to create add 8-10 images represent a spinning wheel to my current TImageList and then cycle through them for that node.
Does anyone have any suggestions?
Probably you can do the animation yourself. For example you could store the animation status in the node data. Then you could repeatedly (from a timer) iterate the whole tree (IterateSubTree) and from the callback check the node data and if the node should be animated call RepaintNode (to force repainting immediately) or InvalidateNode (to invalidate the node so it's painted in the next paint cycle). The actual painting could be done as usual from an OnGetImageIndex event handler, returning different image indexes for different stages of the animation cycle.

stop flickering

I have an old program written in borland pascal and in Delphi if I use the Form1.Canvas.LineTo and MoveTo functions I get a flickering effect. Can anyone tell me how to get rid of the flickering?
Try to set DoubleBuffered to true in Form.OnCreate.
A general technique for reducing flicker in animated graphics operations is called double buffering. The idea is that you do all drawing to an offscreen bitmap, then when you've finished rendering the whole scene, copy the entire bitmap to the visible display.
The term also relates to hardware-supported techniques such as the ability to exchange the whole video display buffer with an alternate one, which is used in dedicated systems like console video games.
Although using double buffering is usually the best solution, it is not always the right solution, and definitely not the most memory saving solution. However if you only draw a part of the image, I'd go with that solution as well setting DoubleBuffered to true as mentioned in the other comments.
However if you fill the entire components area every time you draw anyway, you might want to choose a different approach. If you set the ControlStyle to csOpaque you'll avoid having the component erase the background, and thereby removing a source of the flickering, without having to double buffer. This of course requires you to draw on the entire component area, so the solution is only really suitable if you do.
In general however if memory consumption is of no importance, I'd go for the double buffering as well, I just wanted to supply you with an alternative. :)
Easy code sample on the double buffering.
Create Buffer ( TBitmap )
Draw on the Buffer canvas.
Draw the bitmap on the canvas. Form1.Canvas for example.
Buffer := TBitmap.Create;
try
Buffer.Width:=Form1.Width;
Buffer.Height:=Form1.Height;
//clearBuffer
Buffer.Canvas.FillRect(Buffer.Canvas.ClipRect);
//draw Something
Buffer.Canvas.TextOut(0,0,'Hello World');
Buffer.Canvas.Rectangle(0,1,2,3);
//drawBuffer on canvas
Form1.Canvas.Draw(0,0,Buffer);
finally
Buffer.free
end;
Double Buffering is one way but not always sufficient. Imagine you paint a shape at runtime, it depends on your repaint method. Forces the control to repaint its image on the screen could be crucial.
Call Repaint to force the control to repaint its image immediately. If the ControlStyle property includes csOpaque, the control paints itself directly.
So double buffering plus invalidate and [csOpaque] as controlstyle could be an improvement:
var Compass: TPaintBox;
procedure TForm1ForceRepaint(Sender: TObject);
{Called when display type or compass angle changes}
begin
compass.controlstyle:= [csOpaque];
while heading.value<0 do heading.Value:=heading.Value+360;
while heading.value>360 do heading.Value:=heading.Value-360;
compass.Invalidate;
end;
In my case, none of the above-mentioned solutions really worked. I have two graphics which are overlaying one on another. The change of the DoubleBuffered property has solved the flickering problem, but the background graphics was not correctly rendered.
After a small research, I have found two alternative fixes described on page https://delphi-bar.blogspot.com/2012/11/prevent-screen-refresh-and-flickering.html.
LockWindowUpdate(Handle);
try
// Code goes here
finally
LockWindowUpdate(0);
end;
As discussed for example here, it is not always a recommended way. The second option, in my case, has fixed almost completely the flicker problem:
SendMessage(Handle, WM_SETREDRAW, WPARAM(False), 0);
try
// Code goes here
finally
SendMessage(Handle, WM_SETREDRAW, WPARAM(True), 0);
RedrawWindow(Handle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
end;

Resources