Title: How to mimic MS Word or MS IE free-floating windows using VCL
Question: In this article I will show how to relatively simply mimic the multiple
free-floating windows concept of MS applications. The advantage of this concept
is that there is only one application running but the individual windows have
an taskbar icon so that they can be minimized, maximized, organized into tiles, etc.
In each window a separate instance is running, so it is possible to work independently
in each window as for example in MS Word. The core idea is to create a hidden
main form which contains a list of child windows (i.e., the actual application). The
whole application is terminated after all child windows are closed.
Answer:
I will describe how to make a simple multiple free-floating windows application.
Using this example it is possible to rewrite an existing application to use the
multiple free-floating windows concept. The sample application is able to
create a new child window, hide or close a child window. Two a bit "dirty" tricks are
involved: hiding of the main application form and forcing the child windows to
appear on the task bar.
First we will create a new VCL project. The main application form TMainForm will
contain a FWndList which will contain all the child windows. The FWndList is a
classical TObjectList. Next, we make two public methods - AddForm and RemoveForm -
which will be used to add and remove some a child window from the FWndList. These
methods are called when a new child window is created or when a child window is closed.
We also make a method which will signal to the child windows that the window list
was changed, so that the child windows can update their menus, list boxes, etc.
After the application is executed it is convenient to immediately open one child
window. It is done by the method Init in the listing which is called from the
project file (.dpr).
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FWndList: TObjectList;
protected
procedure UpdateChildWindows;
public
procedure AddForm(const F: TForm);
procedure RemoveForm(const F: TForm);
property WndList: TObjectList read FWndList;
procedure Init;
end;
implementation
{$R *.dfm}
uses Child;
{ TMainForm }
procedure TMainForm.AddForm(const F: TForm);
// add a new child window to the window list
begin
// and the form
FWndList.Add(F);
// signal to other child windows to update their listboxes
UpdateChildWindows;
end;
procedure TMainForm.RemoveForm(const F: TForm);
// remove the child window F from the window list
var
idx: Integer;
begin
// find the child window F in the FWndList
idx:=FWndList.IndexOf(F);
if (idx = -1) then
raise Exception.Create('ERROR: form not found');
// remove the element
FWndList.Delete(idx);
// signal to other child windows to update their listboxes
UpdateChildWindows;
end;
procedure TMainForm.Init;
// creates the first form
var
F: TChildWindow;
begin
// creates the form
F:=TChildWindow.Create(Application);
// show the form
F.Show;
// add the form to the windows list
AddForm(F);
// a "dirty" trick to hide the main application window from the task bar
ShowWindow(Application.Handle, SW_HIDE);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
// create the object list so that the elements of the list are not *owned* by
// the list
FWndList:=TObjectList.Create(false);
// hides the main application window
Application.ShowMainForm:=false;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// dispose the list, but not the elements of the list
FWndList.Free;
end;
procedure TMainForm.UpdateChildWindows;
// signal to the child windows to update their menus, listboxes, etc.
var
i: Integer;
begin
for i:=0 to FWndList.Count-1 do
(FWndList[i] as TChildWindow).UpdateState;
end;
Next we will create a child window which will act as an independent application
window. We will create a child window which is able to open another child window,
close and hide itself. In a real application the child window will be replaced by
your application. Thus, the child window will have three methods - NewChildWindow,
HideChildWindow and CloseChildWindow.
Additionaly, we must add a method UpdateState which updates the window state after some
other window is closed. Usually, in this method we update the menu with open windows.
type
TChildWindow = class(TForm)
protected
procedure CreateParams(var Param: TCreateParams); override;
public
procedure UpdateState;
function NewChildWindow: TChildWindow;
procedure HideChildWindow;
procedure CloseChildWindow;
end;
implementation
{$R *.dfm}
uses Main;
{ TForm2 }
procedure TChildWindow.CreateParams(var Param: TCreateParams);
begin
inherited;
Param.ExStyle:=Param.ExStyle or WS_EX_APPWINDOW;
Param.WndParent:=GetDesktopWindow;
end;
procedure TChildWindow.CloseChildWindow;
// close the window
begin
Close;
end;
procedure TChildWindow.HideChildWindow;
// hide the window
begin
Hide;
end;
function TChildWindow.NewChildWindow: TChildWindow;
// open a new child window
begin
Result:=TChildWindow.Create(Application);
Result.Show;
MainForm.AddForm(Result);
end;
procedure TChildWindow.FormCreate(Sender: TObject);
begin
Caption:='Child Window ' + IntToStr(MainForm.WndList.Count);
end;
procedure TChildWindow.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// remove the form from the child window list
MainForm.RemoveForm(Self);
// free the form after closing
Action:=caFree;
end;
procedure TChildWindow.UpdateState;
var
i: Integer;
begin
for i:=0 to MainForm.WndList.Count-1 do
// e.g., update menu, listbox, etc.
end;
Finally, we must modify the project file, so that the application is terminated
after all child windows are closed.
program Project1;
uses
Forms,
Main in 'Main.pas' {MainForm},
Child in 'Child.pas' {ChildWindow};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
// show the first child window
MainForm.Init;
// repeat until no child window exists, this construction replaces usual
// Application.Run calling
repeat
Application.ProcessMessages;
until (MainForm.WndList.Count = 0);
end.