Date: | February 27, 2004 / year-entry #77 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20040227-00/?p=40463 |
Comments: | 42 |
Summary: | If you want to display modal UI, you need to disable the owner and enable the modal child, and then reverse the procedure when the modal child is finished. And if you do it wrong, focus will get all messed up. If you are finished with a modal dialog, your temptation would be to clean... |
If you want to display modal UI, you need to disable the owner and enable the modal child, and then reverse the procedure when the modal child is finished. And if you do it wrong, focus will get all messed up. If you are finished with a modal dialog, your temptation would be to clean up in the following order:
But if you do that, you'll find that foreground activation doesn't go back to your owner. Instead, it goes to some random other window. Explicitly setting activation to the intended owner "fixes" the problem, but you still have all the flicker, and the Z-order of the interloper window gets all messed up. What's going on? When you destroy the modal dialog, you are destroying the window with foreground activation. The window manager now needs to find somebody else to give activation to. It tries to give it to the dialog's owner, but the owner is still disabled, so the window manager skips it and looks for some other window, somebody who is not disabled. That's why you get the weird interloper window. The correct order for destroying a modal dialog is
This time, when the modal dialog is destroyed, the window manager looks to the owner and hey this time it's enabled, so it inherits activation. No flicker. No interloper. |
Comments (42)
Comments are closed. |
Since I’ve never coded a WinForms app (just Asp.Net) I’m curious – why do I have to disable the owner of a modal dialog? Doesn’t modal dialog imply that user cannot interact with the owner until the dialog is dismissed?
Where does the term "Modal" come from?
I am going to make some guesses here. Jerry, he is referring to Win32 apps. You are thinking in terms of the end user, he is describing the coding that is required to respond to a user command to destroy the modal dialog.
This seems like a good candidate for some "Pit of Success" treatment. Sure it makes sense; but it is an invitation for buggy code. Perhaps an api call that did something like DestroyModalDialog( bFocusToParent ) bFocusToParent being a boolean for whether or not to make sure the parent dialog box is enabled.
Jerry: I’m talking about the case where a program is manually managing modality. If you use built-in functions like DialogBox() then the OS manages the enabling/disabling for you.
Steve: Conceptually, a modal dialog is one that forces you to complete a task before you can continue. The program has been put into a "mode" that you have to complete before you can continue. I don’t know if that’s the actual history of the term but that’s how I think of it.
Steve: Modal comes from the idea of programming "modes", where instead of working on a given task (eg. using a wordprocessor or a webbrowser), you switch the application into a different mode of operation (eg. changing configuration parameters, or creating mailing labels).
Modeless dialogs do not involve this conceptual switchover, as they act within the context of the current task. Modeless dialogs, however, require that you focus entirely on another task, which can completely change the behavior of the previous task – and so you have to wait until the new task is finished before you go back. Which is why modal things prevent you from messing with the main application.
As a user interface design principle, modality is to be avoided if at all possible, because it forces the user down a given path and prevents them from having full control over the application. There are cases where it makes sense, but it has been abused really badly in the past.
Raymond: Yep, that’s it. I can’t remember where I first came across the term… it might have been in an Apple Lisa version of the Apple Human Interface Guidelines, but it dates back to the days of text console displays on mainframes, where each display screen was a different mode.
This isn’t applicable to MFC is it?
Cdialog1Dlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
MFC uses modeless dialogs behind the scenes. The DialogBox API does its own modal message loop internally, which MFC can’t hook into. This would mean that things like PreTranslateMessage and idle processing would fail to work.
I believe there are other reasons too.
Thankfully, MFC appears to get this right.
See the code in DLGCORE.CPP (if you don’t have MFC source installed and you’re writing MFC code, go and install it right now, you’ll find debugging much easier, IMO).
I see, this is about using modeless dialogs and making them act as modal. Then it makes sense even though I still don’t understand why not just use a modal dialog if I want to show the user a modal dialog. Any processing can (and imo should) run on a worker thread…
If you are using a framework that handles for you, then great! Use the framework and then you don’t need to worry about this.
But if you want to do it manually, now you know how. (For example, if you’re writing your own framework.)
Let me just make sure I’m clear on this:
Referring back to your previous discussion on modal dialogs…
When you create a dialog, you specify its owner.
DialogBox then – if this owner is not NULL – disables the owner window, which then disables all of its child windows.
If you specify the desktop window as your owner, this causes problems – because every top level window is a child of the desktop, and disabling the desktop then disables the dialog.
Disabling / Enabling a window only affect its children, and not windows that are owned by the window being enabled/disabled.
Owned and child windows always appear above their owners in the Z-order.
The question being this:
If you create a window, and give it a modeless dialog (eg. a find window), and then the user does something to show a modal dialog (eg. decides to change app. config), what happens to the modeless find window? Do you have to disable this separately and manually? Or am I missing a piece of the relationship here?
Thanks!
You can run this experiment and answer the question yourself.
Run Notepad, hit Ctrl+F to open the Find dialog, which is modeless.
Now click File, Save As. This creates a modal dialog.
Now play around.
Yipes. That’s fascinating, not the behavior I expected, and slightly scary all in one.
Even with a modal dialog present, I can still do an instance-by-instance replace of text in the control.
Now, that leaves us with another question: what would the *right* way to handle this be?
Personally, if I were implementing notepad, I’d probably come up with some way of disabling all of my owned windows except the one I’m using as a modal dialog. So you wouldn’t be able to activate the find/replace window while you were trying to save.
Ouch – thanks Raymond – that’s weird.
Using modeless dlgs and faking modality is also useful in WTL, if you’re like me and need PreTranslateMessage() to work when the dialog is up. You have to do that to get proper keyboard handling when there are ActiveX controls in the dialog.
Someone at the eMbedded Visual C++ group should look at this article… it has the flicker bug in the "send file to device" dialog.
Sometimes i’ve found it that while filling in a modal dialog, i need to write some text in it that i forgot to copy / find from the now disabled parent/owner. Currently it sometimes works and sometimes maybe not, can’t remember.
Point is i find useful it that you can scroll/copy from a textbox while the same app has a modal dialog open. Most of the time the modal dialog will have OK/Apply button that does the changes, why should the rest of the app be ‘stuck’ while i’m thinking whether to do the thing in the modal dialog or not? It’s not like your desktop gets disabled while you are at display properties! And it shouldn’t!
Or maybe i’ve just misunderstood the whole thing you are talking here :-)
Trying to answer my own question.. Maybe apps should use Modal Dialogs only when it is really necessary to disable the owner.
For example, when i press the About box in help menu, what is there in the regular About box that has to disable rest of the UI while viewing it? Maybe it keeps the UI a bit simpler when you can’t open a ton of dialogs, but as a power user i like control.
Someone in the Outlook Express group should read Mr. Chen’s blog. In another note someone mentioned that Outlook Express leaks GDIs every time the user changes folders. Here we can mention the FOUR flickers every time the OE main window recovers focus from a dialog box. This beats the one flicker that Mark mentioned for Pocket Explorer.
Joku wrote:
> Trying to answer my own question.. Maybe
> apps should use Modal Dialogs only when it
> is really necessary to disable the owner.
Yep :) If you don’t need to disable the owner, you shouldn’t; it removes control from the user, and makes the app less intuitive and fun to use.
> For example, when i press the About box in
> help menu, what is there in the regular
> About box that has to disable rest of the UI
> while viewing it? Maybe it keeps the UI a
> bit simpler when you can’t open a ton of
> dialogs, but as a power user i like control.
Good question… I guess it’s because it’s not something you generally want to see, so it’s probably not a good idea to have the window hanging around, but that is indeed an interesting edge case.
When I use the find and replace while saving feature in Notepad, the replace dialog seems to ignore the tab key while the save dialog is shown. Odd.
That’s because Tab and Alt+? for a dialog are handled by calling IsDialogMessage on the HWND of that dialog.
Normally Notepad calls IsDialogMessage(hwndFindOrReplaceDialog) in its main message loop. But while a modal dialog is open, the main message loop isn’t running; it’s replaced by one inside the Save dialog.
The Save dialog doesn’t know about Notepad’s Replace dialog, so the Save dialog grabs the Tab and Alt+? keys.
If you would like to show another application (exe) as a modal dialog, what is the signal that it is time to re-enable your window? My guess is that you would have to write some sort of hook to get the WM_CLOSE message for the apps main window, but I was wondering if there is a standard recommended approach.
Eva, you normally wait for a process to end by starting it with CreateProcess and using one of the wait functions (e.g. WaitForSingleObject) to wait until the process handle is signalled.
Jonathan/Tim: That’s correct. Which is why making "all" UI modeless is very difficult to code. It’s a very different programming model, since you have to turn everything into a state machine.
Seth– if you wait for the process handle to be signaled, it is too late; the app window is already destroyed and the behavior Raymond describes happens (flicker, interloper) since the owner dialog has not been re-enabled in time.
You have to have the cooperation of the program you are launching. Anything else is just a hack.
Raymond– can you recommend a design pattern for this (high level)? We have control over the applications we are launching. Thanks.
Very simple. Pass the window handle to the launched process. Then the launched process passes that window handle as the owner window for its UI. If you use a system function like DialogBox(), then the proper enabling/disabling happens automatically. If you do some custom UI then you’ll have to do it manually as described above.
Maybe you can explain something I noticed when I first started windows programming, but never felt I understood it 100%: Is there any difference between a parent window and a owner window, other than a child window must be inside its parent, but owned window is not inside the owner window ? Why there’re Get/SetParent() but no Get/SetOwner() ? But in GetWindow() call, there’s GW_OWNER, but no GW_PARENT.
Owner and parent are very different. I have a note to explain the difference in a future entry, thanks for asking.
BY: Maybe I can answer this… probably not as well as Raymond could, but here you go:
Parent/Child relationships are part of the window tree, and strongly determine Z-ordering. Changing the parent/child relationship immediately forces changes to the window hierarchy, and requires a re-layout of the window information (thus it’s a separate function).
Owner/Owned relationships aren’t part of the Window tree; they only weakly determine Z-ordering. Changing the owner of a window has no immediate effect on the layout of the windows, and as such can be left to GetWindowLong/ SetWindowLong as it doesn’t need to interface to the Window Manager.
At least, that’s my rather lame understanding of it.
Regarding parent/child relationships…
I have once had an attempt to build an appbar stuck at the screen edge. That’s nothing really special. But I had it contain other applications’ top-level windows, those that never were designed to be, so to say, /adopted/.
For the most part, it worked. But it introduced all kinds of system instability.
I’m just curious… what evil things may happen when you take a window that was born to live free on a desktop, and SetParent it into a cage? (Not that I would want to try it again…)
The biggest problem is that activation goes all weird for that one stack of windows.
Heck, check out MDI; that’s pretty much the same thing you’re describing :-)
Raymond: I have always wondered what happened to modal dialogs in Windows 2000 (or NT 4, can’t remember for sure). When most apps show a modal dialog, clicking the owner window causes the title bar of the modal dialog to flash a few times. In some applications (like VS.NET 2003) modal dialogs still work like they always have been (no flashing). Was this change in Windows a deliberate one or is the flashing just a bug?
The flashing was a feature added to DefWindowProc, as I recall.
Any idea what’s the reason for adding this feature (flashing)?
I thought it was obvious. If you click on a window that is disabled because it has a modal dialog up, it used to be that nothing happens Then you get frustrated and say, "I hate Windows; it always hangs," and reboot your computer.
The blinking is there to say, "Look over here. This is the window you want."
Commenting on this article has been closed.
Exploiting the rules for handling of the WM_QUIT message.
Disable the owner window, but make sure it’s really the owner.