Form not closing as it should - forms

I am automating an open source program written in Delphi. From the main form, I am performing the following loop:
for i := 0 to analysisNames.Count - 1 do begin
currentAnalysisName := analysisNames[i];
analysisID := DatabaseModule.GetAnalysisIDForName(analysisNames[i]);
frmIIGraph.autoMode := true;
frmIIGraph.ShowModal();
end;
As you can see, it opens a form called frmIIGraph. Inside that form, I must open another form, which I do with the following code:
procedure TfrmIIGraph.FormActivate(Sender: TObject);
begin
if autoMode then begin
events := DatabaseModule.GetEvents(analysisID);
frmEventEdit.autoMode := true;
frmEventEdit.OpenDialog(events,0,analysisID);
frmEventEdit.ShowModal();
//frmEventEdit.Close;
SetFocus;
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The form opened from the above method is called frmEventEdit. Within that form I am running this code:
procedure TfrmEventEdit.FormActivate(Sender: TObject);
begin
if autoMode then begin
btnRTK_CalcClick(nil);
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The problem is that the PostMessage(Self.Handle,wm_close,0,0); in the latter code works fine and closes the form, resuming the code on the frmIIgraph at SetFocus;. However, the PostMessage(Self.Handle,wm_close,0,0); in the IIGraph form code, does not close the graph form, so that execution can resume on the main form, for the next iteration of the loop. You have to manually close the graph for it to proceed.
Any help is appreciated.

Your fundamental problem is that you have coded all your business logic in GUI code. So you are not able to execute the code that you want to execute without the convoluted code seen in the question.
If you want to solve your real problem you will deal with the root cause of your woes. You will separate the business logic and the GUI code. You will arrange for your business logic to be able to be executed in the absence of GUI.
If you don't want to solve your real problem, and wish to continue with this madness, you need to post a WM_CLOSE message to frmIIGraph.Handle in the OnDeactivate event handler for TfrmEventEdit. Presumably the one you post in TfrmIIGraph.FormActivate is getting consumed by the sub-form's message loop, or perhaps some call to ProcessMessages. But I cannot endorse this as a sane way to proceed.

Related

Delphi Change main form while application is running

