How to check if the Application.MainForm is valid?

613 Views Asked by At

How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it (from a MadExcept ExceptionHandler).

This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)

I was thinking:

if Assigned(Application)
    and (not Application.Terminated)
    and Assigned(Application.MainForm)
    and Application.MainForm.HandleAllocated then
  begin
    PostMessage(Application.MainForm.Handle, MyMessage, 0, 0);
  end;

Is this correct?

2

There are 2 best solutions below

2
On

Make some global variable flag = false at the beginning.

Make your mainform turn it to true.

Check that flag to see if main form was already initialised or not yet.

You can make it from such places as mainform's OnActivate event or overridden TMainForm.Loaded method

Similarly when your application would be terminating and the mainform would get hidden (and later even destroyed) - you would reset the flag back to false

6
On

How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it.

OK.

This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)

Uh oh.

....

Is this correct?

No it certainly is not. Your code can never be made threadsafe because it is not permitted to access VCL objects from outside the main thread.

In your particular case consider the following sequence of events:

  1. You perform your tests in the if culminating in your evaluating Application.MainForm.HandleAllocated as True. Ignore for a moment the fact that you are doing this outside the main thread.
  2. You then set about preparing the call to PostMessage. But at this very instance, the main form is destroyed.
  3. By the time your thread gets round to accessing Application.MainForm, it has gone.

You are going to need to work a little harder here. You'll need to do something like this:

// interface section of some unit

procedure RegisterMainFormHandle(Wnd: HWND);
procedure UnregisterMainFormHandle;
procedure PostMessageToMainForm(...);

// implementation section 

var
  MainFormHandleLock: TCriticalSection;
  MainFormHandle: HWND;

procedure RegisterMainFormHandle(Wnd: HWND);
begin
  MainFormHandleLock.Acquire;
  try
    MainFormHandle := Wnd;
  finally
    MainFormHandleLock.Release;
  end;
end;

procedure UnregisterMainFormHandle;
begin
  MainFormHandleLock.Acquire;
  try
    MainFormHandle := 0;
  finally
    MainFormHandleLock.Release;
  end;
end;

procedure PostMessageToMainForm(...);
begin
  MainFormHandleLock.Acquire;
  try
    if MainFormHandle <> 0 then
      PostMessage(MainFormHandle, ...)
  finally
    MainFormHandleLock.Release;
  end;
end;

You also need to create and destroy the critical section, but I assume that you know how to do that.

In your main form you override CreateWnd and DestroyWnd and arrange that they call RegisterMainFormHandle and UnregisterMainFormHandle.

Then you can call PostMessageToMainForm from any thread at any time.

Of course, if the main form's window is recreated then you'll lose some messages. Which sounds like it could be a problem. Using AllocateHwnd to have a window whose lifetime you control is usually a better option than using the main form's window like this.