Hidden modal dialogs in Windows XP

1529 views
Skip to first unread message

Scott Shelton

unread,
Jul 28, 2003, 2:06:54 PM7/28/03
to
In Windows XP, modal dialogs intermittently get hidden behind other
windows after switching between applications. Once the modal dialog
is hidden, the application appears locked up. The user can press
ALT+TAB to bring the modal dialog back into focus.

This occasionally happens in all versions of Windows with all
applications, including Microsoft's own software. However, Microsoft
changed something in the API that is causing this to happen a lot more
frequently in Windows XP.

One way to reproduce the problem on Windows XP is to switch
applications while the program is processing to bring up a message
dialog.

procedure TForm1.Button1Click(Sender: TObject);
begin
Sleep(10000); // Now switch applications while the program is busy.
MessageDlg('This should not be hidden behind the main form.',
mtInformation, [mbOK], 0);
end;

Scott Shelton

unread,
Jul 29, 2003, 8:44:42 AM7/29/03
to
One alternative that works is calling Windows.MessageBox instead of
MessageDlg.

MessageBox(Screen.ActiveForm.Handle, PChar('This will not be hidden
behind other forms!'), PChar('MessageBox'), MB_APPLMODAL or
MB_ICONEXCLAMATION);

Microsoft changed something in Windows XP that causes MessageDlg (and
other modal dialogs) to intermittently hide behind other windows if
you switch applications before the dialog pops up. However, this does
not seem to have affected the "MessageBox" API call.

MessageBox allows you to pass in the owner handle of the message
window, while MessageDlg uses Application as the owner. You can see
this in the function "CreateMessageDialog" in Delphi's VCL
"Dialogs.pas". However, the owner handle is not the only cause of this
problem. I tried changing just that part in MessageDlg calls and the
dialogs would still hide behind other windows. From what I've read
online, Microsoft has changed something deep in Windows XP API calls
that messes up the Z-order of MessageDlg calls, but not MessageBox.
Not only are MessageDlg windows sometimes hidden, but sometimes
they're not even modal!

MessageBox has a slightly smaller subset of buttons from which to
choose, while it adds the ability to customize the title of the
message dialog. Other than that, MessageBox and MessageDlg are
visually the same.

NOTE: This won't completely solve the hidden dialog problem, because
it can happen with any modal form. Using MessageBox instead of
MessageDlg merely reduces your chances of seeing the problem.

Scott Shelton

unread,
Jul 29, 2003, 2:04:33 PM7/29/03
to
We found a complete solution to the diving dialog problem!
It's actually done in 2 parts:

1. Call "Screen.ActiveForm.Show" in an "Application.OnActivate" event
handler.
2. Disable Process Windows Ghosting in Windows XP.

I will discuss part 1 here. This will solve the problem of a modal
dialog hiding behind other windows if you switch to a different
application while it's processing.

I created an "Application.OnActivate" event handler which calls
"Screen.ActiveForm.Show". This brings the active form (modal or
otherwise) to the foreground. This works for forms, MessageDlg,
ShowMessage, and MessageBox calls. (So if you do this, you don't have
to use MessageBox instead of MessageDlg.)

In the main form of your application, define an Application OnActivate
event handler, and assign it during FormCreate.

procedure OnApplicationActivate(Sender: TObject);

procedure TfrmMain.FormCreate(Sender: TObject);
begin
// Setup Application.OnActivate event handler.
Application.OnActivate := OnApplicationActivate;
end;

// Application.OnActivate event handler
procedure TfrmMain.OnApplicationActivate(Sender: TObject);
begin
// This is designed to prevent modal dialogs from being
intermittently
// hidden behind other windows, predominantly in Windows XP.
Application.ProcessMessages;
if (Assigned(Screen.ActiveForm)) then begin
// You may wish to temporarily turn off any OnShow
// event handlers for the active form here.
Screen.ActiveForm.Show;
end;
Application.ProcessMessages;
end;

Scott Shelton

unread,
Jul 29, 2003, 2:31:10 PM7/29/03
to
Here is part 2 of the fix, Disabling Windows Ghosting.

This prevents a different way to get dialogs to hide in Windows XP,
which is to drag the title bar of the main form while it's busy
bringing up a new form.

In previous versions of Windows, an application could be considered to
be 'Not Responding' by the OS if it was busy processing for roughly 10
seconds or more, even if it was truly working. During this time, the
main form was disabled and the user could not interact with the
application until it finished processing.

In Windows XP and above, Microsoft enabled "Window Ghosting" to give
the user control back to the application if it appeared hung. In
theory, this allows the user access to the title bar of the busy
application so it can be closed. However, by clicking back on the
main form while it is processing to bring up a new form, the main form
receives focus and the new form appears behind the main form again
(and it is non-modal).

We found a way to disable "Window Ghosting" for an application in a
newsgroup. This disables a feature of Windows XP, but it prevents
this and other problems. Add this function call to the DPR project
source code (in Delphi, select "Project/View Source"). It should be
called before "Application.Initialize".

procedure DisableProcessWindowsGhosting;
var
DisableProcessWindowsGhostingImp : procedure;
begin
@DisableProcessWindowsGhostingImp :=
GetProcAddress(GetModuleHandle('user32.dll'),
'DisableProcessWindowsGhosting');
if (@DisableProcessWindowsGhostingImp <> nil) then
DisableProcessWindowsGhostingImp;
end;

begin
// Disable Window Ghosting for the entire application.
DisableProcessWindowsGhosting;
Application.Initialize;
Application.Title := 'Hidden Dialog Test';
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.

Scott Shelton

unread,
Aug 6, 2003, 11:28:34 AM8/6/03
to
Our fix for the XP "diving dialog" problem doesn't work for standard
Windows dialogs, like "Save As". If a modal dialog launches a
TSaveDialog and you switch applications, the TSaveDialog will be
hidden behind the launching modal dialog (but not behind the entire
app).

We disabled Windows Ghosting and in the Application.OnActivate event
handler, we are calling
Screen.ActiveForm.Show;

The problem is that Windows XP does not recognize standard Windows
dialogs as the Screen's ActiveForm. It treats them as a special case
because they're not descended from forms. So, when the
Application.OnActivate event fires, it brings what it thinks is the
active form to the front, which is actually the modal dialog that
launched the "Save As" dialog.

This problem occurs for other standard modal dialogs, including:
Save As Dialog
Open Dialog
Font Dialog
Print Dialog
Printer Setup Dialog
Color Selector Dialog

However, the previous fix does work for modal forms that we create, as
well as ShowMessage, MessageDlg, MessageBox, etc.

Scott Shelton

unread,
Aug 22, 2003, 12:46:59 PM8/22/03
to
I fixed the XP diving dialog problem to keep standard Windows dialogs
on top when you switch applications, as well as descendents of TForm.

I overrode the OnShow and OnClose event handlers in subclassed dialog
components to store their window handles in a global variable,
"DialogToPlaceOnTop". In our Application's OnActivate event handler,
I check for that variable. If it is not equal to 0, I bring the
dialog's window handle to the front. If it is equal to 0, I call
"Screen.ActiveForm.Show".

Now in the application, I just converted all existing calls to
TOpenDialog, TSaveDialog, etc. to be the subclassed dialog components
instead. The sample code below illustrates overriding
TOpenDialog--similar subclasses should be created for all standard
dialogs found in Delphi's "Dialogs.pas".

type
TTopmostOpenDialog = class(TOpenDialog)
private
{ Private declarations }
protected
{ Protected declarations }
published
{ Published declarations }
procedure DoShow; override;
procedure DoClose; override;
public
{ Public declarations }
end;

var


// This is designed to prevent modal dialogs from being
intermittently

// hidden behind other windows, predominantly in Windows XP. The
calling
// application can use this global variable to get the window handle
// of one of these dialogs and bring it to the top, if necessary.
// This should be done in the Application's OnActivate event
handler.
DialogToPlaceOnTop: HWND = 0;

implementation

{ TTopmostOpenDialog }

procedure TTopmostOpenDialog.DoClose;
begin
inherited;
DialogToPlaceOnTop := 0;
end;

procedure TTopmostOpenDialog.DoShow;
begin
inherited;
DialogToPlaceOnTop := Handle;
end;

Then, in the main unit of the application I modified the
Application.OnActivate event handler that was previously implemented.

// Application.OnActivate event handler
procedure TfrmMain.OnApplicationActivate(Sender: TObject);
begin
// This is designed to prevent modal dialogs from being
intermittently
// hidden behind other windows, predominantly in Windows XP.
Application.ProcessMessages;

if (DialogToPlaceOnTop <> 0) then begin
// If this global variable is set from a subclassed dialog,
// it means a dialog is showing and should be brought to the top.
// This includes Open, Save, OpenPicture, SavePicture, Font,
Color,
// Print, Printer Setup, Find, and Replace dialogs. Since these
dialogs
// are descended from TCommonDialog instead of TCustomForm, they
do not
// get recognized by Screen.ActiveForm.
BringWindowToTop(DialogToPlaceOnTop);
end
else if (Assigned(Screen.ActiveForm)) then begin
// Bring the active form to the forefront. This only applies to
descendents
// of TCustomForm. For example, "MessageDlg" is descended from
TCustomForm,
// but "Windows.MessageBox" is not. Fortunately,
"Windows.MessageBox"
// always shows on top anyway, and is unaffected by this Windows
XP bug.
Screen.ActiveForm.Show;
end;
Application.ProcessMessages;
end;

Reply all
Reply to author
Forward
0 new messages