MDI DEVELOPMENT IN DELPHI. Part I.
Constructing "multiple document interface" application with Delphi. Examining MDI parent/child relationship, menus and some most important MDI parent form properties.
What is MDI?
MDI stands for multiple document interface. In an MDI application, more than one document or child window can be opened within a single parent window. This is common in applications such as spreadsheets or word processors - one window, usually called the MDI parent or MDI container, contains many other windows, usually called child forms.
Every MDI application usually has three basic parts:
In a MDI application we can have only one MDI container form to a project, and that form will, mostly, be the start up form.
To create the main window for an MDI application, follow these steps:
- Start Delphi and select File | New Application... Delphi will create a new project with one form called form1, by default.
- Assign a Name property such as frMain to the form.
- Set the FormStyle property to fsMDIform.
- Save this project (name the project as you like, e.g. prMDIExample), along with uMain.pas in a newly created folder.
Every MDI "mother" needs at least one child. MDI child forms are just simple forms, but unlike windows as an AboutBox, MDI child windows are restricted to appearing inside the "mothers" window boundaries. Moreover, if the child form is minimized, its icon appears inside the MDI parent form, rather than in the Windows desktop ( only the parent window's icon appears in the task bar).
Now, we'll create some additional forms - MDI child forms, precisely. Simply choose File | New Form. This will create a new form object called form1, by default. With the Object Inspector change form1's Name property to frChild, and more important set FormStyle property to fsMDIChild. Save this form with it's associated unit as uchild.pas. Note: we can also turn an existing form into an MDI child form by adjusting this property.
Your application can include many MDI child forms of similar or different types.
Note: MDI application can also include standard, non-MDI forms that are not contained in the MDI form. A typical use of a standard form in an MDI application is to display a modal dialog box (such as about box, or custom file handling dialog).
As we can see, at design time both parent and child form look the same - it's hard to tell which one is "the boss".
A child widow in an MDI applications is like any other "normal" window in a non MDI (that is: SDI) application. Child forms can contain any components like Grids, Memos and Pictures. Traditionally, objects like status bars and toolbars usually appear in the MDI parent window.
Auto create -> Available
Next we'll make some adjustments to the project. Select Project | Options, this will open the Project Options dialog. Select the frChild from the left pane ("Auto-create forms"), and move it to the right one (Available forms). The right pane lists those forms that are used by your application but are not automatically created. In a MDI application, by default, all child forms are automatically created and are displayed inside their container. There will be nothing wrong if we don't perform this adjustment, however, standard Windows behavior is that MDI applications create their child window under program (user) control.
Create and show...
Once we have set that the Child form is not automatically created, we'll need some code that creates (one) instance of a frChild form object. The following CreateChildForm function needs to be placed inside the main (MDI parent) form's unit (along with the header in the interface's private part):
uses uchild; ... procedure TfrMain.CreateChildForm (const childName : string); var Child: TfrChild; begin Child := TfrChild.Create(Application); Child.Caption := childName; end;
Note 2: Don't forget that "uses uchild" part.
Close don't minimize!
Closing the child window in a MDI application only minimizes it in parent's client area. Therefore we have to provide an OnClose procedure, and set the Action parameter to caFree:
procedure TfrChild.FormClose (Sender: TObject; var Action: TCloseAction); begin Action := caFree; end;
Note: If a form is an MDI child form, and its BorderIcons property is biMinimize (default), then the default Action is caMinimize. If a MDI child form does not have these settings, the default Action is caNone, meaning that nothing happens when the user attempts to close the form.
MDI parent menu
Every MDI application should have a main menu with, if nothing more, window arrange/cascade.. options. As we have previously moved the child form from Auto-create forms to Available forms, we'll need some code that (menu item) will create child forms.
If you don't know how to construct menus in Delphi, go for "Adding menu support to MyNotepad" article. If you know how to work with TMainMenu component, than just drop one on the frMain (MDI parent) and let the menus look like:
We'll use "New child" menu item to construct new child forms to our application. The second menu (Window menu) will be used to arrange the child windows inside the MDI parent window-form.
...Create and show
Finally we can write some code to create a MDI child form. The application's File | New Child menu item, procedure NewChild1Click calls CreateChildForm procedure, to create (next) instance of the frChild form.
procedure TfrMain.NewChild1Click(Sender: TObject); begin CreateChildForm('Child '+IntToStr(MDIChildCount+1)); end;
Note: Newly created child forms will have "Child x" caption, where x represents the number of child forms inside a MDI form, as discussed below.
When working with multiple document interface applications, like this one, it's always preferable to have some code that will close all child forms.
procedure TfrMain.CloseAll1Click(Sender: TObject); var i: integer; begin for i:= 0 to MdiChildCount - 1 do MDIChildren[i].Close; end;
Note: You should check if there are some information inside each child form that needs to be saved before closing. The best is to use OnCloseQuery event handler for such purposes.
Since these are the first MDI-only-meaningful properties we are addressing for now, let's see what do they do. MdiChildCount read only property contains the number of created child forms. If no child windows are created this property is 0. We often use MdiChildCount along with MDIChildren array. MDIChildren is zero based array which contains created TForm object references to all child windows.
Note: the index of the most-recently-created MDI child is always 0, and the index of the first-created MDI child is always MDIChildCount - 1.
The Window menu
Delphi provides most of the commands that are likely to be placed inside a Window menu item. Commands like: tile, cascade and arrange all are used to rearrange, non minimized, child windows inside the parent. We simply call these methods like:
procedure TfrMain.Cascade1Click(Sender: TObject); begin Cascade; end; procedure TfrMain.Tile1Click(Sender: TObject); begin Tile; end; procedure TfrMain.ArrangeAll1Click(Sender: TObject); begin ArrangeIcons; end;
Note: here's how to display an MDI child stored inside a DLL
Next time we'll add some "real" code to this MDI application: we'll see how to create a simple graphic file viewer.