2

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.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Graymatter
  • 6,529
  • 2
  • 30
  • 50
  • That looks like a bug. Or something unexpected at least. You'll get it to work properly if you add a column to the header, so it's something related to clipping when the columns collection is empty, but it is so sensitive part of code that I don't want to suggest any fix. I would just add a column and stay happy :) – TLama Feb 26 '14 at 23:51
  • Just tested by adding a column. Using the same example and code above, the first two rows aren't painted and the rest are painted :) – Graymatter Feb 27 '14 at 00:00
  • @TLama I am using 5.1.3. I have also tested with the latest version and it has the same behavior in both cases (with and without columns). I will do a diff and see what has changed between the version you have and 5.1.3. It might help to get something working in the interim until there is a fix out. Will also log a bug with VST. – Graymatter Feb 27 '14 at 00:18
  • Sorry, I've deleted my previous comment since I realized that I've modified one thing in your original code. I've used `Vst.GetTreeRect` instead of the fixed rectangle as the second parameter of the `PaintTree` method call. So, for the record, having only that change in your code, I've been able to reproduce the problem and got the correct result when I've added a column to the header with VTV 5.1.0. – TLama Feb 27 '14 at 00:27
  • @TLama I stumbled across a fix for this problem in another issue that has been logged against VST. I will put it in as an answer until there is an official fix for it. – Graymatter Feb 27 '14 at 01:14
  • Be careful, VT might be sometimes very sensible. [`One thing`](http://stackoverflow.com/a/19030907/960757) you seemingly fix and the [`other one`](http://stackoverflow.com/q/20637534/960757) appears. – TLama Feb 27 '14 at 01:20
  • @TLama Thank you for the warning. In this case, the risk for me is low because of the limited scope of the fix. – Graymatter Feb 27 '14 at 01:29

1 Answers1

1

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.

Graymatter
  • 6,529
  • 2
  • 30
  • 50