Parcourir la source

IDE: Use proper owner on message boxes.

AppMessageBox wasn't compatible with MainFormOnTaskBar=True; it displayed every message box with no owner.

Also found that the VCL doesn't set the owner correctly on dialogs displayed when the app isn't in the foreground. Workaround implemented.

Left the IsIconic call (from #164) in for now, though I don't think it's needed anymore in the IDE as long as message boxes use the main form as their owner. It may still be needed by Setup; haven't yet checked.

Jordan Russell il y a 9 mois
Parent
commit
72dd250bb1
2 fichiers modifiés avec 57 ajouts et 5 suppressions
  1. 25 0
      Projects/Src/IDE.MainForm.pas
  2. 32 5
      Projects/Src/Shared.CommonFunc.Vcl.pas

+ 25 - 0
Projects/Src/IDE.MainForm.pas

@@ -504,6 +504,7 @@ type
     function AnyMemoHasBreakPoint: Boolean;
     function AnyMemoHasBreakPoint: Boolean;
     class procedure AppOnException(Sender: TObject; E: Exception);
     class procedure AppOnException(Sender: TObject; E: Exception);
     procedure AppOnActivate(Sender: TObject);
     procedure AppOnActivate(Sender: TObject);
+    class procedure AppOnGetActiveFormHandle(var AHandle: HWND);
     procedure AppOnIdle(Sender: TObject; var Done: Boolean);
     procedure AppOnIdle(Sender: TObject; var Done: Boolean);
     function AskToDetachDebugger: Boolean;
     function AskToDetachDebugger: Boolean;
     procedure BringToForeground;
     procedure BringToForeground;
@@ -1184,6 +1185,29 @@ begin
     MB_OK or MB_ICONSTOP);
     MB_OK or MB_ICONSTOP);
 end;
 end;
 
 
+class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
+begin
+  { As of Delphi 11.3, the default code in TApplication.GetActiveFormHandle
+    (which runs after this handler) calls GetActiveWindow, and if that returns
+    0, it calls GetLastActivePopup(Application.Handle).
+    The problem is that when the application isn't in the foreground,
+    GetActiveWindow returns 0, and when MainFormOnTaskBar=True, the
+    GetLastActivePopup call normally just returns Application.Handle (since
+    there are no popups owned by the application window).
+    So if the application calls Application.MessageBox while it isn't in the
+    foreground, that message box will be owned by Application.Handle, not by
+    the last-active form as it should be. That can lead to the message box
+    falling behind the form in z-order.
+    To rectify that, we return Screen.ActiveForm.Handle if possible, which is
+    valid whether or not the application is in the foreground. This code is
+    from TCustomTaskDialog.Execute. (HandleAllocated call added to be safe) }
+
+  if Assigned(Screen.ActiveForm) and
+     (Screen.ActiveForm.FormStyle <> fsMDIChild) and
+     Screen.ActiveForm.HandleAllocated then
+    AHandle := Screen.ActiveForm.Handle;
+end;
+
 procedure TMainForm.FormAfterMonitorDpiChanged(Sender: TObject; OldDPI,
 procedure TMainForm.FormAfterMonitorDpiChanged(Sender: TObject; OldDPI,
   NewDPI: Integer);
   NewDPI: Integer);
 begin
 begin
@@ -7852,6 +7876,7 @@ begin
 end;
 end;
 
 
 initialization
 initialization
+  Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
   InitThemeLibrary;
   InitThemeLibrary;
   InitHtmlHelpLibrary;
   InitHtmlHelpLibrary;
   { For ClearType support, try to make the default font Microsoft Sans Serif }
   { For ClearType support, try to make the default font Microsoft Sans Serif }

+ 32 - 5
Projects/Src/Shared.CommonFunc.Vcl.pas

@@ -212,16 +212,43 @@ var
   ActiveWindow: HWND;
   ActiveWindow: HWND;
   WindowList: Pointer;
   WindowList: Pointer;
 begin
 begin
+  { Always try to bring the message box to the foreground. Task dialogs appear
+    to do that by default.
+    Without this, if the main form is minimized and then closed via the
+    taskbar 'X', a message box shown in its OnCloseQuery handler gets
+    displayed behind the foreground app's window, with no indication that a
+    message box is waiting. With the flag set, the message box is still shown
+    behind the foreground app's window, but the taskbar button begins blinking
+    and the main form is restored automatically. (These tests were done with
+    MainFormOnTaskBar=True and the message box window properly owned by the
+    main form. Don't run under the debugger when testing because that changes
+    the foreground stealing rules.) }
+  Flags := Flags or MB_SETFOREGROUND;
   if MessageBoxRightToLeft then
   if MessageBoxRightToLeft then
     Flags := Flags or (MB_RTLREADING or MB_RIGHT);
     Flags := Flags or (MB_RTLREADING or MB_RIGHT);
 
 
   TriggerMessageBoxCallbackFunc(Flags, False);
   TriggerMessageBoxCallbackFunc(Flags, False);
   try
   try
-    { If the application window isn't currently visible, show the message box
-      with no owner window so it'll get a taskbar button } 
-    if IsIconic(Application.Handle) or
-       (GetWindowLong(Application.Handle, GWL_STYLE) and WS_VISIBLE = 0) or
-       (GetWindowLong(Application.Handle, GWL_EXSTYLE) and WS_EX_TOOLWINDOW <> 0) then begin
+    { Application.MessageBox uses Application.ActiveFormHandle for the message
+      box's owner window. If that window is Application.Handle AND it isn't
+      currently shown on the taskbar [1], the result will be a message box
+      with no taskbar button -- which can easily get lost behind other
+      windows. Avoid that by calling MessageBox directly with no owner window.
+      [1] That is the case when we're called while no forms are visible.
+          But it can also be the case when Application.MainFormOnTaskBar=True
+          and we're called while the application isn't in the foreground
+          (i.e., GetActiveWindow=0). That seems like erroneous behavior on the
+          VCL's part (it should return the same handle as when the app is in
+          the foreground), and it causes modal TForms to get the 'wrong' owner
+          as well. However, it can be worked around using a custom
+          Application.OnGetActiveFormHandle handler. }
+    var ActWnd := Application.ActiveFormHandle;
+    if ActWnd = 0 then  { shouldn't be possible, but they have this check }
+      ActWnd := Application.Handle;
+    if (ActWnd = Application.Handle) and
+       (IsIconic(Application.Handle) or
+        (GetWindowLong(Application.Handle, GWL_STYLE) and WS_VISIBLE = 0) or
+        (GetWindowLong(Application.Handle, GWL_EXSTYLE) and WS_EX_TOOLWINDOW <> 0)) then begin
       ActiveWindow := GetActiveWindow;
       ActiveWindow := GetActiveWindow;
       WindowList := DisableTaskWindows(0);
       WindowList := DisableTaskWindows(0);
       try
       try