There seems to be a bug with the PaintTree logic in TVirtualStringTree when using the poUnbuffered option. Only the first node of the tree is visible in the output. I tested using the Minimal VST example and the behavior is identical. When poUnbuffered is used as an option then only the first node is visible, remove the option and the tree is correctly painted.
If I step through the code then all of the objects are being painted on the canvas so it looks like a clipping issue but I have not worked with VST enough to identify what the problem is. They play a lot with the canvas origin and clipping.
To see the problem in action, just put the following code on any form that includes a VST, change the names to protect the innocent as required and click away.
procedure TMainForm.Button2Click(Sender: TObject);
var
saveBitmap: TBitmap;
begin
saveBitmap := TBitmap.Create;
try
saveBitmap.height := 400;
saveBitmap.width := 400;
vst.PaintTree(
saveBitmap.Canvas,
Rect(0, 0, 400, 400),
Point(0, 0),
[poBackground, poColumnColor, poGridLines, poUnbuffered], // Remove poUnbuffered to have the tree paint correctly
pfDevice // pixelformat
);
saveBitmap.SaveToFile('E:\temp\CanvasSave' + FormatDateTime('hhnnsszzz', Now) + '.bmp');
finally
saveBitmap.Free;
end;
end;
Has anyone run into this before?
Some more details:
There are very few differences between the paint code for poUnbuffered and without it. I am not using columns so the major differences are:
if not (poUnbuffered in PaintOptions) then
begin
// Create small bitmaps and initialize default values.
// The bitmaps are used to paint one node at a time and to draw the result to the target (e.g. screen) in one step,
// to prevent flickering.
NodeBitmap := TBitmap.Create;
// For alpha blending we need the 32 bit pixel format. For other targets there might be a need for a certain
// pixel format (e.g. printing).
if MMXAvailable and ((FDrawSelectionMode = smBlendedRectangle) or (tsUseThemes in FStates) or
(toUseBlendedSelection in FOptions.PaintOptions)) then
NodeBitmap.PixelFormat := pf32Bit
else
NodeBitmap.PixelFormat := PixelFormat;
NodeBitmap.Width := PaintWidth;
// Make sure the buffer bitmap and target bitmap use the same transformation mode.
SetMapMode(NodeBitmap.Canvas.Handle, GetMapMode(TargetCanvas.Handle));
PaintInfo.Canvas := NodeBitmap.Canvas;
end
else
begin
PaintInfo.Canvas := TargetCanvas;
NodeBitmap := nil;
end;
and
if not (poUnbuffered in PaintOptions) then
begin
// Adjust height of temporary node bitmap.
with NodeBitmap do
begin
if Height <> PaintInfo.Node.NodeHeight then
begin
// Avoid that the VCL copies the bitmap while changing its height.
Height := 0;
Height := PaintInfo.Node.NodeHeight;
SetCanvasOrigin(Canvas, Window.Left, 0);
end;
end;
end
else
begin
SetCanvasOrigin(PaintInfo.Canvas, -TargetRect.Left + Window.Left, -TargetRect.Top);
ClipCanvas(PaintInfo.Canvas, Rect(TargetRect.Left, TargetRect.Top, TargetRect.Right,
Min(TargetRect.Bottom, MaximumBottom)))
end;
There are a couple of BitBlt's later on where the bitmap is copied across to the canvas when not using poUnbuffered.
This is a workaround for the problem that I picked up from another issue regarding the PaintTree code and poUnbuffered. There is apparently a problem in the second code extract that I listed above. Apparently SetCanvasOrigin is changing the origin and the ClipCanvas is not taking these changes into account. The code should be changed as follows:
begin
SetCanvasOrigin(PaintInfo.Canvas, -TargetRect.Left + Window.Left, -TargetRect.Top);
// ClipCanvas(PaintInfo.Canvas, Rect(TargetRect.Left, TargetRect.Top, TargetRect.Right,
// Min(TargetRect.Bottom, MaximumBottom)))
ClipCanvas(PaintInfo.Canvas, Rect(0, 0, TargetRect.Right - TargetRect.Left,
Min(TargetRect.Bottom - TargetRect.Top, MaximumBottom - TargetRect.Top)));
end;
I have verified that this works in my situation. It may not work in all cases. In my situation I am only using poUnbuffered in this case so the risk for me is limited.