TPanel as a splash screen in a MDI app - delphi

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.

Related

delphi MDI form without Tmainmenu

I am trying to work with MDI forms without TMainMenu and call Form2 from speedbutton which on Ttoolbar. when Form2(child) maximized , form header and bordericons disappears.
I want Form2(child) will be maximized under Ttoolbar and header and border icons are visible
This is how MDI works. instead of using MDI you can use a TPanel and set it as Parent for child forms, something like this:
var
FChild: TfrmChild;
begin
FChild := TfrmChild.Create(Self);
FChild.Parent := pnlMain;
FChild.Show;
end;
...

Disabling the form still allow childs controls to receive input

I'm having a lot of headache in the last days with delphi, what im trying to do is a lot simple, block the interface at somepoint and enable after some other point.
But as simply as it sound i couldn't figure out why somethings are allowed by design, so to clarify:
1) create a project
2) in the form put a edit and a button, tab order of the edit must be first
3) configure the OnExit event of the edit and write:
Enabled := False;
4) configure the OnClick event of the button and write:
ShowMessage('this is right?');
basically this is it, now compile, the focus it will be at the edit, press tab and the form will be disabled as we demanded, so accordingly to the tab order the next control to gain focus is the button (but we disabled the form), now press space and the message should come up.
so the question is: is this right? whats the logical explanation to this behaviour?
thx in advance.
Both TButton and TEdit are TWinControl descendents - this means that they are windowed controls. When they are created they are allocated their own HWND and the operating system posts messages to them directly when they have focus. Disabling their containing form prevents the main form from receiving input messages or from receiving focus but it does not disable any other windowed control if it already has input focus.
If these controls do not have input focus, it is responsibility of the containing form to transfer input focus to them when user input (click, tab key, etc) dictates. If the form is disabled and these controls are not focused then the form will not receive the input messages that would allow it to transfer focus. If focus is transferred to a windowed control, however, then all user input goes directly to that control, even if their parent control's window is disabled - they are in fact their own separate windows.
I'm not sure the behaviour you have observed is a bug - it is perhaps not expected, but it is standard behaviour. There is generally no expectation that disabling one window will also disable others within the same application.
The problem is that there are two separate hierarchies in play. On the VCL level, the Button is a child control and has a parent (the form). On the OS level, however, both are separate windows and the (component level) parent/child relationship is not known to the OS. This would be a similar situation :
procedure TForm1.Button1Click(Sender: TObject);
var
form2 : TForm1;
begin
self.Enabled := false;
form2 := TForm1.Create(self);
try
form2.ShowModal;
finally
form2.Free;
end;
end;
Would you really expect form2 to be disabled when it was shown, simply because its TComponent owner is Form1? Surely not. Windowed controls are much the same.
Windows themselves can also have a parent/child relationship, but this is separate from component ownership (VCL parent/child) and does not necessarily behave in the same way. From MSDN:
The system passes a child window's input messages directly to the
child window; the messages are not passed through the parent window.
The only exception is if the child window has been disabled by the
EnableWindow function. In this case, the system passes any input
messages that would have gone to the child window to the parent window
instead. This permits the parent window to examine the input messages
and enable the child window, if necessary.
Emphasis mine - if you disable a child window then its messages will be routed to the parent for an opportunity to inspect and act upon them. The reverse is not true - a disabled parent will not prevent a child from receiving messages.
A rather tedious workaround could be to make your own set of TWinControls that behave like this :
TSafeButton = class(TButton)
protected
procedure WndProc(var Msg : TMessage); override;
end;
{...}
procedure TSafeButton.WndProc(var Msg : TMessage);
function ParentForm(AControl : TWinControl) : TWinControl;
begin
if Assigned(AControl) and (AControl is TForm) then
result := AControl
else
if Assigned(AControl.Parent) then
result := ParentForm(AControl.Parent)
else result := nil;
end;
begin
if Assigned(ParentForm(self)) and (not ParentForm(self).Enabled) then
Msg.Result := 0
else
inherited;
end;
This walks up the VCL parent tree until it finds a form - if it does and the form is disabled then it rejects input to the windowed control as well. Messy, and probably could be more selective (maybe some messages should not be ignored...) but it would be the start of something that could work.
Digging further, this does seem to be at odds with the documentation :
Only one window at a time can receive keyboard input; that window is
said to have the keyboard focus. If an application uses the
EnableWindow function to disable a keyboard-focus window, the window
loses the keyboard focus in addition to being disabled. EnableWindow
then sets the keyboard focus to NULL, meaning no window has the focus.
If a child window, or other descendant window, has the keyboard focus,
the descendant window loses the focus when the parent window is
disabled. For more information, see Keyboard Input.
This does not seem to happen, even explicitly setting the button's window to be a child with :
oldParent := WinAPI.Windows.SetParent(Button1.Handle, Form1.Handle);
// here, in fact, oldParent = Form1.Handle, so parent/child HWND
// relationship is correct by default.
A bit more (for repro) - same scenario Edit tabs focus to button, exit handler enables TTimer. Here the form is disabled, but the button retains focus even though this seems to confirm that Form1's HWND is indeed the parent window of the button and it should lose focus.
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Form1.Handle
self.Enabled := false;
h3 := GetFocus; // h3 = Button1.Handle
end;
In the case where we move the button into a panel, everything seems to work (mostly) as expected. The panel is disabled and the button loses focus, but focus then moves to the parent form (WinAPI suggests it should be NULL).
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Panel1.Handle
Panel1.Enabled := false;
h3 := GetFocus; // h3 = Form1.Handle
end;
Part of the problem seems to be here - it looks like the top form itself is taking responsibility for defocusing controls. This works except when the form itself is the one being disabled :
procedure TWinControl.CMEnabledChanged(var Message: TMessage);
begin
if not Enabled and (Parent <> nil) then RemoveFocus(False);
// ^^ False if form itself is being disabled!
if HandleAllocated and not (csDesigning in ComponentState) then
EnableWindow(WindowHandle, Enabled);
end;
procedure TWinControl.RemoveFocus(Removing: Boolean);
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.DefocusControl(Self, Removing);
end
Where
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
begin
if Removing and Control.ContainsControl(FFocusedControl) then
FFocusedControl := Control.Parent;
if Control.ContainsControl(FActiveControl) then SetActiveControl(nil);
end;
This partially explains the above observed behaviour - focus moves to the parent control and the active control loses focus. It still doesn't explain why the 'EnableWindow` fails to kill focus to the button's child window. This does start to seem like a WinAPI problem...

How can I create a Delphi child window that has a main menu?

I am working with a Delphi form that contains a TMainMenu. In a particular situation I want to show this form parented and client-aligned inside another form. This works fine but the main menu of the parented form does not appear. I see a comment in this SO question that states "A child window cannot have a menu". Is there anything that I can do to override this behaviour and make a TMainMenu appear?
An aside: I've only just noticed this because where I have used this principle before, I've been using the Developer Express menu component and this is quite happy to show in a child form.
Later edit:
Using the code from TLama below, this works (but the child menu is not themed, I,e very plain):
This works:
procedure TForm65.FormShow(Sender: TObject);
begin
Winapi.Windows.SetParent(ChildForm.Handle, Handle); // <<<<<<<<
ChildForm.BorderStyle := bsNone;
ChildForm.Align := alClient;
ChildForm.Show;
end;
This code DOES NOT work. Why?
procedure TForm65.FormShow(Sender: TObject);
begin
ChildForm.Parent := Self; // <<<<<<<<<
ChildForm.BorderStyle := bsNone;
ChildForm.Align := alClient;
ChildForm.Show;
end;
MSDN makes this perfectly clear:
A child window has a client area but no other features, unless they are explicitly requested. An application can request a title bar, a window menu, minimize and maximize buttons, a border, and scroll bars for a child window, but a child window cannot have a menu.
This refers to the menu as drawn by Windows itself. If your component custom draws a menu bar, then of course it can have a menu, even if it is a child window.
Your call to SetParent does not make your window into a child window. This is explained in the documentation:
For compatibility reasons, SetParent does not modify the WS_CHILD or WS_POPUP window styles of the window whose parent is being changed. Therefore, if hWndNewParent is NULL, you should also clear the WS_CHILD bit and set the WS_ POPUP style after calling SetParent. Conversely, if hWndNewParent is not NULL and the window was previously a child of the desktop, you should clear the WS_POPUP style and set the WS_CHILD style before calling SetParent.

detect if the scrollbars of a form are visible in an mdi child

I cannot detect if the scrollbars of a form are visible.
Googleing the Internet shows that the code below should work. Everybody uses it:
function VertScrollBarVisible(WindowHandle: THandle): Boolean;
begin
Result:= (GetWindowlong(WindowHandle, GWL_STYLE) AND WS_VSCROLL) <> 0
end;
I call it like this:
procedure TFrmBaser.Button1Click(Sender: TObject);
begin
if VertScrollBarVisible(MainForm.Handle)
then Caption:= 'visible';
end;
It returns False all the time, even is the scrollbars are visible. They are made visible by some MDI child forms that I drag a bit out of screen.
Delphi 7, Win XP SP3, Themes on
This
It returns False all the time, even is the scrollbars are visible. They are made visible by some MDI child forms that I drag a bit out of screen.
shows that the form you are having problems with is a MDI parent form (FormStyle is fsMDIForm).
MDI parent forms are different from normal forms in that they create a special client window that fills the whole client area of the form, and which manages the MDI child windows / forms. The MDI client window will never be larger than the client area of its parent, so the parent form will never show scrollbars. This explains that the code in your question always returns false.
The scrollbars you see are part of the MDI client window. Modify your code to check the window style of the client window, its handle can be accessed with the ClientHandle property of the MDI parent form:
procedure TFrmBaser.Button1Click(Sender: TObject);
begin
if VertScrollBarVisible(MainForm.ClientHandle) then
Caption := 'visible';
end;
For more information about MDI at the Windows API level see About the Multiple Document Interface on MSDN.
Try this:
function VertScrollBarVisible(Form : TForm) : Boolean;
begin
Result:=(Form.Width-Form.ClientWidth>10)
end;
I'm not sure if it works, but it compares the "available" width of the form against the "total" width of the form (normally they are within 2-3 pixels of each other, but with a scroll bar, the available width should be significantly lower).

Non-Modal Child Window That Allows Mainform To Be Drawn On Top - Delphi

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?

Resources