3
votes

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?

2
A non-windowed control, such as a TImage and TShape will work as you wish.Andreas Rejbrand
"The panel will contains link/buttons/shortcuts from where the user will call misc. functions" Sounds like a toolbar. Why would you want that to below the other forms? The use must minimize or close all forms to reach that functionality. Rather make this panel an MDI form itself, so it can be opened or put on top in a single click.GolezTrol
Why not align the panel to an edge of the main form? It will reduce the client space for the MDI childs, but it sure works well without VCL hacks. Or consider using a collapsable side bar.NGLN
@GolezTrol - I think that will work too if I get rid of the form's caption. Thanks.Server Overflow
@AndreasRejbrand That will work, but you cannot place TButtons on a TShape. It's not about the panel, but about the controls it should contain. Resorting to handle-less controls (TSpeedButton?) is an option, but you are very limited in your choice of controls and will lack keyboard input.GolezTrol

2 Answers

6
votes

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.

1
votes

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.