Windows graphic question

 I'm writing a component descended from TGraphicControl. I need it to be transparent, but it contains a fair amount of animated drawing. The problem is, of course that it flickers badly.
  Now the most obvious solution is to buffer the drawing work onto a bitmap and then copy that to the components display, but then we lose the transparency, UNLESS that is we can somehow grab the background that my control will be sitting on. The question is therefore, how do we grab this background?
LVL 4
StevenBAsked:
Who is Participating?
 
MadshiCommented:
>> I don't like drawing to the canvas outside of the paint method, because any updates have to be replicated there anyway, otherwise the control will lose its appearance if it is obscured briefly (by another form for example)

Yes, of course. I usually have something like a "PaintToDC(dc: dword)" function, which I call both from WM_PAINT and from outside of WM_PAINT. When being called from outside of WM_PAINT (e.g. when you do your 30ms animation) I give in MyComponent.Canvas.Handle. This way I don't have the code twice. Writing directly to the Canvas is faster than using Invalidate(Rect) - what should be perfect for animation. BTW, Windows' ListView control behaves similar...   :-)

But doesn't matter, if InvalidateRect is fast enough for your animation: Fine. But if it is not, please try my suggestion...

Regards, Madshi.
0
 
StevenBAuthor Commented:
 ... or can you think of a better way of doing this?
0
 
GreymanCommented:
If you descend from TImage instead, you can do your drawing into the Picture property and set the Transparent property to True.

You won't be able to accept input focus, but you can't do that with TGraphicContol either.
0
Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

 
StevenBAuthor Commented:
 This won't solve the flickering problem though will it? I'll check, but I cant see why it would.
0
 
GreymanCommented:
The Picture property takes a TBitmap - so you would need to combine this approach with your idea of buffering into a bitmap.
0
 
StevenBAuthor Commented:
 Wherein lies the real problem:

  How do I get the background as a bitmap (bearing in mind that this background isn't necessarily even static)?
0
 
GreymanCommented:
You don't need to get the background into the bitmap - when you set the transparent property to True, the TImage handles transparency for you.

You can either use the Bottom-Left pixel as the 'transparent colour' (this is the default) or you can go into TImage.Picture.Bitmap.TransparentColour and set it there.
0
 
StevenBAuthor Commented:
 Yes, but graphic controls handle transparency by simply drawing on top of whatever lies behind them. This update isn't fast enough to prevent flicker, which is specifically what I'm trying to eliminate here. I've already sucessfully implemented transparent graphic controls, but the problem occurs when windows repaints the form. The clasic solution to removing this flicker in graphics work is to draw the images onto a buffered bitmap, which can then be blitted to screen fast enough to eliminate flicker. The drawback is that in order to implement this in a windows environment you have to wrestle with windows own interpretation of how things should be painted. In order to simulate transparency I'm going to have to access the appropriate background if I want to use a buffered background.
  I'm investigating your suggestion, however, in case I've missed something along the way :o)

  Cheers,

  Steven.
0
 
GreymanCommented:
Thank you for explaining the likely shortcomings of the suggestion (I mean it).
0
 
MadshiCommented:
Here comes the solution:

(used in my own transparent components :-)

type TParentControl = class(TWinControl);
procedure CopyParentImage(Control: TControl; Dest: TCanvas);
var I, Count, X, Y, SaveIndex: integer;
    DC: cardinal;
    R, SelfR, CtlR: TRect;
begin
  if Control.Parent = nil then Exit;
  Count := Control.Parent.ControlCount;
  DC := Dest.Handle;
  SelfR := Bounds(Control.Left, Control.Top, Control.Width, Control.Height);
  X := -Control.Left; Y := -Control.Top;
  { Copy parent control image }
  SaveIndex := SaveDC(DC);
  SetViewportOrgEx(DC, X, Y, nil);
  IntersectClipRect(DC, 0, 0, Control.Parent.ClientWidth, Control.Parent.ClientHeight);
  TParentControl(Control.Parent).PaintWindow(DC);
  RestoreDC(DC, SaveIndex);
  { Copy images of graphic controls }
  for I := 0 to Count - 1 do begin
    if (Control.Parent.Controls[I] <> nil) and
       (not (Control.Parent.Controls[I] is TWinControl)) and
       (not (Control.Parent.Controls[I] is TMRSpeedButton)) then begin
      if Control.Parent.Controls[I] = Control then Break;
      with Control.Parent.Controls[I] do begin
        CtlR := Bounds(Left, Top, Width, Height);
        if Bool(IntersectRect(R, SelfR, CtlR)) and Visible then begin
          SaveIndex := SaveDC(DC);
          SetViewportOrgEx(DC, Left + X, Top + Y, nil);
          IntersectClipRect(DC, 0, 0, Width, Height);
          Perform(WM_PAINT, integer(DC), 0);
          RestoreDC(DC, SaveIndex);
        end;
      end;
    end;
  end;
