StevenB
asked on
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?
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?
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.
You won't be able to accept input focus, but you can't do that with TGraphicContol either.
ASKER
This won't solve the flickering problem though will it? I'll check, but I cant see why it would.
The Picture property takes a TBitmap - so you would need to combine this approach with your idea of buffering into a bitmap.
ASKER
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)?
How do I get the background as a bitmap (bearing in mind that this background isn't necessarily even static)?
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.Tran sparentCol our and set it there.
You can either use the Bottom-Left pixel as the 'transparent colour' (this is the default) or you can go into TImage.Picture.Bitmap.Tran
ASKER
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.
I'm investigating your suggestion, however, in case I've missed something along the way :o)
Cheers,
Steven.
Thank you for explaining the likely shortcomings of the suggestion (I mean it).
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.ControlCoun t;
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.ClientHeigh t);
TParentControl(Control.Par ent).Paint Window(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.
(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.ControlCoun
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
TParentControl(Control.Par
RestoreDC(DC, SaveIndex);
{ Copy images of graphic controls }
for I := 0 to Count - 1 do begin
if (Control.Parent.Controls[I
(not (Control.Parent.Controls[I
(not (Control.Parent.Controls[I
if Control.Parent.Controls[I]
with Control.Parent.Controls[I]
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.
listening :-)
ASKER
TMRSpeedButton?
Guess that's one of your controls then ;o)
Looking at your code now Madshi ...
Guess that's one of your controls then ;o)
Looking at your code now Madshi ...
ASKER
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.
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
Cheers,
Steven.
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.
(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.
ASKER
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.W MEraseBkgn d(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?
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.W
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?
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.
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.
ASKER
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)
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)
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Cheers for all the comments and advice Madshi, I've managed to develop a satisfactory solution from them.
Thanks, Steven.
Thanks, Steven.
ASKER