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(EmbeddedWindow
Handle, 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(va
r Msg: TWMEraseBkgnd);
begin
Msg.Result := 1;
end;
//--- End WMEraseBkgnd --------------------------
----------
----------
---------/
/
//--- CreateParams --------------------------
----------
----------
----------
---\\
procedure TSkinLayer.CreateParams(va
r 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(In
dex: 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].OnPressDo
wn := OnPressDown;
FHotSpots[Index].Toggle := False;
end;
//--- End WriteHotSpot --------------------------
----------
----------
---------/
/
//--- ReadHotSpot --------------------------
----------
----------
----------
----\\
function TSkinLayer.ReadHotSpot(Ind
ex: 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].OnPressDo
wn;
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(Butto
n: 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].OnPressD
own = '') 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].Toggl
e) 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].OnPressU
p <> '') 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].OnPressU
p = '') 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].Toggl
e) 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]