How to create transparent control with TCustomTransparentControl? - delphi

I want to create a transparent panel that holds an PNG image that has transparency in it. I want to put this panel on top of other panels and see trough.
I have the code below but it won't accept controls. If I uncomment the commented lines the IDE raises an exception when I put the controls on the form.
unit TransparentPanel5;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Vcl.Controls, ExtCtrls;
type
TTransparentPanel5 = class(TCustomTransparentControl)
private
public
constructor Create(AOwner: TComponent); override;
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure Register;
implementation
constructor TTransparentPanel5.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
//ControlStyle := ControlStyle - [csSetCaption]+ [csAcceptsControls];
end;
procedure TTransparentPanel5.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
// Params.ExStyle := Params.ExStyle + WS_EX_Transparent;
// ControlStyle := ControlStyle - [csOpaque] + [csAcceptsControls]
end;
procedure Register;
begin
RegisterComponents('His', [TTransparentPanel5]);
end;
end.

You've got two unrelated questions. I chose to answer the second one.
The IDE raise an AV because you fail to apply extended styles properly. You have to use or operator to set a bit. When you use + you add up the value of the style bit and end up with an entirely different meaning, in this case with some WS_EX_MDICHILD which causes the CreateWindowEx call to fail.
Should be like this:
procedure TTransparentPanel5.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle or WS_EX_Transparent;
// ControlStyle := ControlStyle - [csOpaque] + [csAcceptsControls]
end;
Note that it won't have any effect since the TCustomTransparentControl already sets that extended style.

Related

Delphi 5 transparency breaks child draw

