Use a TPanel as a host for child windows (MDI simulation) (Views: 711)
Problem/Question/Abstract: I was wondering if someone can offer assistance with this application. Basically the application is for configuring our system. At present it is a MDI where child windows are various functions (security, report options, etc.). The number of functions are growing, currently around 15, which means an increase in different child forms and, overall, a growing exe. I would like the child forms to be standalone programs or dlls which can appear in the control program as child windows and also execute by themselves. Only one child form is displayed at a time and always maximised within the parent window. I did see some code about that provided for a dll as a child form, but this would not help as a standalone execution. Answer: This is an interesting problem. As it happens it is possible in Win32 to make another processes window appear like a child window in ones own windows. It does not work quite as well as a true child in your own process but takes care about moving the pseudo-child with your menu app. The general design is this: the main/menu app has a form with menu, perhaps tool and status bars, and a client-aligned panel that will serve as the host for the child windows. It reads the available child apps from INI file or registry key and builds a menu or selection list from this info. On user request it launches the appropriate child app and passes the panels window handle on the commandline. The child app checks the command line, if there are no parameters it rans as designed, if there is a parameter it reads it, removes its border and bordericon, parents itself to the passed window handle and sizes itself to its client area. It also sends a message with *its* window handle to the panels parent (the main app form) to register itself. The main app can close the child with this handle and also resize it when the user resizes the main app. Main app: has a menu with two entries (OpenMenu, CloseMenu), a toolbar with two buttons attached to the same events as the two menus, a statusbar, a client-aliged panel. unit MenuApp; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, ExtCtrls, ComCtrls, ToolWin; const UM_CHILDREGISTER = WM_USER + 111; UM_CHILDUNREGISTER = WM_USER + 112; type TUmChildRegister = packed record msg: Cardinal; childwnd: HWND; unused: Integer; result: Integer; end; TUmChildUnregister = TUmChildregister; TForm1 = class(TForm) MainMenu1: TMainMenu; OpenMenu: TMenuItem; StatusBar1: TStatusBar; ToolBar1: TToolBar; ToolButton1: TToolButton; CloseMenu: TMenuItem; ToolButton2: TToolButton; Panel1: TPanel; procedure OpenMenuClick(Sender: TObject); procedure CloseMenuClick(Sender: TObject); procedure Panel1Resize(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } FChildAppHandle: HWND; procedure UMChildRegister(var msg: TUmChildRegister); message UM_CHILDREGISTER; procedure UMChildUnRegister(var msg: TUmChildUnRegister); message UM_CHILDUNREGISTER; public { Public declarations } end; var Form1: TForm1; implementation uses shellapi; {$R *.DFM} procedure TForm1.OpenMenuClick(Sender: TObject); var path, param: string; begin if FChildAppHandle = 0 then begin path := ExtractFilePath(Application.Exename) + 'childAppProj.exe'; param := '$' + IntTohex(panel1.handle, 8); ShellExecute(handle, 'open', pchar(path), pchar(param), nil, SW_SHOWNORMAL); end else ShowMessage('Child already loaded'); end; procedure TForm1.CloseMenuClick(Sender: TObject); begin if FChildAppHandle <> 0 then SendMessage(FchildApphandle, WM_CLOSE, 0, 0); end; procedure TForm1.Panel1Resize(Sender: TObject); begin if FChildAppHandle <> 0 then MoveWindow(FchildAppHandle, 0, 0, Panel1.ClientWidth, Panel1.ClientHeight, true); end; procedure TForm1.UMChildRegister(var msg: TUmChildRegister); begin FChildAppHandle := msg.childwnd; end; procedure TForm1.UMChildUnRegister(var msg: TUmChildUnRegister); begin if FChildAppHandle = msg.childwnd then FChildAppHandle := 0; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if FChildAppHandle <> 0 then SendMessage(FchildApphandle, WM_CLOSE, 0, 0); end; end. Child app has a couple of edits, two buttons, a memo. unit ChildApp; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, AppEvnts; type TForm2 = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Button1: TButton; Memo1: TMemo; Button2: TButton; ApplicationEvents1: TApplicationEvents; procedure Button1Click(Sender: TObject); procedure ApplicationEvents1Activate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormResize(Sender: TObject); private { Private declarations } FMenuAppWnd: HWND; FParentPanelWnd: HWND; public { Public declarations } constructor Create(aOwner: TComponent); override; procedure CreateWnd; override; procedure DestroyWnd; override; end; var Form2: TForm2; implementation {$R *.DFM} const UM_CHILDREGISTER = WM_USER + 111; UM_CHILDUNREGISTER = WM_USER + 112; procedure TForm2.Button1Click(Sender: TObject); begin close; end; procedure TForm2.ApplicationEvents1Activate(Sender: TObject); begin if FMenuAppWnd <> 0 then SendMessage(FMenuAppWnd, WM_NCACTIVATE, 1, 0); memo1.lines.add('Activated'); end; constructor TForm2.Create(aOwner: TComponent); begin if ParamCount > 0 then begin FParentPanelWnd := StrToInt(ParamStr(1)); FMenuAppWnd := Windows.GetParent(FParentPanelWnd); end; inherited; if FParentPanelWnd <> 0 then begin Borderstyle := bsNone; BorderIcons := []; {remove taskbar button for the child app} SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW); end; end; procedure TForm2.CreateWnd; var r: Trect; begin inherited; if FMenuAppWnd <> 0 then begin SendMessage(FMenuAppWnd, UM_CHILDREGISTER, handle, 0); Windows.SetPArent(handle, FParentPanelWnd); Windows.GetClientRect(FParentPanelWnd, r); SetBounds(r.left, r.top, r.right - r.left, r.bottom - r.top); end; end; procedure TForm2.DestroyWnd; begin if FMenuAppWnd <> 0 then SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0); inherited; end; procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin {Closing the main form does not fire DestroyWnd for some reason} if FMenuAppWnd <> 0 then SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0); end; procedure TForm2.FormResize(Sender: TObject); begin memo1.width := clientwidth - memo1.Left - 10; memo1.height := clientheight - memo1.Top - 10; end; end. One problem I noted is that sometimes the main applications caption will loose the active look when switching between main and child despite the action taken in the childs Application.OnActivate handler. |