end;

Regards, Madshi.
0
 
GwenaCommented:
listening :-)
0
 
StevenBAuthor Commented:
 TMRSpeedButton?
  Guess that's one of your controls then ;o)

  Looking at your code now Madshi ...
0
 
StevenBAuthor Commented:
 The function seems to work pretty well Madshi, but I have a few questions:

  1) In order to use the technique you have to descend from a TCustomControl and prevent the WMEraseBkgnd from occuring in order to eliminate flicker. Is there a way to prevent this happening using a TGraphicControl descendant?

  2) Whilst the function works fine regarding grabbing graphic controls, it does not seem to grab the image of Windowed controls (having commented out (not (Control.Parent.Controls[I] is TWinControl))) Even worse, the { Copy parent control image } section doesnt seem to work at all, simply returning a white block. (NB, why the different implementations of PaintWindow(DC) for the parent and Perform(WM_PAINT, integer(DC), 0) for controls?)

  Cheers,
  Steven.
0
 
MadshiCommented:
Yeah, TMRSpeedButton is one of my controls...  :-)

(1) In fact TMRSpeedButton *IS* a TGraphicControl descendant and I don't have any flickering!

(2) Why did you comment this line out? Please don't. Look, what do we have to do in order to copy the real parent image?
(a) We have to paint the image of the parent's TWinControl (this is done by PaintWindow(DC)).
(b) We have to paint all the non-TWinControl-controls of our parent, because all those controls *might* overlap with our control.
We do NOT have to copy any other TWinControl-controls of our parent, because they will definetely NOT overlap with our control. They would have to be our parent if they should overlap (and be below us). And we already have painted our parent in (a). So please don't comment that line out.

I'm not sure why you have problems, I do not. My controls work fine. But maybe there are some limitations. It's sooooo long ago that I created those controls, I don't really remember...

Regards, Madshi.
0
 
StevenBAuthor Commented:
 Yeah, I only commented out the line to test whether I could access the image of a WinControl (which incidentally could be beneath our control and not be the parent, if our control is also windowed).

  Your routine seems to work perfectly as regards GraphicControls, but the PaintWindow(DC) call does not draw the parent onto the canvas, but rather fills it with white.

  I get flicker, because I have a background thread updating the image every 30ms, whereas your controls I guess, are static. The flicker is caused by windows erasing the background behind the GraphicControl each time. I have avoided this by Descending from TCustomControl (neat "transparent" Win controls :o) ) and overriding:

procedure TMyTransparentWinControl.WMEraseBkgnd(var m : TWMEraseBkgnd);
begin
  m.Result := LRESULT(False);
end;

  The only remaining problem is to actually get the image of the parent form (and ideally any windowed controls beneath my control).
  Any thoughts?
0
 
MadshiCommented:
If you simply ignore the WM_ERASE messages in any of your parents, then of course there might be other TWinControls that we have to draw. But if the only reason is to get rid of the flickering, then forget about WM_ERASE, instead descend your control from TGraphicControl like I do, there really is no flickering with my control!! Okay, I'm not updating it every 30ms. But if I go with my mouse over my speed buttons, they change their outfit, and there is absolutely no flickering, while I do see flickering when using the standard TSpeedButton controls.
I think the reason why you have flickering is the way how your thread updates your control! You can do that without that the parent TWinControl even gets a WM_ERASE message! Don't use Invalidate, instead use InvalidateRect, there you can say, whether the parent TWinControl should get a WM_ERASE message. Or instead of using Invalidate(Rect) you can also directly draw on the canvas, that's even better. Then the parent TWinControl does not do anything.

Regards, Madshi.
0
 
StevenBAuthor Commented:
 InvalidateRect looks good, trying it now.

  I don't like drawing to the canvas outside of the paint method, because any updates have to be replicated there anyway, otherwise the control will lose its appearance if it is obscured briefly (by another form for example)
0
 
StevenBAuthor Commented:
 Cheers for all the comments and advice Madshi, I've managed to develop a satisfactory solution from them.

  Thanks, Steven.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.