2012. november 9., péntek
Use a TPanel as a host for child windows (MDI simulation)
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.
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése