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.
Child app has a couple of edits, two buttons, a memo.
101 unit ChildApp;
102
103 interface
104
105 uses
106 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
107 StdCtrls, AppEvnts;
108
109 type
110 TForm2 = class(TForm)
111 Edit1: TEdit;
112 Edit2: TEdit;
113 Edit3: TEdit;
114 Button1: TButton;
115 Memo1: TMemo;
116 Button2: TButton;
117 ApplicationEvents1: TApplicationEvents;
118 procedure Button1Click(Sender: TObject);
119 procedure ApplicationEvents1Activate(Sender: TObject);
120 procedure FormClose(Sender: TObject; var Action: TCloseAction);
121 procedure FormResize(Sender: TObject);
122 private
123 { Private declarations }
124 FMenuAppWnd: HWND;
125 FParentPanelWnd: HWND;
126 public
127 { Public declarations }
128 constructor Create(aOwner: TComponent); override;
129 procedure CreateWnd; override;
130 procedure DestroyWnd; override;
131 end;
132
133 var
134 Form2: TForm2;
135
136 implementation
137
138 {$R *.DFM}
139
140 const
141 UM_CHILDREGISTER = WM_USER + 111;
142 UM_CHILDUNREGISTER = WM_USER + 112;
143
144 procedure TForm2.Button1Click(Sender: TObject);
145 begin
146 close;
147 end;
148
149 procedure TForm2.ApplicationEvents1Activate(Sender: TObject);
150 begin
151 if FMenuAppWnd <> 0 then
152 SendMessage(FMenuAppWnd, WM_NCACTIVATE, 1, 0);
153 memo1.lines.add('Activated');
154 end;
155
156 constructor TForm2.Create(aOwner: TComponent);
157 begin
158 if ParamCount > 0 then
159 begin
160 FParentPanelWnd := StrToInt(ParamStr(1));
161 FMenuAppWnd := Windows.GetParent(FParentPanelWnd);
162 end;
163 inherited;
164 if FParentPanelWnd <> 0 then
165 begin
166 Borderstyle := bsNone;
167 BorderIcons := [];
168 {remove taskbar button for the child app}
169 SetWindowLong(Application.Handle, GWL_EXSTYLE,
170 GetWindowLong(Application.Handle, GWL_EXSTYLE)
171 and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);
172 end;
173 end;
174
175 procedure TForm2.CreateWnd;
176 var
177 r: Trect;
178 begin
179 inherited;
180 if FMenuAppWnd <> 0 then
181 begin
182 SendMessage(FMenuAppWnd, UM_CHILDREGISTER, handle, 0);
183 Windows.SetPArent(handle, FParentPanelWnd);
184 Windows.GetClientRect(FParentPanelWnd, r);
185 SetBounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
186 end;
187 end;
188
189 procedure TForm2.DestroyWnd;
190 begin
191 if FMenuAppWnd <> 0 then
192 SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0);
193 inherited;
194 end;
195
196 procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
197 begin
198 {Closing the main form does not fire DestroyWnd for some reason}
199 if FMenuAppWnd <> 0 then
200 SendMessage(FMenuAppWnd, UM_CHILDUNREGISTER, handle, 0);
201 end;
202
203 procedure TForm2.FormResize(Sender: TObject);
204 begin
205 memo1.width := clientwidth - memo1.Left - 10;
206 memo1.height := clientheight - memo1.Top - 10;
207 end;
208
209 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.
|