OP Tech
MDI / ActiveX / Delphi 3 and up
MDI and ActiveX
Oil and Water Can Mix
Multiple document interface (MDI) applications have been a part of the Windows operating system for a long time. Unfortunately, ActiveX has no concept of MDI. This can be easily demonstrated by creating a simple project that has MDI forms. Figure 1 shows the result.

Figure 1: You receive this error message if you try to use MDI in an
ActiveX control.
This certainly makes it more difficult to convert your existing MDI applications to be ActiveX compatible through the use of Delphi’s ActiveForms. To successfully make the transition from an MDI application to an ActiveForm, you have two options:
1) Don’t use MDI.
2) Take control of the form’s creation process to circumvent MDI behavior.
The first option is reminiscent of the patient who goes to his doctor and says: “It hurts when I do this.” To which the doctor replies, “Then don’t do that.” It may be fine advice, but seldom is it that easy to avoid “doing that.” The second option provides these, and other, benefits:
· You can have an ActiveX and a stand-alone version of your program.
· You share the code base among an ActiveForm and an MDI application.
The rest of this article is for those of you who want these benefits.
Getting Started
When creating an MDI application that will be hosted in an ActiveForm, you cannot rely on, or use, anything related to MDI. This includes references to the Visual Component Library (VCL) properties ActiveMDIChild, MDIChildCount, MDIChildren, or any API-level MDI messages. None of these will work properly in an ActiveX control. If you absolutely must use MDI-processing, you’ll have to isolate these calls by checking if the application is being run in ActiveX mode or executable mode. The easiest way to accomplish this is to use a Boolean flag, which we’ll cover later.
There are two form styles in the VCL that deal with MDI forms: fsMDIForm and fsMDIChild. When you set the FormStyle property of a form to one of these styles, the VCL performs some magic under the hood to use the Windows API to create the MDI forms. Figure 2 shows the pertinent excerpt from TCustomForm.CreateWindowHandle.
procedure TCustomForm.CreateWindowHandle(
const Params: TCreateParams);
var
CreateStruct: TMDICreateStruct;
begin
if (FormStyle = fsMDIChild) and
not (csDesigning in ComponentState) then
begin
if (Application.MainForm = nil) or
(Application.MainForm.ClientHandle = 0) then
raise EInvalidOperation.Create(SNoMDIForm);
with CreateStruct do begin
szClass := Params.WinClassName;
szTitle := Params.Caption;
hOwner := HInstance;
X := Params.X;
Y := Params.Y;
cX := Params.Width;
cY := Params.Height;
style := Params.Style;
lParam := Longint(Params.Param);
end;
WindowHandle := SendMessage(
Application.MainForm.ClientHandle,
WM_MDICREATE, 0, Longint(@CreateStruct));
Include(FFormState, fsCreatedMDIChild);
end
else
begin
inherited CreateWindowHandle(Params);
Exclude(FFormState, fsCreatedMDIChild);
end;
end;
Figure 2: Relevant code from TCustomForm.CreateWindowHandle.
Therefore, to allow your MDI forms to work under ActiveX, we need to set the form to fsNormal. Because the CreateWindowHandle method creates the form based on the current value of FormStyle, we’ll override this method and modify FormStyle before calling the inherited method. We can control whether we force the style to fsNormal by using a flag variable. This variable is placed in the form’s interface declaration, and will be set by calling a new constructor instead of the standard Create constructor. You’ll need to add the code shown in Figure 3 to each child form that will be displayed in the ActiveForm.
constructor TfrmStep1.CreateNoMDI(AOwner: TComponent);
begin
FNoMDI := True;
inherited Create(AOwner);
end;
procedure TfrmStep1.CreateWindowHandle(
const Params: TCreateParams);
begin
if FNoMDI then begin
FormStyle := fsNormal;
Visible := False;
end;
inherited CreateWindowHandle(Params);
end;
Figure 3: Code used to remove MDI-specific attributes from a form.
When changing the form’s style to fsNormal, however, the child form will no longer respect the client space of the main form, but will instead use all of the main form’s space. This is troublesome if you have controls like toolbars and status bars on your parent form. To prevent this behavior, we’ll set the parentage of the child forms to a Panel component that will be placed on the parent form.
One last technique to help transition your MDI application to an ActiveForm is the use of a proxy form. In effect, this method allows you to create one Delphi form that is responsible for all the other Delphi forms. If you create a typical ActiveForm, you’re actually creating a Delphi form that serves as a placeholder for your visual interaction between the ActiveForm and Internet Explorer. Because this is a Delphi form, you can use it as a parent to other “child” forms you create. A perfect example would be the MDI-less forms we just created. The FormCreate method for the main ActiveForm is shown in Figure 4.
procedure frmMain.FormCreate(Sender: TObject);
begin
frmMain:=TfrmMain.Create(Self);
frmMain.Parent := Self;
frmMain.Align := alClient;
frmMain.BorderStyle := bsNone;
frmMain.Visible := True;
frmMain.Panel1.Visible := True;
frmContacts := TfrmContacts.CreateNoMDI(Self);
frmContacts.Parent := frmMain.Panel1;
frmContacts.Show;
end;
Figure 4: The FormCreate method for the main ActiveForm.
(Conrad Herrmann first made this proxy technique available on his Web site shortly after the release of Delphi 3. He also hosts other Frequently Asked Questions for ActiveX problems; for more information visit http://pw2.netcom.com/~cherrman/daxfaqs.htm.)
Not So Fast
Now that we have the basic functionality implemented, there are a couple of caveats to ActiveForm application development you should know about. First, the OnActivate and OnDeactivate events don’t get called when using Internet Explorer 4. You can either move the contents of these procedures to another method, such as OnCreate or OnShow, or you can manually call the events as needed.
Second, the ActiveForm doesn’t auto-create any forms. You must take control of this process by creating secondary forms manually. A good place to do this is in the ActiveForm’s OnCreate event. Also, items such as Data Modules don’t get created automatically, so they must be created manually. You should double-check the order that forms were auto-created in your stand-alone application; if you try to use a form that hasn’t been created from another form’s OnCreate event, an access violation will occur.
Conclusion
Using the techniques presented here, you can get an MDI application converted for use within an ActiveForm in record time, and with minimal problems (see Figure 5).

Figure 5: An MDI application in an ActiveX control.
By using an ActiveForm with the functionality of your existing application, you can capture a whole new segment of your market.
The projects referenced in this article are available for download.
Dan Miser is a long-time Delphi programmer and consultant, specializing in multi-tier application design using MIDAS. He is active in the Borland newsgroups, where he serves as a proud member of TeamB (http://www.teamb.com). Dan also finds time to write for Delphi Informant and speak at Borland conferences. You can visit his Web site at http://www.execpc.com/~dmiser, or contact him at mailto:dmiser@execpc.com.
10 Steps to ActiveForm Conversion
1) Create a new ActiveForm using File | New | ActiveX | ActiveForm.
2) Save this form in a sub-directory under your existing MDI application.
3) Add the existing MDI forms to the ActiveForm project using the Project Manager.
4) Add an invisible Panel component to your MDI parent form to act as parent to the MDI child forms.
5) Add the code in Figure 3 to all MDI children.
6) Add the code in Figure 4 to the ActiveForm.
7) Make sure you create all the Data Modules and forms.
8) Ensure you aren’t using MDI-related properties or messages.
9) Ensure you aren’t using OnActivate and OnDeactivate events for the ActiveForm.
10) Compile, deploy, and enjoy!
Like oil and water, MDI and ActiveX just don’t mix. Or do they? Using smoke, mirrors, and Delphi’s ActiveForm, Mr Miser demonstrates ten simple steps for getting them to cooperate.
Can MDI and ActiveX mix?