Author: Jonas Bilinkevicius
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.
1 unit MenuApp;
2
3 interface
4
5 uses
6 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
7 Menus, ExtCtrls, ComCtrls, ToolWin;
8
9 const
10 UM_CHILDREGISTER = WM_USER + 111;
11 UM_CHILDUNREGISTER = WM_USER + 112;
12
13 type
14 TUmChildRegister = packed record
15 msg: Cardinal;
16 childwnd: HWND;
17 unused: Integer;
18 result: Integer;
19 end;
20 TUmChildUnregister = TUmChildregister;
21
22 TForm1 = class(TForm)
23 MainMenu1: TMainMenu;
24 OpenMenu: TMenuItem;
25 StatusBar1: TStatusBar;
26 ToolBar1: TToolBar;
27 ToolButton1: TToolButton;
28 CloseMenu: TMenuItem;
29 ToolButton2: TToolButton;
30 Panel1: TPanel;
31 procedure OpenMenuClick(Sender: TObject);
32 procedure CloseMenuClick(Sender: TObject);
33 procedure Panel1Resize(Sender: TObject);
34 procedure FormClose(Sender: TObject; var Action: TCloseAction);
35 private
36 { Private declarations }
37 FChildAppHandle: HWND;
38 procedure UMChildRegister(var msg: TUmChildRegister);
39 message UM_CHILDREGISTER;
40 procedure UMChildUnRegister(var msg: TUmChildUnRegister);
41 message UM_CHILDUNREGISTER;
42 public
43 { Public declarations }
44 end;
45
46 var
47 Form1: TForm1;
48
49 implementation
50
51 uses
52 shellapi;
53
54 {$R *.DFM}
55
56 procedure TForm1.OpenMenuClick(Sender: TObject);
57 var
58 path, param: string;
59 begin
60 if FChildAppHandle = 0 then
61 begin
62 path := ExtractFilePath(Application.Exename) + 'childAppProj.exe';
63 param := '$' + IntTohex(panel1.handle, 8);
64 ShellExecute(handle, 'open', pchar(path), pchar(param), nil, SW_SHOWNORMAL);
65 end
66 else
67 ShowMessage('Child already loaded');
68 end;
69
70 procedure TForm1.CloseMenuClick(Sender: TObject);
71 begin
72 if FChildAppHandle <> 0 then
73 SendMessage(FchildApphandle, WM_CLOSE, 0, 0);
74 end;
75
76 procedure TForm1.Panel1Resize(Sender: TObject);
77 begin
78 if FChildAppHandle <> 0 then
79 MoveWindow(FchildAppHandle, 0, 0, Panel1.ClientWidth, Panel1.ClientHeight,
80 true);
81 end;
82
83 procedure TForm1.UMChildRegister(var msg: TUmChildRegister);
84 begin
85 FChildAppHandle := msg.childwnd;
86 end;
87
88 procedure TForm1.UMChildUnRegister(var msg: TUmChildUnRegister);
89 begin
90 if FChildAppHandle = msg.childwnd then
91 FChildAppHandle := 0;
92 end;
93
94 procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
95 begin
96 if FChildAppHandle <> 0 then
97 SendMessage(FchildApphandle, WM_CLOSE, 0, 0);
98 end;
99
100 end.
101
102 //Child app has a couple of edits, two buttons, a memo.
103
104 unit ChildApp;
105
106 interface
107
108 uses
109 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
110 StdCtrls, AppEvnts;
111
112 type
113 TForm2 = class(TForm)
114 Edit1: TEdit;
115 Edit2: TEdit;
116 Edit3: TEdit;
117 Button1: TButton;
118 Memo1: TMemo;
119 Button2: TButton;
120 ApplicationEvents1: TApplicationEvents;
121 procedure Button1Click(Sender: TObject);
122 procedure ApplicationEvents1Activate(Sender: TObject);
123 procedure FormClose(Sender: TObject; var Action: TCloseAction);
124 procedure FormResize(Sender: TObject);
125 private
126 { Private declarations }
127 FMenuAppWnd: HWND;
128 FParentPanelWnd: HWND;
129 public
130 { Public declarations }
131 constructor Create(aOwner: TComponent); override;
132 procedure CreateWnd; override;
133 procedure DestroyWnd; override;
134 end;
135
136 var
137 Form2: TForm2;
138
139 implementation
140
141 {$R *.DFM}
142
143 const
144 UM_CHILDREGISTER = WM_USER + 111;
145 UM_CHILDUNREGISTER = WM_USER + 112;
146
147 procedure TForm2.Button1Click(Sender: TObject);
148 begin
149 close;
150 end;
151
152 procedure TForm2.ApplicationEvents1Activate(Sender: TObject);
153 begin
154 if FMenuAppWnd <> 0 then
155 SendMessage(FMenuAppWnd, WM_NCACTIVATE, 1, 0);
156 memo1.lines.add('Activated');
157 end;
158
159 constructor TForm2.Create(aOwner: TComponent);
160 begin
161 if ParamCount > 0 then
162 begin
163 FParentPanelWnd := StrToInt(ParamStr(1));
164 FMenuAppWnd := Windows.GetParent(FParentPanelWnd);
165 end;
166 inherited;
167 if FParentPanelWnd <> 0 then
168 begin
169 Borderstyle := bsNone;
170 BorderIcons := [];
171 {remove taskbar button for the child app}
172 SetWindowLong(Application.Handle, GWL_EXSTYLE,
173 GetWindowLong(Application.Handle, GWL_EXSTYLE)
174 and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);
175 end;
176 end;
177
178 procedure TForm2.CreateWnd;
179 var
180 r: Trect;
181 begin
182 inherited;
183 if FMenuAppWnd <> 0 then
184 begin
185 SendMessage(FMenuAppWnd, UM_CHILDREGISTER, handle, 0);
186 Windows.SetPArent(handle, FParentPanelWnd);
187 Windows.GetClientRect(FParentPanelWnd, r);
188 SetBounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
189 end;
190 end;
191
192 procedure TForm2.DestroyWnd;
193 begin
194 if FMenuAppWnd <> 0 then
195 SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0);
196 inherited;
197 end;
198
199 procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
200 begin
201 {Closing the main form does not fire DestroyWnd for some reason}
202 if FMenuAppWnd <> 0 then
203 SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0);
204 end;
205
206 procedure TForm2.FormResize(Sender: TObject);
207 begin
208 memo1.width := clientwidth - memo1.Left - 10;
209 memo1.height := clientheight - memo1.Top - 10;
210 end;
211
212 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.
|