I have a control in Delphi 5 like this (following this post: https://tips.delphidabbler.com/tips/74.html):
type
TTransparentGroupbox = class(TGroupBox)
protected
procedure CreateParams(var params: TCreateParams); override;
procedure WMEraseBkGnd(var msg: TWMEraseBkGnd); message WM_ERASEBKGND;
end;
.....
procedure TTransparentGroupbox.CreateParams(var params: TCreateParams);
begin
inherited CreateParams(params);
params.ExStyle := params.ExStyle or WS_EX_TRANSPARENT;
end;
procedure TTransparentGroupbox.WMEraseBkGnd(var msg: TWMEraseBkGnd);
begin
SetBkMode(msg.DC, TRANSPARENT);
msg.result := 1;
end;
But I have trouble when it is redrawn. The portion not blurred should be listbox on such a TTransparentGroupbox, but it is not entirely redrawn, as if the background mode would have been propagated to the children but they are repainted on the TWinControl level. I have tried to set it back to OPAQUE at the only point I could override, but with no success:
procedure TTransparentGroupbox.PaintWindow(DC: HDC);
begin
SetBkMode(DC, OPAQUE);
inherited;
end;
The annoying part is, that I have other TTransparentGroupbox instances, with other TListBox instances that do not behave in this way.
The second problem I have is that whenever the visibility of a child control is changed to false, it is not "erased".

Flickering TSpeedButton in a TMaskEdit descendant

I try to create a TComboBox like component, inherited from TMaskEdit with a TSpeedButton inside the edit itself.
The problem when I type something into the edit the most of the button disappears (the right and the bottom edge still visible). If I move the mouse over the component or exit using TAB, the button appears again.
Here is the code:
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, Mask, Buttons;
type
TMyEdit = class(TCustomMaskEdit)
private
FButton: TSpeedButton;
protected
procedure CreateButton;
public
constructor Create(AOwner: TComponent); override;
procedure CreateWnd; override;
destructor Destroy; override;
end;
implementation
constructor TMyEdit.Create(AOwner: TComponent);
begin
inherited;
CreateButton;
end;
procedure TMyEdit.CreateButton;
begin
FButton := TSpeedButton.Create(Self);
FButton.Parent := Self;
FButton.Align := alRight;
FButton.Width := 16;
FButton.Caption := '';
FButton.Transparent := False;
end;
destructor TMyEdit.Destroy;
begin
FreeAndNil(FButton);
inherited;
end;
procedure TMyEdit.CreateWnd;
begin
inherited;
Perform(EM_SETMARGINS, EC_RIGHTMARGIN, (FButton.Width + 4) shl 16);
end;
What do I miss?
Solved.
WS_CLIPCHILDREN flag have to be included in the CreateParams() AND the button must be placed on TWinControl descendant (TPanel in my case) OR the button itself must be a TWinControl descendant (for example TButton). Graphic controls has no Handle, thats the problem.
Modified code:
procedure TMyEdit.CreateButton;
var
xDrawRect: TRect;
xPanel : TPanel;
begin
xPanel := TPanel.Create(Self);
xPanel.Parent := Self;
xPanel.SetBounds(Width - Height, 0, Height, Height);
xPanel.BevelOuter := bvNone;
FButton := TSpeedButton.Create(Self);
FButton.Parent := xPanel;
FButton.Align := alClient;
FButton.Caption := '';
end;
procedure TMyEdit.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or WS_CLIPCHILDREN;
end;

Selection box of composite component not drawn properly

I have a composite component that consists of a TEdit and a TButton (yes, I know about TButtonedEdit) that inherits from TCustomControl. The edit and button are created in its constructor and placed on itself.
At designtime the selection box is not drawn properly - my guess is that the edit and button are hiding it because its been drawn for the custom control and then overdrawn by them.
Here the comparison:
I have also seen this for other 3rd party components (like the TcxGrid also only draws the outer part of the selection indicator)
Question: how can I change that?
Most simple case for reproducing:
unit SearchEdit;
interface
uses
Classes, Controls, StdCtrls;
type
TSearchEdit = class(TCustomControl)
private
fEdit: TEdit;
public
constructor Create(AOwner: TComponent); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Custom', [TSearchEdit]);
end;
{ TSearchEdit }
constructor TSearchEdit.Create(AOwner: TComponent);
begin
inherited;
fEdit := TEdit.Create(Self);
fEdit.Parent := Self;
fEdit.Align := alClient;
end;
end.
As I said in the comments, the easiest thing I can think of is to paint the controls in the parent and "hide" them from the designer at design time. You can do this by calling SetDesignVisible(False) on each of the child controls. Then you use PaintTo to do the painting on the parent.
Using your example we get:
type
TSearchEdit = class(TCustomControl)
...
protected
procedure Paint; override;
...
end;
constructor TSearchEdit.Create(AOwner: TComponent);
begin
inherited;
fEdit := TEdit.Create(Self);
fEdit.Parent := Self;
fEdit.Align := alClient;
fEdit.SetDesignVisible(False);
end;
procedure TSearchEdit.Paint;
begin
Inherited;
if (csDesigning in ComponentState) then
fEdit.PaintTo(Self.Canvas, FEdit.Left, FEdit.Top);
end;

How to set CreateParams after the constructor has run?

TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
end;
TForm2 = class(TForm)
private
FAppWindow: Boolean;
protected
procedure CreateParams(var Params: TCreateParams); override;
public
property AppWindow: Boolean read FAppWindow write FAppWindow;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form2 := TForm2.Create(Self);
Form2.AppWindow := True;
Form2.Show;
end;
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited;
if FAppWindow then begin
Params.Style := Params.Style or WS_EX_APPWINDOW;
Params.WndParent := 0;
end;
end;
This doesn't work, because the window handle is created during the constructor of TForm, so CreateParams is run too early and FAppWindow is always False.
Writing a custom constructor also doesn't work since you have to eventually call the inherited constructor which creates the handle before you can save any data to the instance:
constructor TForm2.CreateAppWindow(AOwner: TComponent);
begin
inherited Create(AOwner);
FAppWindow := True;
end;
Is there a way to:
Delay the creation of the window handle?
Alter the window style after creation of the window handle?
Recreate the window handle after the constructor has run?
Some other option I haven't thought of, yet?
How can I change the style of a form from the "outside" of the class?
The simplest solution is to pass the parameter to the form in its constructor, rather than wait until it has finished being created.
That means you need to introduce a constructor for TForm2 that accepts as parameters whatever information you need to pass on in CreateParams.
Make a note of any state before you call the inherited constructor. Also, there's no need to set WS_EX_APPWINDOW when you are setting the owner to be zero.
The nice thing about Delphi is that a derived constructor DOES NOT have to call the inherited constructor as its first statement. So you can set your FAppWindow member first, THEN call the inherited constructor to stream the DFM and create the window, eg:
procedure TForm1.FormCreate(Sender: TObject);
begin
Form2 := TForm2.CreateAppWindow(Self);
Form2.Show;
end;
constructor TForm2.CreateAppWindow(AOwner: TComponent);
begin
FAppWindow := True;
inherited Create(AOwner);
end;
This seems to work to recreate the handle, I got the idea from the RecreateAsPopup VCL method:
procedure TForm2.SetAppWindow(const Value: Boolean);
begin
FAppWindow := Value;
if HandleAllocated then
RecreateWnd
else
UpdateControlState;
end;

How to make my TCustomControl descendant component stop flickering?

I have a graphical TCustomControl descendant component with a TScrollBar on it. The problem is that when I press the arrow key to move the cursor the whole canvas is painted in background color, including the region of the scroll bar, then the scroll bar is repainted and that makes scroll bar flicker. How can I solve this ?
Here is the code. There is no need install the component or to put something on the main form, just copy the code and assign TForm1.FormCreate event:
Unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, SuperList;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
List: TSuperList;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
List:=TSuperList.Create(self);
List.Top:=50; List.Left:=50;
List.Visible:=true;
List.Parent:=Form1;
end;
end.
SuperList.pas
unit SuperList;
interface
uses Windows, Controls, Graphics, Classes, Messages, SysUtils, StdCtrls, Forms;
type
TSuperList = class(TCustomControl)
public
DX,DY: integer;
ScrollBar: TScrollBar;
procedure Paint; override;
constructor Create(AOwner: TComponent); override;
procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
published
property OnMouseMove;
property OnKeyPress;
property OnKeyDown;
property Color default clWindow;
property TabStop default true;
property Align;
property DoubleBuffered default true;
property BevelEdges;
property BevelInner;
property BevelKind default bkFlat;
property BevelOuter;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Marus', [TSuperList]);
end;
procedure TSuperList.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result:= Message.Result or DLGC_WANTARROWS;
end;
procedure TSuperList.WMKeyDown(var Message: TWMKeyDown);
begin
if Message.CharCode=VK_LEFT then begin dec(DX,3); Invalidate; exit; end;
if Message.CharCode=VK_RIGHT then begin inc(DX,3); Invalidate; exit; end;
if Message.CharCode=VK_UP then begin dec(DY,3); Invalidate; exit; end;
if Message.CharCode=VK_DOWN then begin inc(DY,3); Invalidate; exit; end;
inherited;
end;
procedure TSuperList.WMLButtonDown(var Message: TWMLButtonDown);
begin
DX:=Message.XPos;
DY:=Message.YPos;
SetFocus;
Invalidate;
inherited;
end;
constructor TSuperList.Create(AOwner: TComponent);
begin
inherited;
DoubleBuffered:=true;
TabStop:=true;
Color:=clNone; Color:=clWindow;
BevelKind:=bkFlat;
Width:=200;
Height:=100;
DX:=5; DY:=50;
ScrollBar:=TScrollBar.Create(self);
ScrollBar.Kind:=sbVertical;
ScrollBar.TabStop:=false;
ScrollBar.Align:=alRight;
ScrollBar.Visible:=true;
ScrollBar.Parent:=self;
end;
procedure TSuperList.Paint;
begin
Canvas.Brush.Color:=Color;
Canvas.FillRect(Canvas.ClipRect);
Canvas.TextOut(10,10,'Press arrow keys !');
Canvas.Brush.Color:=clRed;
Canvas.Pen.Color:=clBlue;
Canvas.Rectangle(DX,DY,DX+30,DY+20);
end;
end.
I think the first thing that I would do is remove that scroll bar control. Windows come with ready made scroll bars. You just need to enable them.
So, start by removing ScrollBar from the component. Then add a CreateParams override:
procedure CreateParams(var Params: TCreateParams); override;
Implement it like this:
procedure TSuperList.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_VSCROLL;
end;
Yippee, your control now has a scroll bar.
Next you need to add a handler for WM_VSCROLL:
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
And that's implemented like this:
procedure TSuperList.WMVScroll(var Message: TWMVScroll);
begin
case Message.ScrollCode of
SB_LINEUP:
begin
dec(DY, 3);
Invalidate;
end;
SB_LINEDOWN:
begin
inc(DY, 3);
Invalidate;
end;
...
end;
end;
You'll need to fill out the rest of the scroll codes.
I would also suggest that you do not set DoubleBuffered in the constructor of your component. Let the user set that if they wish. There's no reason for your control to require double buffering.

Resources