Combining ActiveForms written with Delphi 3.02 and Internet Explorer 4.01
by Conrad Herrmann
Date 17 May 1998
ActiveForms written in Delphi are very useful, but there are some problems with Internet Explorer 4's implementation that makes Delphi controls not act properly. This document provides an overview of the things you'll need to do in order to get your Delphi ActiveForm to work under IE4.
Some of these are Delphi bugs, others are incompatibilities between IE4 and other (legacy) ActiveX containers and controls.
There are two sections to this document. The first, The Short Version just tells you what to download and add to your project to get going. The second, The Long Version describes each problem in detail and describes the solution implemented.Thanks to Colin Wilson, Steve Teixeira, and Joe Bentley for providing some of these details.
if AnsiPos(' ',Temp) <> 0 then The effect of this line of code is to cause the object server to be registered with double-quotes around the path name if the path contains spaces. These quotes are superfluous, and cause the Internet Explorer CodeDownload service to download your control every time it is created on a page, since it assumes the control doesn't exist on your machine.
Solution: If you don't use packages, you can copy ComObj into your project, delete the offending lines of code, rebuild your project and re-register the control.
If you use packages, you must instead use a factory class that undoes this bug's effect after the bad code has executed. The attached file, AXFixed.PAS (available in afrms302.zip), implements a factory subclassed from the standard factory, and which corrects the problem. To use this factory, simply change the name of the factory you use in your control unit from TActiveFormFactory to TFixedActiveFormFactory. (The factory creation code is in your ActiveForm's unit initialization section).
Problem 2. Apartment threading model.
Description: IE4 doesn't work with controls that don't support Apartment threading. Legacy controls are just out of luck running in the new platform.
Symptoms: These are all situations in which bugs in IE's non-threaded control support can cause strange things to happen.
Solution: Implement your control as an Apartment threaded server. There are two things to do here:
If you used the TFixedFormFactory above, you're set, because the factory also registers the object as Apartment-threaded. Otherwise, study the code in the AddThreadingKeys function in AXFixed.Pas to see how to implement the registry entry.
Incidentally, this trick of subclassing a factory to add registry entries is a standard trick when implementing ActiveX controls using DAX. To paraphrase Martha Stuart, it's a Good Thing to know.
AXCtrls calls DestroyWindow to destroy the ActiveX parking window, which is not a thread-safe operation. If you don't fix this, IE will GPF randomly, but not all the time. If you're not using packages, you can copy AXCtrls.PAS into your project and change the finalization section of AXCtrls.PAS to the following:
finalization
Problem 3. UIActivation on mouse selection.
Description: When the user clicks on the window or any of its children, the ActiveForm does not become "UI-active".
Symptom: When you click on a control that's a child of the ActiveForm, and then press the TAB key, focus does not shift from one child to the next.
Analysis: This is similar to the situation encountered when writing MFC controls using MFC 4.2 or before, and is described in a Microsoft PR number Q168777. The problem is that when a child control gains the focus, it needs to tell the ActiveForm that it has done so, so the ActiveForm does not become UI-active. Because it does not become UI-active, its container (IE) does not tell it about container-level keystrokes like TAB keys, and so the ActiveForm cannot process the TAB key.
Solution1: Implement a handler for the WM_MOUSEACTIVATE message, which UI-activates the form if the form's default handler indicates that the form should be activated. This seems to work fine for most child controls, but you should be aware that not all controls forward WM_MOUSEACTIVATE to their parents. For example, the RichEdit control does not.
Solution2: This is the solution advocated by MS in the PR: For every child control, bind its OnClick (or OnMouseUp) to a handler that UI-activates the form.
Problem 4. UIActivation by Tabbing from the Web page.
Description: Pressing the TAB key once does not set focus to the ActiveForm, even though the ActiveForm is the only control on the web page.
Explanation: This seems to be the way IE works. You need to press TAB several times to select the ActiveForm using the keyboard. The same thing happens with other controls. Nevertheless, this can cause confusion because if the focus is not set to a child of the form, which is not required, there is no visual means of knowing that the form has UI-activation. This leads into the next problem.
Problem 5. UI activating the form does not set focus to the first control on the form.
Description: If there is no current ActiveControl on the form, and the form becomes UI-activated, focus is set to the form itself, and not to the first control on the form.
Analysis: In some cases, this is reasonable behavior, since it is perfectly acceptable for the form itself to process user input. However, in most cases you will be designing a form that is more like a dialog box, where focus never resides on the form itself but instead focus should always be transferred to one of the form's child controls. If there is no currently active child control, focus should be set on the first one.
Solution1: Always specify the ActiveControl property on your form. In the Object Inspector, select the ActiveControl property and set it to point to one of your child controls.
Solution2: Change the implementation of TCustomForm.SetFocus so that will Activate the first child control, if the form does not care to receive focus itself. This is the solution I implemented in the TFixedActiveForm class, which handles the WM_SETFOCUS message and assigns focus to the appropriate child window.
Solution3: The best solution would be to allow a TForm to be marked as a control that always passes focus onto a child. TForm code would then choose a child to make the ActiveControl when the TForm received focus.
Problem 6. The ActiveControl does not regain focus when the frame loses activation and then regains it.
Description: A control on an ActiveForm does not regain the focus when you switch away and back again without explicitly setting focus to any other controls.
Symptom: You have an active form with multiple controls, and you've clicked on one of the controls so it has focus. You then select another application, sending IE and the ActiveForm to the background. Then, you reselect IE by clicking on its title bar, but *not* by clicking inside the web page area. This should reactivate IE, bring it to the front, and reset focus back on the control that's current in the form. The last step does not happen.
Analysis: This is happening because the ActiveForm is already the UI-active control, even though it does not have focus. When the frame becomes active again, the control is called via IInPlaceActiveObject.OnFrameWindowActivate, but this method just calls InPlaceActivate(True). Since the control is already UI-active, InPlaceActivate(True) effectively does nothing, but it should set focus to the active child of the Form.
Solution: Add code to the OnFrameWindowActivate method to set focus to the control, to TActiveXControl
function TActiveXControl.OnFrameWindowActivate(fActivate: BOOL): HResult;
begin
Result := InPlaceActivate(True);
SetFocus(FWinControl.Handle);
end;
In the case of an ActiveForm, setting focus to the form will transfer focus to the ActiveControl of the form, if any.
Problem 7. If no control is currently the ActiveControl in the ActiveForm, TAB key does not work in the ActiveForm.
Symptom: Bring up an ActiveForm, and click on the body of the form so it doesn't set focus to a child of the form. If you've implemented the code above, the form becomes UI active. However, pressing the TAB key does not navigate to the child controls.
Analysis: This seems to be a limitation of the TCustomForm class. The problem is that if there is no ActiveControl already selected on the form, pressing the TAB key does not set one of the form's children to be active. This problem is basically caused by the same situation as problem #5.
Solutions: The solutions to this problem are essentially the same as the solution to problem #5.
Problem 8. Tabbing past the last control on an ActiveForm does not send the focus away from the ActiveForm.
Description: If you've implemented the above fixes, you can tab around from control to control on the ActiveForm. However, tabbing past the last control on the form does not set focus back to the Web page. Instead, focus goes back to the first control on the form.
Analysis: This is part of the implementation of TCustomForm, which doesn’t provide support for forms as children of other containers.
Solution: I haven't fixed this one yet. It seems to require a change to TCustomForm to handle tabbing off the end of the form, and maybe a property or flag to tell the form whether to do this.
(C) Copyright 1998 Conrad Herrmann. See Legal Notices for your rights.