7

I'm starting to have issues with my main form disappearing behind other application windows on closing modal forms and I was hoping someone would have come across (and solved!) this issue previously or have suggestions on where to locate breakpoints to debug the problem.

My issues originally started with the classic 'shy dialog' problem with modal dialogs appearing under the main form which occurred intermittently. To try to sort this I changed all my modal forms' popupmode to pmAuto and also added Application.ModalPopupMode := pmAuto; and Application.MainFormOnTaskBar := true; to my application dpr.

Now I'm getting the main form disappearing behind other windows on closing the modal pop-ups. I have suspicion is that the behaviour is mainly caused when a modal form opens a second window (I've problems with both a MessageDlg and a straight Form.create(Application); Form.show;), though there's no obvious problems with the show/free code (ShowModal forms are created owner = nil, modeless with owner = application). In both cases the form disappears on closing the first original modal form, but manipulating the modal form without triggering a new form/dialog to appear seems to work as expected.

There are other nasties going on in the background on the main form with a refresh timer that activates a background thread, but usually this hasn't fired in the time it takes to see it not working. Other than that we are firing off calls to a remote server via a third-party DLL (the application is effectively a client-side GUI).

Annoyingly I can't get a mini program to mimic the behaviour and running in the IDE makes seeing the behaviour difficult, as the IDE itself contains a lot of windows that muddy the Z-ordering.

Edit - After writing my answer below, it appears I'm getting a deactivate event sent to the application (I can catch it through Application.OnDeactivate) - it seems similar to WPF App loses completely focus on window close Delphi doesn't have the Activate method that the c# solutions have, but I'll play with some windows messaging to see if I get anywhere

Community
  • 1
  • 1
Matt Allwood
  • 1,448
  • 12
  • 25
  • 3
    You need to do some debugging. You state that the IDE confounds things. That is normal. But not all debugging should be done in the IDE. Indeed for this sort of debugging the IDE is often useless. Use trace debugging. Logging. What you need to log is the owner of each top-level window. And I don't mean the `TComponent` property `Owner`. In mean the Win32 window owner. Find out what it is by calling `GetWindow` passing `GW_OWNER`. Log it using some logging tool. OutputDebugString would suffice. Then you'll see that the ownership structure is broken. Then you need to find out why. – David Heffernan Mar 17 '16 at 15:58
  • 4
    You'll want to be familiar with this: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632599.aspx and very specifically this *An owned window is always above its owner in the z-order.* The main form's window should generally be at the top of the ownership chain. Your main form window is clearly not. You need to find out why not. – David Heffernan Mar 17 '16 at 16:00
  • Your symptoms are so that your non-modal windows are not enabling in time. I'm not saying it is the case, but it is one thing to check. A disabled window cannot be activated, hence if your calling form is not enabled when a modal dialog closes, the window manager picks another window on its discretion. – Sertac Akyuz Mar 18 '16 at 16:46

1 Answers1

6

Following David's advice in comments I created a little logging form to be created on startup containing memo, timer and the following OnTimer event:

procedure TForm1.Timer1Timer(Sender: TObject);
  function logtomemo(aHandle: HWND): TWinControl;
  var
    form: TWinControl;
  begin
    form := findControl(ahandle);
    if form <> nil then
      memo1.Lines.Add(format('handle %d - form %s', [ahandle, form.Name]));
    result := form;
  end;
var
  handle: HWND;
  form: TWinControl;

begin
  memo1.Clear;
  handle := application.ActiveFormHandle;
  repeat
    form := logtomemo(handle);
    handle := GetWindow(handle, GW_OWNER);
  until (handle = application.MainFormHandle) or (form = nil);
  logtomemo(handle);
end;

Clicking around I noticed that as soon as I clicked outside of my application, our splash form appeared as the only form in the list. (Historically our splash screen used to only be freed after Application.Run, as they used to keep some other references on it for some reason - before my time and wasn't really needed anymore).

Changing the lifetime of the splashscreen to be destroyed before Application.Run appears to have sorted the issue - something that I'd never have guessed would be the cause in a million years.

Need a final sign-off that it doesn't reappear once I get rid of this little debug form, but hopefully a problem that's been frustrating me for a few days is now sorted - thanks!

Edit

As I noted in my edit and the comments to this question, the above debug didn't work, as the presence of the new form 'fixed' the problem. Changing the code so the output was sent to the Event Log or a text file rather than requiring a form also didn't reveal anything - all forms in the Z order remained in place.

In the end, I was able to fix the symptom rather than the cause by attaching the following code to Application.OnModalEnd

if Application.ModalLevel = 0 then
  Windows.SetActiveWindow(Application.MainFormHandle);

This successfully sets the active window back to the main form after the last modal dialog has been closed.

This may have some side-effects if the user is expecting a non-modal form that isn't the main form to regain focus, but our application architecture doesn't really follow this structure and with Application.MainFormOnTaskbar, the main form won't hide the other forms (as long as they're not unparented)

Matt Allwood
  • 1,448
  • 12
  • 25
  • 3
    Logging is such an undervalued debugging tool. Glad that it proved to be so instructive here. – David Heffernan Mar 17 '16 at 17:43
  • @DavidHeffernan Spoke too soon :-( - the presence of my debug form fixes the issue, but it reappears when I remove it again. It appears that my application is getting a Deactivate message sent to it, but having difficulty finding from where – Matt Allwood Mar 18 '16 at 14:22
  • You need to log properly in a non intrusive way. I told you how already. – David Heffernan Mar 18 '16 at 14:27
  • @DavidHeffernan Did that straight after finding the form. Nothing bad occurring to my ownership list, but I am getting a DEACTIVATE message being sent to my app - not sure how to catch what's sending it though but I've linked a similar C# issue to the question – Matt Allwood Mar 18 '16 at 14:29