Project: SKINbedder. Embeds other programs windows to my TWindowContainer component (CustomControl), then overlays semi-transparent PNG graphics with multiple "Hot-Spot" buttons through a TSkinLayer component(CustomControl). (mostly used to "skin" iGuidance GPS navigation software)
Problem: When the embedded window repaints its self, my buttons disappear. They are not re-drawn because no WM_PAINT message gets received (even though according to MSDN, a WS_EX_TRANSPARENT window should receive a WM_PAINT whenever anything underneath it is re-drawn). I cannot continually repaint because it ruins the semi-transparency, causes flicker, and eats CPU time. I cannot use Rgn for a transparency effect because it wouldn't work for semi-transparency plus I want to intercept mouse clicks even on the transparent parts (no click-through).
Question: I need a way to know when my semi-transparent buttons have been erased by the embedded window repainting its self so I can repaint my buttons. Basically I need to 1) Intercept all WM_PAINT messages to it (although I'm not sure that would work if it is repainting due to its own internal event), 2) Assign the embedded window an OnPaint or actually a "AfterPaint" event (probably impossible), or 3) Figure out why my component doesn't always receive WM_PAINT whenever anything behind it changes (as WS_EX_TRANSPARENT controls should according to M$)
Quirk / Possible alternate solution: If you minimize then restore my app, everything is always drawn perfectly. I guess this is because the embedded window paints first and then I paint. I have tried to continually repaint but also continually SendMessage(EmbeddedWindowHandle, WM_PAINT, 0, 0) first. It didn't repaint its self over my buttons though so that did not solve the ruining semi-transparency bug as I had hoped.
Source Code: Below is the source code to my TSkinLayer. Any suggestions about any part of the component are welcome ;). This and TWindowContainer are my first 2 components ever (and are based on TmodderBut which was posted here on experts-exchange for an earlier version of this same program).
[code]
unit SkinLayer;
interface
uses
// Dialogs,
Windows, Messages, Classes, Graphics, Controls, Forms, PNGImage;
type
THotSpots = record
Top: integer;
Bottom: integer;
Left: integer;
Right: integer;
OnPressDown: String;
OnPressUp: String;
Toggle: Boolean;
end;
TSkinLayer = class(TCustomControl)
private
protected
FNorm, FDown: TPNGObject;
FHotSpots: Array of THotSpots;
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure CreateParams(var Params: TCreateParams); override;
procedure Paint; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure WriteHotSpot(Index: integer; Top, Bottom, Left, Right: integer; OnPressUp, OnPressDown: String);
function ReadHotSpot(Index: integer; var Top: Integer; var Bottom: Integer; var Left: Integer; var Right: integer; var OnPressUp: String; var OnPressDown: String): Boolean;
property Canvas;
published
property NormalBmp: TPNGObject read FNorm write FNorm;
property DownBmp: TPNGObject read FDown write FDown;
property Height default 1;
property Width default 1;
property Visible default False;
property Cursor default crCross;
property Name;
property Anchors;
property Left;
property Top;
property OnMouseDown;
property OnMouseUp;
end;
procedure Register;
implementation
uses
SysUtils;
procedure Register;
begin
RegisterComponents('RPM', [TSkinLayer]);
end;
//--- WMEraseBkgnd -----------------------------------------------------------\\
procedure TSkinLayer.WMEraseBkgnd(var Msg: TWMEraseBkgnd);
begin
Msg.Result := 1;
end;
//--- End WMEraseBkgnd -------------------------------------------------------//
//--- CreateParams -----------------------------------------------------------\\
procedure TSkinLayer.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
WindowClass.lpszClassName := 'TSkinLayer';
WindowClass.hbrBackground := 0;
WindowClass.style := WindowClass.style OR CS_HREDRAW OR CS_VREDRAW OR CS_OWNDC OR CS_SAVEBITS;
Style := Style OR WS_CHILD OR WS_CLIPSIBLINGS AND NOT(WS_TABSTOP) AND NOT(WS_VISIBLE);
ExStyle := ExStyle OR WS_EX_TRANSPARENT;
end;
end;
//--- End CreateParams -------------------------------------------------------//
//--- Create -----------------------------------------------------------------\\
constructor TSkinLayer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := [csCaptureMouse, csClickEvents, csFixedWidth, csFixedHeight, csReflector];
Width := 1;
Height := 1;
Cursor := crCross;
Visible := False;
FNorm := TPNGObject.Create;
FDown := TPNGObject.Create;
end;
//--- End Create -------------------------------------------------------------//
//--- Destroy ----------------------------------------------------------------\\
destructor TSkinLayer.Destroy;
begin
FreeAndNil(FNorm);
FreeAndNil(FDown);
inherited Destroy;
end;
//--- End Destroy ------------------------------------------------------------//
//--- WriteHotSpot -----------------------------------------------------------\\
procedure TSkinLayer.WriteHotSpot(Index: integer; Top, Bottom, Left, Right: integer; OnPressUp, OnPressDown: String);
begin
if (length(FHotSpots) < (Index + 1)) then
SetLength(FHotSpots, Index + 1);
FHotSpots[Index].Top := Top;
FHotSpots[Index].Bottom := Bottom;
FHotSpots[Index].Left := Left;
FHotSpots[Index].Right := Right;
FHotSpots[Index].OnPressUp := OnPressUp;
FHotSpots[Index].OnPressDown := OnPressDown;
FHotSpots[Index].Toggle := False;
end;
//--- End WriteHotSpot -------------------------------------------------------//
//--- ReadHotSpot ------------------------------------------------------------\\
function TSkinLayer.ReadHotSpot(Index: integer; var Top: Integer; var Bottom: Integer; var Left: Integer; var Right: integer; var OnPressUp: String; var OnPressDown: String): Boolean;
begin
if Index < length(FHotSpots) then
begin
Top := FHotSpots[Index].Top;
Bottom := FHotSpots[Index].Bottom;
Left := FHotSpots[Index].Left;
Right := FHotSpots[Index].Right;
OnPressUp := FHotSpots[Index].OnPressUp;
OnPressDown := FHotSpots[Index].OnPressDown;
Result := True;
end
else
Result := False;
end;
//--- End ReadHotSpot --------------------------------------------------------//
//--- Paint ------------------------------------------------------------------\\
procedure TSkinLayer.Paint;
var
Index: Integer;
begin
Parent.Update; //Reduces re-draw errors while draging a window in front of us.
//Draw FNorm and then overlay FDown if button is Toggled (bad method!)
if not(FNorm.Empty) then
begin
FNorm.Draw(Canvas, Rect(0, 0, FNorm.Width, FNorm.Height));
if not(FDown.Empty) then
begin
Index := 0;
while (Index < length(FHotSpots)) do
begin
if (FHotSpots[Index].Toggle) then
FDown.Draw(Canvas, Rect(FHotSpots[Index].Left, FHotSpots[Index].Top, FHotSpots[Index].Right, FHotSpots[Index].Bottom));
Index := Index + 1;
end;
end;
end;
end;
//--- End Paint --------------------------------------------------------------//
//--- MouseDown --------------------------------------------------------------\\
procedure TSkinLayer.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
Index := 0;
while (Index < length(FHotSpots)) do
begin
if (X > FHotSpots[Index].Left) and (X < FHotSpots[Index].Right) and (Y > FHotSpots[Index].Top) and (Y < FHotSpots[Index].Bottom) then
begin
if (FHotSpots[Index].OnPressDown = '') then
begin
//No Down Command: Just paint FDown to HotSpot.
if (not FDown.Empty) then
FDown.Draw(Canvas, Rect(FHotSpots[Index].Left, FHotSpots[Index].Top, FHotSpots[Index].Right, FHotSpots[Index].Bottom));
end
else if not(FHotSpots[Index].Toggle) then
begin
//Non-Toggle or 1st-Click: Run command & paint FDown to HotSpot.
inherited; //Command is run in external OnMouseDown Event (assigned in MainForm on create)
if (not FDown.Empty) then
FDown.Draw(Canvas, Rect(FHotSpots[Index].Left, FHotSpots[Index].Top, FHotSpots[Index].Right, FHotSpots[Index].Bottom));
//If Toggle, set flag.
if (FHotSpots[Index].OnPressUp <> '') then
FHotSpots[Index].Toggle := True;
end
else
//Toggle-Button 2nd-Click: Do nothing, but handle next up.
FHotSpots[Index].Toggle := False;
end;
Index := Index + 1;
end;
end;
//--- End MouseDown ----------------------------------------------------------//
//--- MouseUp ----------------------------------------------------------------\\
procedure TSkinLayer.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
Index := 0;
while (Index < length(FHotSpots)) do
begin
if (X > FHotSpots[Index].Left) and (X < FHotSpots[Index].Right) and (Y > FHotSpots[Index].Top) and (Y < FHotSpots[Index].Bottom) then
begin
if (FHotSpots[Index].OnPressUp = '') then
begin
//No Up Command: Just paint FNorm to HotSpot.
if not(FNorm.Empty) then
FNorm.Draw(Canvas, Rect(FHotSpots[Index].Left, FHotSpots[Index].Top, FHotSpots[Index].Right, FHotSpots[Index].Bottom));
end
else if not(FHotSpots[Index].Toggle) then
begin
//Non-Toggle or 2nd-Click: Run Command and paint FNorm to HotSpot
inherited; //Command is run in external OnMouseDown Event (assigned in MainForm on create)
if not(FNorm.Empty) then
FNorm.Draw(Canvas, Rect(FHotSpots[Index].Left, FHotSpots[Index].Top, FHotSpots[Index].Right, FHotSpots[Index].Bottom));
end;
//If 1st-Click on Toggle-Button, do nothing.
end;
Index := Index + 1;
end;
end;
//--- End MouseUp ------------------------------------------------------------//
end.
[/code]
by: Slick812Posted on 2005-09-23 at 07:51:45ID: 14944736
hello rpmccormi77, , I am mostly commenting here because no one else has posted anything, I do not have a direct fix for your problem. . .
You say -
" a WS_EX_TRANSPARENT window should receive a WM_PAINT whenever anything underneath it is re-drawn "
I do not think this is correct, and I have used windows with the WS_EX_TRANSPARENT style, what the system seems to do is call for the Painting of windows below (Z-Order), , Before, it calls the Painting of the WS_EX_TRANSPARENT window, WHEN there is an area invalidation that has the WS_EX_TRANSPARENT window in it, as this will institute the WM_PAINT messages. . . I do not think MS indicates that any refreshing of windows below the WS_EX_TRANSPARENT window, will call the WM_PAINT for the WS_EX_TRANSPARENT window, only WM_PAINT is sent if the area is invalidated?
anyway, you might try a Timer, with a 500 msecond event, and call the API functions -
InvalidateRect( )
or
RedrawWindow( )
to get a continuous updating of the area of the WS_EX_TRANSPARENT window, but this does not seem very efficient to me. .
you could also maybe, after your WS_EX_TRANSPARENT window's WM_PAINT, get a small bitmap of the area where your button (or buttons) is, and then , on a timer event, get another bitmap of that same area, and do a scanline compare of the bitmaps, if the bitmaps are not equal (no button image), then call InavalidateRect( ),, , ,
however there may be other problem will both of these methods. . you may need to limit these timer events to just when your app has the keboard focus (top window, forground window)