i have a problem with the VCL-Styles and MDI-Form. I want to use the VCL Styles, but i also want to draw the background (image) of my MainForm (MDI) by myself. This worked fine without VCL Styles, but when a Style is active the background image of the MainForm isn't shown.
I checked out the StyleElements for the MainForm, but exclude the seClient is ignoerd and the background image isn't shown.
When i exclude the seClient and seBoarder the image is shown again. Obviously the Form Boarder lost the Style, which is also not that what i want.
The image is drawn at the Canvas in the ClientWndProc by the messages WM_ERASEBKGND, WM_VSCROLL and WM_HSCROLL. With the Styles, it looks like this events didn't raise. Is there any way the get the image at the form background with VCL Styles active?
The main point to realize here is that form styled fsMDIForm is a very special TWinControl that manages two window handles instead of one - TWinControl.Handle and TForm.ClientHandle. While the first handle is the form window itself the second is MDI client window (container-like for MDI child windows inside MDI parent).
TFormStyleHook hooks both window procedures and introduces new method TFormStyleHook.MDIClientWndProc, which processes messages sent to MDI client. This method luckily virtual. It does some pre-processing of messages and then calls the original hooked procedure. The sad part is that it prevents calling the old procedure for WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCPAINT and WM_ERASEBKGND. Even worse is that on WM_ERASEBKGND it paints the client area background directly using StyleServices.
Thanks to the above the subclassing of TFormStyleHook for MDI forms a PITA. I see multiple design flaws here:
Missing virtual TFormStyleHook.PaintMDIClientBackground similar to TFormStyleHook.PaintBackground.
No way to control/access over the original MDI client proc without hacking (hidden in private field FMDIPrevClientProc).
Disability to control styling of MDI client window via TForm.StyleElements (as noted by OP).
So what is the workaround? The easiest I can see is creating a custom style hook:
type
TMainFormStyleHook = class(TFormStyleHook)
public
procedure MDIClientWndProc(var Message: TMessage); override;
end;
{ TMainFormStyleHook }
procedure TMainFormStyleHook.MDIClientWndProc(var Message: TMessage);
begin
if Message.Msg = WM_ERASEBKGND then
begin
{ TODO: Paint background to TWMEraseBkgnd(Message).DC }
Message.Result := 1;
end
else
inherited;
end;
and applying it to your MDI parent:
type
TMainForm = class(TForm)
private
class constructor Create;
class destructor Destroy;
{ ... }
end;
{ TMainForm }
class constructor TMainForm.Create;
begin
TCustomStyleEngine.RegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
class destructor TMainForm.Destroy;
begin
TCustomStyleEngine.UnRegisterStyleHook(TMainForm, TMainFormStyleHook);
end;
Note that you still need to keep painting background in MDI parent form in case the VCL styles are disabled, so it's worth creating method TMainForm.PaintMDICLientBackground(DC: HDC) and call it from both places.
I would argue that this is a bug in VCL. How about you guys?
Related
How can my Delphi component detect at design time if any other component is being dropped on the form?
You should override Notification method of your component; something like that:
type
TMyComponent = class(TComponent)
//..
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
end;
procedure TMyComponent.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opInsert) and (csDesigning in ComponentState) then begin
// AComponent was dropped on the form
end;
end;
If you mean controls being dropped instead of components, and if you mean dropping on your component rather than dropping on the form, then:
Add an CM_CONTROLLISTCHANGE message handler to track controls before they are inserted, or
Add an CM_CONTROLCHANGE message handler to track controls after they are inserted (WParam points to the control),
Don't do this until csLoading is out of ComponentState to prevent tracking during form creation by the IDE.
Just a tip, if it helps... i am having a similar issue:
I want to hide non-visual component icon (for my TMyLabel=class(Classes.TComponent) component) when i drop it from IDE inside a Form, Panel, etc ...
I have oveloaded: Loaded and ReadState to get such icon out of sight (at design-time) ... on Loaded & ReadState i put DesignInfo to point to (-100,-100), so icon is not shown
I have oveloaded: WriteState to avoid Left & Top be saved to the .dfm (at design-time) ... on WriteState i put it to point (0,0), so it is not saved inside the .dfm
Note: I use same technique/trick to not save properties i do not want, etc ... i really only let Caption to be saved inside the .dfm
The question / tip is:
When i drop a new "control" (of my component) into the form, such icon is visible just where i drop it... how to hide it?
Maybe user1580348 is trying something similar ... or something related to auto-align such non-visual "controls" / "components".
In other words:
How can we control the icon position when dropping a new (our component) control on a form, panel, etc...
I know my problem is much easier, but i did not yet solve it... i want just to hide that icon IDE shows for non-visual components (only for controls that are of my component)... but maybe knowing how to it will also helps user1580348.
As i said, it is just a tip/clue.
I am using the new VCL styles system in Delphi XE2 and its work fine but on one Form I want exception. This Form contains number of TBitBtn control and each TBitBtn control has its own Font colour (clRed, clBlue, clLime etc) different from other.
Due to Style implementation all TBitBtn control’s Caption is display in black colour instead of set colour.
Is there any TStyleHook, which can be register on TBitBtn control, which disabled the Style on TBitBtn Control on that form?
The TBitBtn component doesn't use a vcl style hook, this control use the TButtonGlyph class (which is defined and implemented in the implementation part of the Vcl.Buttons unit) to draw the button using the Windows theme or the current vcl style, this class (TButtonGlyph) is not accessible outside of this unit , so you are out of luck here.
The only option which comes to my mind is create a interposer class and intercept the CN_DRAWITEM message for the TBitBtn control and then execute your own code to draw the button.
TBitBtn = class(Vcl.Buttons.TBitBtn)
private
procedure MyDrawItem(const DrawItemStruct: TDrawItemStruct);
public
procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
end;
procedure TBitBtn.CNDrawItem(var Message: TWMDrawItem);
begin
MyDrawItem(Message.DrawItemStruct^);
end;
procedure TBitBtn.MyDrawItem(const DrawItemStruct: TDrawItemStruct);
begin
//the new code goes here.
end;
I want to show a TPanel in the middle of a form that is MDI parent for other forms. Some kind of 'splash' form, but not quite. The panel will contain links/buttons/shortcuts from where the user will call misc. functions.
The main requirement is that the TPanel should be placed below the MDI child form(s) when I click the MDI child. However, as it is, the TPanel will ALWAYS stay above the MDI child forms.
Calling Panel.SendToBack will make the panel disappear. How can I do?
You will need to override the Panel's WindowProc so that the panel will always be behind the MDI children e.g.:
TMainForm = class(TForm)
...
private
FPanelWndProc: TWndMethod;
procedure PanelWndProc(var M: TMessage);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Windows.SetParent(Panel1.Handle, ClientHandle);
// Override Panel1 WindowProc
FPanelWndProc := Panel1.WindowProc;
Panel1.WindowProc := PanelWndProc;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// Restore Panel1 WindowProc
Panel1.WindowProc := FPanelWndProc;
end;
procedure TMainForm.PanelWndProc(var M: TMessage);
var
P: ^WINDOWPOS;
begin
if M.Msg = WM_WINDOWPOSCHANGING then
begin
P := Pointer(M.LParam);
// Always place panel at bottom
P.hwndInsertAfter := HWND_BOTTOM;
end;
FPanelWndProc(M);
end;
Note: To quickly test the code, you can create a MDI application via File -> New -> MDI Application
EDIT: The code above dose infact answers your initial question. If you want your "Panel to behave somehow as a MDI child" (your comment quote), then simply (...hmmmm...) use a MDI Child form. i.e. create a new form with .FormStyle = fsMDIChild, and then use something like:
SetWindowLong(Child.Handle, GWL_STYLE,
GetWindowLong(Child.Handle, GWL_STYLE) and not (WS_BORDER or WS_DLGFRAME or WS_SIZEBOX));
To remove it's border (since simply setting .BorderStyle = bsNone does not work).
Put whatever you need on that form, and it will move above other MDI forms once you click it.
The MDI system works by having a single window that is the parent of all the MDI child windows, known as the client window. That client window is, in turn, a child of the MDI form. The VCL implementation of MDI creates the child window for you. You can gain access to its window handle through the ClientHandle.
Since the client window is a child of the main form, and parents all the MDI forms, the only solution for you is to make this panel part of the client window.
You could take control of the painting of the client window. You can do this by replacing the window proc of the client window with one of your own. You'll also need to handle button clicks etc. But that's pretty messy.
Now, perhaps you could make your panel a child of the client window. But I'm pretty sure that will screw up your MDI, which indeed you confirm to be the case.
I'm using delphi 2010
I agree with Andreas and Serg in that the control is transparent when themes are enabled.
I, once, had tried to make the CheckBox transparent for when runtime themes are not enabled in project options, or a classic theme is selected with the OS; the result was not perfect. The below is the same code applied to the RadioButton.
Problems easily noticable are, as you would guess from the code, it's a bit flickery and it is not transparent when DoubleBuffered. A problem not easily noticable can (sometimes) be duplicated by bringing a different window in front of the form containing the controls, and then slowly moving it aside, sometimes this leaves some artifacts.
Well, anyway, here it is;
type
TMyRadioButton = class(TRadioButton)
private
procedure CnCtlColorStatic(var Msg: TWMCtlColorStatic); message CN_CTLCOLORSTATIC;
procedure WmEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WmPaint(var Msg: TWMNCPaint); message WM_PAINT;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
uses
themes;
procedure TMyRadioButton.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
procedure TMyRadioButton.WmPaint(var Msg: TWMNCPaint);
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered) then
InvalidateRect(Handle, nil, True);
inherited;
end;
procedure TMyRadioButton.WmEraseBkgnd(var Msg: TWMEraseBkgnd);
var
R: TRect;
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered)
and (Parent <> nil) then begin
R := Rect(Left, Top, Left + Width, Height + Top);
InvalidateRect(Parent.Handle, #R, True);
UpdateWindow(Parent.Handle);
Msg.Result := 1;
end else
inherited;
end;
procedure TMyRadioButton.CnCtlColorStatic(var Msg: TWMCtlColorStatic);
begin
if not (ThemeServices.ThemesEnabled or DoubleBuffered) then begin
SetBKMode(Msg.ChildDC, TRANSPARENT);
Msg.Result := GetStockObject(NULL_BRUSH);
end else
inherited;
end;
Quote Remy Lebeau (TeamB):
TLabel is a TGraphicControl
descendant, and thus has to do all of
its own drawing manually, so it can
implement transparency as needed.
TCheckBox and TRadioButton, on the
other hand, are TWinControl
descendants that wrap standard Win32
API controls, and thus are subject to
whatever capabilities the OS supports
for them (transparency is not one of
them).
https://forums.codegear.com/thread.jspa?threadID=24027&tstart=375
You either need to do some heavy overriding, or else you will need to use a third party component...
A simple trick: make the button color white, shrink it to the minimum size, only the button; and put a transparent label behind it.
Otherwise, to make a button really transparent you need to owner draw it. You may find some examples in the web.
I found some information on responding to the WM_CTLCOLOR message. But I gave a quick try but couldn't quite get it to work.
I experimented with the standard VCL TRadioButton control in Delphi 2009 (I suppose Delphi 2010 is the same).
If you compile the project with runtime themes enabled (Project->Options->Application->Enable Runtime Themes), the TRadioButton control is transparent and its 'Color' property ignored. If the runtime themes disabled, the TRadioButton control is not transparent and its background is defined by its 'Color' property.
So I assume that the standard VCL TRadioButton (and the underlying windows control) is made transparent by the Windows theme, not by the control itself. You can switch off the theme support on application level, and in that case you get a non-transparent radio button. If you need a transparent radiobutton with runtime themes disabled, use 3rd party custom radiobutton (TCustomControl descendant, not a standard Windows radiobutton wrapper)
The easiest way is to buy a component set like Raize Components which will do this for you and lots more besides. Raize in particular allows you to customize lots of aspects of the UI.
http://www.torry.net/quicksearchd.php?String=transparent+radiobutton&Title=No might help. None of those are D2010 or D2009, but I believe porting would be possible.
In Delphi (2009 Pro) - I have a main form that can create non-modal child windows. I want whichever form has the focus to draw on top - even if it is the main window that has the focus.
With Delphi 2007/2009 the VCL changed its behavior regarding the parent of a form. In Delphi 1-2006 the parent of a form was the hidden application window (Application.Handle). In Delphi 2007/2009 the parent of a form is the main form and the main form's parent is the desktop.
If you want to change this you can either change the *.dpr line Application.MainFormOnTaskbar to False what gives you the the old behavior back but also makes your application look strange in Vista and Windows 7. Or you can override the virtual CreateParams method in all your non-modal child forms and set the Params.WndParent field to the desktop (HWND_DESKTOP) or the still existing Application.Handle.
type
TMyChildForm = class(TForm)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent := Application.Handle;
end;
Multiple windows cannot have the focus at the same time. I assume you meant to say that you want your non-modal child Form to be on top when only the MainForm has focus. Have you tried setting the child Form's FormStyle property to fsStayOnTop yet?