How to create a transparent TPanel #210

Answer 1

Tip originally posted for Delphi 2

Particularly note the SetParent bit. It works even with movement. It should even work in Delphi 1, as it doesn't use the Win32 non-rectangular-window method for creating transparency. The code is simple so can be easily retro-fitted to any control that you wished were transparent. I put this together in ten minutes, so it needs proper testing to make sure it doesn't cause any problems, but here it is.

Create one on a form, and drag it about over some edits, combo boxes etc. (and TImages and you'll get major flicker).

type
 
  TTransparentPanel = class(TPanel)
private
  procedure SetParent(AParent: TWinControl); override;
  procedure WMEraseBkGnd(Var Message: TWMEraseBkGnd); message WM_EraseBkGnd;
protected
  procedure CreateParams(Var Params: TCreateParams); override;
  procedure Paint; override;
public
  constructor Create(AOwner: TComponent); override;
  procedure Invalidate; override;
end;
 
constructor TTransparentPanel.Create(AOwner: TComponent);
begin
  Inherited Create(AOwner);
  ControlStyle := ControlStyle - [csOpaque];
end;
 
procedure TTransparentPanel.CreateParams(Var Params: TCreateParams);
begin
  Inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
 
procedure TTransparentPanel.Paint;
begin
  Canvas.Brush.Style := bsClear;
  Canvas.Rectangle(0, 0, Width, Height);
  Canvas.TextOut(Width div 2, Height div 2, 'Transparent');
end;
 
procedure TTransparentPanel.WMEraseBkGnd(Var Message: TWMEraseBkGnd);
begin
  {Do Nothing}
  Message.Result := 1;
end;
 
procedure TTransparentPanel.SetParent(AParent: TWinControl);
begin
  Inherited SetParent(AParent);
  {The trick needed to make it all work! I don't know if changing the parent's
  style is a good idea, but it only removes the WS_CLIPCHILDREN style which
  shouldn't cause any problems.}
  if Parent <> Nil then
    SetWindowLong(
      Parent.Handle,
      GWL_STYLE,
      GetWindowLong(Parent.Handle, GWL_STYLE) And Not WS_ClipChildren
    );
end;
 
procedure TTransparentPanel.Invalidate;
var
  Rect:TRect;
begin
  Rect := BoundsRect;
  if (Parent <> Nil) and Parent.HandleAllocated then
    InvalidateRect(Parent.Handle, @Rect, True)
  else
    Inherited Invalidate;
end;

Tip author unknown

Answer 2

unit TransparentPanel;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls;
 
type
  TTransparentPanel = class(TPanel)
  private
    FBackground: TBitmap;
    procedure WMEraseBkGnd( Var msg: TWMEraseBkGnd ); message WM_ERASEBKGND;
  protected
    procedure CaptureBackground;
    procedure Paint; override;
  public
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    property Canvas;
    constructor Create( aOwner: TComponent ); override;
    destructor Destroy; override;
  published
    { Published declarations }
  end;
 
procedure Register;
 
implementation
 
procedure Register;
begin
  RegisterComponents('PBGoodies', [TTransparentPanel]);
end;
 
procedure TTransparentPanel.CaptureBackground;
var
  canvas: TCanvas;
  dc: HDC;
  sourcerect: TRect;
begin
  FBackground := TBitmap.Create;
  with Fbackground do
  begin
    width := clientwidth;
    height := clientheight;
  end;
  sourcerect.TopLeft := ClientToScreen(clientrect.TopLeft);
  sourcerect.BottomRight := ClientToScreen( clientrect.BottomRight );
  dc:= CreateDC( 'DISPLAY', nil, nil, nil );
  try
    canvas:= TCanvas.Create;
    try
      canvas.handle:= dc;
      Fbackground.Canvas.CopyRect( clientrect, canvas, sourcerect );
    finally
      canvas.handle := 0;
      canvas.free;
    end;
  finally
    DeleteDC( dc );
  end;
end;
 
constructor TTransparentPanel.Create(aOwner: TComponent);
begin
  inherited;
  ControlStyle := controlStyle - [csSetCaption];
end;
 
destructor TTransparentPanel.Destroy;
begin
  FBackground.free;
  inherited;
end;
 
procedure TTransparentPanel.Paint;
begin
  if csDesigning In ComponentState then
    inherited
    {would need to draw frame and optional caption here do not call
    inherited, the control fills its client area if you do}
end;
 
procedure TTransparentPanel.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  if Visible and HandleAllocated and not (csDesigning In ComponentState) then
  begin
    Fbackground.Free;
    Fbackground := Nil;
    Hide;
    inherited;
    Parent.Update;
    Show;
  end
  else
    inherited;
end;
 
procedure TTransparentPanel.WMEraseBkGnd(var msg: TWMEraseBkGnd);
var
  canvas: TCanvas;
begin
  if csDesigning In ComponentState then
    inherited
  else
  begin
    if not Assigned( FBackground ) then
      Capturebackground;
    canvas := TCanvas.create;
    try
      canvas.handle := msg.DC;
      canvas.draw( 0, 0, FBackground );
    finally
      canvas.handle := 0;
      canvas.free;
    end;
    msg.result := 1;
  end;
end;
 
end.

Tip by Peter Below

Answer 3

This panel will be transparent only at runtime.

{ ... }
 
type
  TMyPopUpTransPanel = class(TPanel)
  protected
    procedure CMHitTest(var Message: TCMHitTest); message CM_HITTEST;
    procedure WndProc(var Message: TMessage); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure Paint; override;
  end;
 
{ ... }
 
procedure TMyPopUpTransPanel.CMHitTest(var Message: TCMHitTest);
begin
  Message.Result:=Windows.HTNOWHERE;
end;
 
procedure TMyPopUpTransPanel.WndProc(var Message: TMessage);
var
  XControl: TControl;
  XPos: TPoint;
begin
  if not (csDesigning in ComponentState) and ((Message.Msg >= WM_MOUSEFIRST)
    and (Message.Msg <= WM_MOUSELAST)) then
  begin
    XPos := ClientToScreen(
      POINT(TWMMouse(Message).XPos, TWMMouse(Message).YPos)
    );
    XControl := Parent.ControlAtPos(
      POINT(TWMMouse(Message).XPos + Left,
      TWMMouse(Message).YPos + Top),
      true, true
    );
    if Assigned(XControl) and (XControl is TWinControl) then
    begin
      XPos := TWinControl(XControl).ScreenToClient(XPos);
      TWMMouse(Message).XPos := XPos.X;
      TWMMouse(Message).YPos := XPos.Y;
      PostMessage(
        TWinControl(XControl).Handle, Message.Msg,
        Message.WParam, Message.LParam
      );
    end
    else
    begin
      XPos := Parent.ScreenToClient(XPos);
      TWMMouse(Message).XPos := XPos.X;
      TWMMouse(Message).YPos := XPos.Y;
      PostMessage(Parent.Handle, Message.Msg, Message.WParam, Message.LParam);
    end;
    Message.Result := 0;
  end
  else
    inherited WndProc(Message);
end;
 
procedure TMyPopUpTransPanel.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  if not (csDesigning in ComponentState) then
    Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
 
procedure TMyPopUpTransPanel.Paint;
var
  XBitMap: TBitMap;
  XOldDC: HDC;
  XRect: TRect;
begin
  if (csDesigning in ComponentState) then
    inherited Paint
  else
  begin
    XRect := ClientRect;
    XOldDC := Canvas.Handle;
    XBitMap := TBitMap.Create;
    try
      XBitMap.Height := Height;
      XBitMap.Width := Width;
      Canvas.Handle := XBitMap.Canvas.Handle;
      inherited Paint;
      RedrawWindow(
        Parent.Handle,
        @XRect, 0,
        RDW_ERASE or RDW_INVALIDATE or RDW_NOCHILDREN or RDW_UPDATENOW
      );
    finally
      Canvas.Handle := XOldDC;
      Canvas.BrushCopy(XRect, XBitMap, XRect, Color);
      XBitMap.Free;
    end;
  end;
end;

Tip by Serge Gubenko

Original resource: The Delphi Pool
Author: Various
Added: 2013/01/27
Last updated: 2013/01/27