Solved

Windows graphic question

Posted on 2001-06-18
18
539 Views
Last Modified: 2012-05-04
 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?
0
Comment
Question by:StevenB
  • 9
  • 4
  • 4
  • +1
18 Comments
 
LVL 4

Author Comment

by:StevenB
ID: 6204615
 ... or can you think of a better way of doing this?
0
 
LVL 1

Expert Comment

by:Greyman
ID: 6204704
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
 
LVL 4

Author Comment

by:StevenB
ID: 6204712
 This won't solve the flickering problem though will it? I'll check, but I cant see why it would.
0
Gigs: Get Your Project Delivered by an Expert

Select from freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely and get projects done right.

 
LVL 1

Expert Comment

by:Greyman
ID: 6204717
The Picture property takes a TBitmap - so you would need to combine this approach with your idea of buffering into a bitmap.
0
 
LVL 4

Author Comment

by:StevenB
ID: 6204725
 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
 
LVL 1

Expert Comment

by:Greyman
ID: 6204734
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
 
LVL 4

Author Comment

by:StevenB
ID: 6204750
 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
 
LVL 1

Expert Comment

by:Greyman
ID: 6204877
Thank you for explaining the likely shortcomings of the suggestion (I mean it).
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6205286
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
 
LVL 5

Expert Comment

by:Gwena
ID: 6206244
listening :-)
0
 
LVL 4

Author Comment

by:StevenB
ID: 6208675
 TMRSpeedButton?
  Guess that's one of your controls then ;o)

  Looking at your code now Madshi ...
0
 
LVL 4

Author Comment

by:StevenB
ID: 6208808
 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
 
LVL 20

Expert Comment

by:Madshi
ID: 6209244
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
 
LVL 4

Author Comment

by:StevenB
ID: 6209282
 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
 
LVL 20

Expert Comment

by:Madshi
ID: 6209297
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
 
LVL 4

Author Comment

by:StevenB
ID: 6212563
 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
 
LVL 20

Accepted Solution

by:
Madshi earned 200 total points
ID: 6213076
>> 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
 
LVL 4

Author Comment

by:StevenB
ID: 6213150
 Cheers for all the comments and advice Madshi, I've managed to develop a satisfactory solution from them.

  Thanks, Steven.
0

Featured Post

Live: Real-Time Solutions, Start Here

Receive instant 1:1 support from technology experts, using our real-time conversation and whiteboard interface. Your first 5 minutes are always free.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Convert Jpg /PNG To GIF 5 135
Delphi XE10 Round Image 2 126
delphi parse string to params 3 122
LAN or WAN ? 11 90
This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
This Micro Tutorial will give you a basic overview how to record your screen with Microsoft Expression Encoder. This program is still free and open for the public to download. This will be demonstrated using Microsoft Expression Encoder 4.
Nobody understands Phishing better than an anti-spam company. That’s why we are providing Phishing Awareness Training to our customers. According to a report by Verizon, only 3% of targeted users report malicious emails to management. With compan…

776 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question