I have this problem. When I hide my main form, then the taskbar icon of my application is also hidden. I saw a new question about this problem as well and the answers didn't really help. They suggested to minimize it, but I do not want to minimize the application.
Is it possible to change the main form while the application is already running?
for instance. I have two forms. when I want to hide the one form and show the other form, then the taskbar icon should stay at the taskbar and the main form should switch to the other form.
I am using Delphi XE6 and it is a VCL Forms application.
I have also seen a different old question about changing main form while runtime, but it is very old and still based on Delphi 6.
Is it possible to change the main form while the application is already running?
It is not possible to change the VCL main form whilst the program is running. This property is determined once and for all when the program starts.
One possible way to proceed for you is to arrange for the secondary form, the form that is not the main form, to have a button on the taskbar. Do that by making it unowned, or by using the WS_EX_APPWINDOW extended window style.
Update
Well, you can change Application.MainForm, but it requires you to destroy the current main form, and then create a new one.
It is not possible to change the Application.MainForm once it has been assigned. However, you do not need to, either. The simpliest solution to this issue would be to create a blank hidden TForm to act as the real Application.MainForm and let it manage the taskbar normally, and then you can show/hide any secondary TForm objects when needed, where your desired "MainForm" is a secondary form not the real MainForm.
As David Heffernan already said it is not possible to change the Main Form of an already running application. This is the limitation of the windows itself.
What you can do is cheat and never actually change the Main Form but only make it look like you did.
How do you achieve that?
Step 1: Add code to the second Form to make its own Taskbar button
procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
Step 2: Dynamically create the second Form just before switching to it. Upon its creation previously added code will create a new Taskbar button for your second form.
Step 3: Now hide you actual Main Form. Hiding it will also hide the Taskbar button belonging to it. So you end up still having one Taskbar button shown and it is the one belonging to your second Form.
Step 4: In order to allow your second Form to terminate your application at its closure call Close method of your true Main Form from your second Forms OnClose or OnFormCloseQuery event.
If you wanna be able to switch back to your true Main Form call Show method of your Main Form instead of Close method.
This approach allows us of swapping forms pretty quickly so only most keen users will notice short animation of Taskbar button.
NOTE: If your second for is a complex one and because of that takes some time to create you might wanna create it hidden and then once its creation process is finished show it and do the swap. Otherwise you might end up with two Taskbar buttons being shown at same time which I believe you wanna avoid.
Here is a short example:
- LoginForm is a true Main Form that is created at the application startup
- WorkForm is the Form on which user will spend most of time after logging in and this one is created in login process
Login Form code:
unit ULoginForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TLoginForm = class(TForm)
BLogIn: TButton;
procedure BLogInClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
LoginForm: TLoginForm;
//Global variable to tell us if we are only logging out or closing our program
LoggingOut: Boolean;
implementation
uses Unit2;
{$R *.dfm}
procedure TLoginForm.BLogInClick(Sender: TObject);
begin
//Create second Form
Application.CreateForm(TWorkForm, WorkForm);
//Hide Main Form
Self.Hide;
//Don't forget to clear login fields
end;
end.
Work form code:
unit UWorkForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TWorkForm = class(TForm)
BLogOut: TButton;
//Used in overriding forms creating parameters so we can add its own Taskbar button
procedure CreateParams(var Params: TCreateParams); override;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure BLogOutClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
WorkForm: TWorkForm;
implementation
uses Unit1;
{$R *.dfm}
procedure TWorkForm.BLogOutClick(Sender: TObject);
begin
//Set to true so we know we are in the process of simply logging out
LoggingOut := True;
//Call close method to begin closing the current Form
Close;
end;
procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
procedure TWorkForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
//Check to see if we are in the process of simply logging out
if not LoggingOut then
begin
//If we are not in the process of logging out close the Main Form
LoginForm.Close;
//and then allow closing of current form
CanClose := True;
end
else
begin
//But if we are in the process of simply logging out show the Main Form
LoginForm.Show;
//Reset the LoggingOut to false
LoggingOut := False;
//and then alow closing of current form
CanClose := True;
end;
end;
end.
I implemented this in the same way #DavidHeffernan had already suggested and as yet have not come across any issues, it might not be the best way but it worked for me and what i was trying to achieve, which was have "normal" feeling behaviour when the user minimised their MainWork form.
Code looked similar to this:
procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
frmMainWork := TfrmMain.Create(Application);
try
Pointer((#Application.MainForm)^) := frmMainWork;
frmLogin.Hide;
frmMainWork.ShowModal;
finally
Pointer((#Application.MainForm)^) := frmLogin;
frmLogin.Show;
FreeAndNil(frmMainWork);
end;
end;
hope this helps :-)
You can change main form.
Do a variable F: ^TForm, then set it to #Application.MainForm. After that you can set main form as via F^ := YourAnotherForm.
If you set Application.MainFormOnTaskbar to false in your startup routine (in the .dpr file), then the VCL will create a hidden form whose sole purpose is to provide the taskbar icon. This is an older approach and is generally not recommended, but as long as other windows are visible, it would let you hide your main form without the application disappearing from the taskbar.
You can also provide an Application.OnGetMainFormHandle event handler to change your Application.MainFormHandle at runtime (but not Application.MainForm). MainFormHandle affects stuff like the owner of modal popup dialogs.
More information on Application.MainFormOnTaskbar and the disadvantages of disabling it: This gets complicated in a hurry. The short version is in the VCL docs, which explain that several of Windows' newer features (like live taskbar thumbnails) that were introduced in Vista require MainFormOnTaskbar := True. There's a lot more background reading in this SO discussion.
I've had an additional issue working with Delphi XE2 MDI apps that are also COM servers. My main form Main calls a secondary form MenuForm which contains shared icons and popup menus for the whole app.
In my example, the app works perfectly when run standalone.
Main gets created, and then creates a MenuForm at the end of FormCreate.
All good.
But when called from a COM server, Main.FormCreate is called first, but somehow MenuForm gets assigned to Application.MainForm. There is a race condition in the underlying RTL. This causes havoc when trying to create the first SDI child, as Application.MainForm is not MDI.
I tried working around this by Main.FormCreate posting a message back to itself, to delay creation of MenuForm until after Main.FormCreate has returned.
But there is still a race condition here - MenuForm was still assigned to Application.MainForm.
I was eventually able to work around this using code to poll Application.MainForm every 10ms, with a maximum of 10 seconds. I also had to remove any reference in Main to MenuForm's icon list (in the .dfm) until after MenuForm was created explicitly - otherwise MenuForm would get created implicitly at the end of MainForm.create.
Hope this helps someone!
const
CM_INITIAL_EVENT = WM_APP + 400;
TmainForm = class(TForm)
...
procedure afterCreate(var Message: TMessage); message CM_INITIAL_EVENT;
...
end;
procedure TmainForm.FormCreate(Sender : TObject);
begin
...
...standard init code
...
postmessage( handle, CM_INITIAL_EVENT, 0, 0 );
End;
procedure TmainForm.AfterCreate(var Message: TMessage);
var
i: Integer;
begin
//must assign these AFTER menuform has been created
if menuForm = nil then
begin
//wait for mainform to get assigned
//wait up to 10*1000ms = 10 seconds
for i := 0 to 1000 do
begin
if Application.Mainform = self then break;
sleep(10);
end;
Application.CreateForm(TmenuForm, menuForm);
menuForm.parent := self;
end;
//NOW we can assign the icons
Linktothisfilterfile1.SubMenuImages := menuForm.treeIconList;
ActionManager.Images := menuForm.treeIconList;
allFilters.Images := menuForm.treeIconList;
MainMenu.Images := menuForm.treeIconList;
...
end;
In the current Delphi implementation, I am sure that there are no consequences using a pointer to change Application.MainForm.
If you look into TApplication class, you can see that FMainForm is used only to check if the application has at least one form, and iterates the main loop inside the TApplication.Run method while the FMainForm exists. If you do not want to use a pointer to hack this property, you can implement your own TApplication class, like TMyApplication, copy all the routines inside it and define MainForm property to read and write
Delete the *.dproj file. Delete the following line in the *.dpr file. You get rid of all your troubles :)
Application.MainFormOnTaskbar := True;
Sorry for my bad English.

Delphi2006 - How to create in DLL a message pump for a new window in a thread?

I have multithreaded application that loads my custom dll.
In this dll I need to create a window.
I'm doing it by creating new thread and inside it I'm trying to create this window, but I have got error that tells me: EInvalidOperation - Canvas does not allow drawing.
By searching in the net, I have discovered that I need custom message pump for that thread.
So, my question is, how properly do this?
What I do now is:
- external app is loading dll
- than this app in separte thread is calling Init function from dll
- Init function creates thread
- TMyThread is declared as:
type
TMyThread = class(TThread)
private
Form: TMyForm;
FParentHWnd: HWND;
FRunning: Boolean;
protected
procedure Execute; override;
public
constructor Create(parent_hwnd: HWND); reintroduce;
end;
constructor TMyThread.Create(parent_hwnd: HWND);
begin
inherited Create(False); // run after create
FreeOnTerminate:=True;
FParentHWnd:=parent_hwnd;
FRunning:=False;
end;
procedure TMyThread.Execute;
var
parent_hwnd: HWND;
Msg: TMsg;
XRunning: LongInt;
begin
if not Terminated then begin
try
try
parent_hwnd:=FParentHWnd;
Form:=TMyForm.Create(nil); // <-- here is error
Form.Show;
FRunning:=True;
while FRunning do begin
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then begin
if Msg.Message <> WM_QUIT then
Application.ProcessMessages
else
break;
end;
Sleep(1);
XRunning:=GetProp(parent_hwnd, 'XFormRunning');
if XRunning = 0 then
FRunning:=False;
end;
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
The exception EInvalidOperation - Canvas does not allow drawing is fired before thread reaches my existing message pump code.
What do I do wrong or what is the right way to make it work?
Thanks for any help.
To create second GUI thread in a DLL, I must do things exactly as in standard application.
Can anyone confirm my thinking?
In the DLL begin...end. section I do:
begin
Application.CreateForm(THiddenForm, HiddenForm);
Application.Run;
end.
In the TMyThread.Execute I must do:
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
try
Application.CreateForm(TMyForm, Form);
???? how to make a thread that has remained in this place until you close this window ???
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
Is this the right way? Could it be that simple?
The simplest way to run a message queue in a thread is as follows:
procedure PerformThreadLoop;
var
Msg: TMsg;
begin
while GetMessage(Msg, 0, 0, 0) and not Terminated do begin
Try
TranslateMessage(Msg);
DispatchMessage(Msg);
Except
Application.HandleException(Self);
End;
end;
end;
And in your thread procedure would look like this:
procedure TMyThread.Execute
begin
InitialiseWindows;
PerformThreadLoop;
end;
All that said, what you are attempting is not going to work. You appear to be trying to use VCL components away from the main thread. That is specifically not allowed. The VCL's threading model dictates that all VCL code is run on the main thread. Your attempts to create a VCL form away from the main thread are doomed to failure.
I would question your desire to create a new thread. A Delphi DLL can show VCL forms provided that it runs those forms out of the thread that loaded and called the DLL. You can call Show from that thread and show a modeless form. This means that you are relying on the host application's message queue to deliver messages to your windows. By and large this can be made to work. If your form is modal then you can simply call ShowModal and the form will be serviced by the standard Delphi modal message loop.
So my advice to you is to keep all your GUI in the host app's GUI thread. If your DLL is expected to show GUI, and is also expected to do that away from the host app's GUI thread then you are in trouble. But I think that's highly unlikely to be the case.
Earlier (a year ago) I stated this: "To create second GUI thread in a DLL, I must do things exactly as in standard application".
This is exactly what everybody who is searching for this solution should do.
Let me explain, step by step:
we must add our application object to our thread:
type
TMyThread = class(TThread)
private
ThreadApplication: TApplication;
now some modification to definition of procedure TMyThread.Execute;
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
ThreadApplication:=TApplication.Create(nil);
try
ThreadApplication.Initialize;
ThreadApplication.CreateForm(TMyForm, Form);
ThreadApplication.Run;
finally
ThreadApplication.Free;
end;
finally
Terminate;
end;
end;
end;
so, this is it, now we have message pump in a second GUI thread in a DLL.
Recently I found confirmation to this solution in a Delphi-Jedi blog, wrote by Christian Wimmer:
http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/
Thank You very much.

Correct way to destroy a form and show another in Delphi

Currently in my program I have a Startup form, and a Main form. The startup form shows for a second or two.
Right now, I have the following code within a timer:
frmStartup.Destroy;
frmMain := TfrmMain.Create(Self);
frmMain.Show;
Right now, I'm not sure if this is the correct way to do it.. It works, but when calling application.Terminate();
I receive an access violation message, leading my to believe that I have done something wrong in the destruction of the startup form..
If anyone could show the the correct procedure for doing what I want (non-modally) It would be greatly appreciated.
Thanks in advance,
EDIT:
Thanks for all the feedback, I fixed my access violations by simply adding the code:
Action := caFree;
Into the frmStartup.formClose method.
Don't create the frmStartup using Application.CreateForm. The first form created there becomes your application's main form, and if that's frmStartup you're destroying it outside Application knowledge.
Instead, use a normal Form.Create in your project source (.dpr) file:
var
frmStartup: TfrmStartup;
begin
Application.Initialize;
Application.MainFormOnTaskBar := True;
frmStartup := TfrmStartup.Create(nil); // No owner assigned here!
frmStartup.Show;
frmStartup.Update;
Application.CreateForm(TfrmMain, frmMain); // Let Application have this for main form
// Delay here if needed
frmfrmStartup.Free;
Application.Run;
end.
You may want to have your Splash Screen showing up as early as possible, so ideally it should be done during the initialization phase, then it should disappear only when the MainForm is ready to take over.
That's exactly what we do in our application where we reuse the About dialog as a splash screen then release it when the MainForm steals the focus.
In the dpr, as high as possible in the uses clause after the needed VCL/RTL units:
f_adtDlgAbout in 'f_adtDlgAbout.pas' {frmDlgAbout}, // ASAP to be used as a Splash screen
The About unit (FYI, FormStyle is fsStayOnTop and Position is poScreenCenter):
unit f_adtDlgAbout;
[...]
type
TfrmDlgAbout = class(TForm)
[...]
procedure TfrmDlgAbout.SplashFormDeactivate(Sender: TObject);
begin
Release;
end;
initialization
// Use it as a Splash screen
with TfrmDlgAbout.Create(nil) do begin
AlphaBlend := True;
AlphaBlendValue := 208;
BorderStyle := bsNone;
btnOK.Visible := False;
OnDeactivate := SplashFormDeactivate;
Show;
Update;
end;
end.
TFrmMain.Create(Self)??? What is "Self"? Are you doing that from inside frmStartup? If so, don't. Use TFrmMain.Create(NIL).
Use frmStartup.Release to release frmStartup, if all of the code you sent is inside a method of frmStartup, you need to put that line at the bottom of the method.
Set frmMain as Main Form
On frmMain.FormCreate
frmStartup := TfrmStartup.Create(nil);
try
frmStartup.ShowModal;
finally
frmStartup.Free;
end;

delphi 7 closing available modal form Onactivate under certain condition

im working on a project using delphi 7, The project is a maintenance project and im not the original coder of the project, i have a situation where i need to close a available form after it had been created through code under certain situations,The form is model
here is sample code of that
var
frmStratum : TfrmStratum;
begin
if not assigned(frmStratum) then myMainForm.OnExecute(PropAction);
end;
inside myMainForm.OnExecute(PropAction); i have
frmStratum := TfrmStratum.Create(Self, Self as IStratum,inttostr(m_surveyno),Module,m_stations,false);
now the procedure TfrmStratum.FormActivate of TfrmStratum i do lots of calucaltion and write to database
var
if (bMassStratumExport) AND (bDoneOne) then
begin
//write to database..
end;
now i have to do this atleast 20 times
that is
1. Create the form
2. onactivate do database writing
3. close TfrmStratum
since it is a modal form i cannot close if below from where i create it,so i wanted to close it onactivate as soon as the step 2 is done
now i have tried this
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then frmStratum.close;
end
EDIT :(edited to make the question more clear)
Onactivate of the form(frmStratum) , i want to close the modal form (frmStratum),so i do this
procedure TfrmStratum.FormActivate(Sender: TObject);
begin
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then self.close;// i need to close the form after after doing database write
end
end;
but the control while bugging goes to self.close but it doesnt close the form.
how to tackle this ?
In the past when I needed to close a form during activation I posted a message to myself instead of calling self.close.
PostMessage(Self.Handle, WM_CLOSE, 0, 0);
I tried to find my original source that pointed me in this direction but I could not find it.
PostMessage will return immediately and not wait for the message to be processed. Once the OnActivate function is finished and the message Delphi processing loop processes the message close will be called on your form.
Assuming its frmStatum, being invalid that's giving you the A/V exception
if bNowClo then self.close;

Make sure Form does not get focus when routine is run

I have 2 forms - The first form has a bunch of edits, comboboxes, etc, and the 2nd form has a Webbrowser.
I have a routine in the 2nd form, that loads HTML into the Webbrowser, and that routine is fired in the OnChange event of all my controls on the first form.
The problem is, that my focused control on my first form looses focus, when the 2nd form is loading the HTML into the webbrowser.
How can I make sure that the 2nd form does not get focus when the routine is fired? Or, more importantly - make sure that the focused control on my first form, does not loose focus?
What you're trying to do goes against the VCL, and probably goes against usual user expectations: When a new window is shown, unless it's a tool window, the usual (and expected) behavior is to move focus to it. The Win32 Api to show a window is ShowWindow and it'll activate the window, unless the SW_SHOWNOACTIVATE flag (or one of it's variants) is specified.
When you make a VCL form visible, a call to that function is also made. The call to ShowWindow is buried in procedure TCustomForm.CMShowingChanged(var Message: TMessage), a 135 lines procedure that hard-codes the SW_SHOWNORMAL flag (ie: an Activating flag) for the ShowWindow call that the VCL makes. Unfortunately that's a big piece of code and overriding it is not going to be easy. If this was my program I'd probably attempt changing the code in place: I'd add a DoNotActivate:Boolean flag to TCustomForm and change the single line of code in CMShowingChanged that calls ShowWindow for non-MDI forms to take that flag into account and simply call ShowWindow(Handle, SW_SHOWNOACTIVATE). If changing the VCL is not something you'd do light-hearted, you can use the following hacky solution:
The trick I'm suggesting is to create the new form (the one holding TWebBrowser) but do NOT set it's Visible property to True. Instead, make manual calls to ShowWindow(Handle, SW_SHOWNOACTIVATE) to show the form without activating it. Because this code would no longer code through the usual Delphi VCL, owned controls would not automatically be created and shown, so the ShowWindow(...) call needs to be made recursively, for all TWinControl descendants of the form:
procedure TForm15.Button1Click(Sender: TObject);
var F: TForm16;
procedure RecursiveShowNoActivate(W: TWinControl);
var i:Integer;
begin
ShowWindow(W.Handle, SW_SHOWNOACTIVATE);
for i:=0 to W.ControlCount-1 do
if W.Controls[i] is TWinControl then
RecursiveShowNoActivate(TWinControl(W.Controls[i]));
end;
begin
F := TForm16.Create(Application);
F.Top := Top + height; // So the new form doesn't overlap mine
RecursiveShowNoActivate(F);
F.WebBrowser1.Navigate('http://msdn.microsoft.com/en-us/library/ms123401');
end;
There's an other catch with this code: Make sure you navigate to a web page that doesn't have a form that's automatically focused. Navigating to microsoft's MSDN library might be unusual for a code sample, but that canonical example (www.google.com) sets focus to the search form.
One simple solution can be to disable the form containing the web browser control. A disabled window will not gain the focus.
When the OnDocumentComplete event of TWebControl is fired, the browser control is ready to gain focus. Disable the form here, and post yourself a message so that you can enable the form shortly:
const
UM_POSTENABLE = WM_USER + 12;
type
TForm2 = class(TForm)
WebBrowser1: TWebBrowser;
procedure WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
private
procedure UMPostEnable(var Msg: TMessage); message UM_POSTENABLE;
end;
var
Form2: TForm2;
implementation
uses Unit1;
{$R *.dfm}
procedure TForm2.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
begin
if Screen.ActiveForm = Form1 then begin
Enabled := False;
PostMessage(Handle, UM_POSTENABLE, 0, 0);
end;
end;
procedure TForm2.UMPostEnable(var Msg: TMessage);
begin
Enabled := True;
end;
Note that, according to the documentation, OnDocumentComplete can be fired multiple times. But since each call will receive a matching user message, this wouldn't be a problem.
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.OnActiveFormChange := ActiveFormChanged;
end;
procedure TForm1.ActiveFormChanged(Sender: TObject);
begin
if not (csDestroying in ComponentState) then
if ActiveControl <> nil then
ActiveControl.SetFocus
end;
procedure TForm1.EditOrComboChange(Sender: TObject);
begin
Form2.WebBrowser.SetFocus;
end;
Edit
Maybe this is not the most elegant way. As an alternative I tried
Form2.WebBrowser.Enabled := False;
to prevent the focus exchange at all. This keeps the focus on the edit control and strangely enough does the disabled WebBrowser update to the new page, but more mysteriously, this hides the caret in the edit control on Form1.