Date: | September 22, 2006 / year-entry #324 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060922-03/?p=29623 |
Comments: | 53 |
Summary: | One customer wanted to wait until the dialog box was displayed before displaying its own dialog box. (Personally, I think immediately displaying a doubly-nested dialog box counts as starting off on the wrong foot from a usability standpoint, but let's set that issue aside for now.) The customer discovered that displaying the nested dialog box... |
One customer wanted to wait until the dialog box was displayed
before displaying its own dialog box.
(Personally, I think immediately displaying
a doubly-nested dialog box counts as starting off on the wrong foot
from a usability standpoint,
but let's set that issue aside for now.)
The customer discovered that displaying the nested dialog box
in response to the One proposed solution was the following code fragment: case WM_INITDIALOG: PostMessage(hDlg, WM_APP, 0, 0); return TRUE; case WM_APP: ... display the second dialog ... break;
|
Comments (53)
Comments are closed. |
I’ve used the first WM_PAINT for something similar, on the grounds that if the window’s being painted it’s probably visible. Feel free to wince, tell me that it won’t work in Vista, etc.
"I’ve used the first WM_PAINT for something similar, on the grounds that if the window’s being painted it’s probably visible. Feel free to wince, tell me that it won’t work in Vista, etc."
I don’t like adding any overhead to WM_PAINT so, to me, this is a bad idea.
The overriding issue here is the priority of messages retrieved by GetMessage: first sent messages, then posted messages, input messages, then finally paint messages are generated if there are any invalid regions, and right at the end timer messages.
You can pretty much assume that when WM_PAINT returns (and there are no more invalid regions, although it’s a bad WM_PAINT handler that doesn’t do all its painting in one pass) that the window is displayed, although IIRC child windows are painted after their parent – in fact painting should occur in Z-order, back to front – so the dialog’s controls won’t be fully painted until the last child’s WM_PAINT returns.
On Vista with Glass presumably some notification is given to DWM that there is no more painting to do and it can now update the texture on the window client area.
So you could always use a timer. When the timer fires you can guarantee that no more painting is required. Feels a bit kludgy, though.
1) There will be a short delay before the 2nd dialog is displayed (due to other message processing?)
2) WM_ACTIVATE?
I’ve always used a WM_TIMER for this. Perhaps handling WM_WINDOWPOSCHANGED once is better?
I fail to see why posting WM_APP is a bad idea though. After all, WM_APP is specific to the application, right?
Because your main line window processor does not run until the dialog exits.
But it doesn’t need to – DialogBox would have a modal message loop in it, so posted messages would still get through.
"Personally, I think immediately displaying a doubly-nested dialog box counts as starting off on the wrong foot from a usability standpoint, but let’s set that issue aside for now."
Right-click the clock in the notification area and select "Customize Notifications…". Watch as the taskbar properties dialog comes up and is immediately occluded by the Customize Notifications dialog. You opened one dialog but have to dismiss two.
Not saying you’re wrong, and not blaming you for this, but it /is/ in the Windows shell…
Yo Matthew Douglass-Riley – Raymond made a post about this kind of comment and its worth reading.
The people that right the shell are just that…people. they make mistakes too!
Seems that it’s not a modal dialog so WM_ENTERIDLE does not work.
WM_ACTIVATE could not work either.
WM_SETFOCUS?
If the first dialog box is modal, I’d show the 2nd dialog box in response to the first WM_SHOWWINDOW message.
If the first dialog box is modaless, then it’s just CreatDialog(), ShowWindow(), then display the 2nd dialog box.
Reminds me of <a href="http://qstuff.blogspot.com/2006/07/animation-control-incident.html">this</a>
I bet the "wrong" code would work in practice.
Doesn’t WM_APP get queued beside the other stuff so will arrive after WM_ACTIVATE (or whatever).
I don’t use dialogs, ever.
I always work at a higher level than the windows API, but I’m guessing that the dialog response is passed to the application through a message – handle that message and use it to display the second dialog unless the user cancelled the first one. Or just display the second dialog with an option to cancel :)
I had a similar problem with .NET and want to know the answer. I used a timer bad then and felt very guilty.
I hate it when I select one menu item, or make one click (on a webpage) and more that one thing (window and, or, dialog) pops up on my screen.
I hate it more when one or more of the multiple things that popped-up are not ‘together’ (one may be at the bottom of a pile of things I actually wanted to open).
Almost unimaginably worse still is when I try to click on what I think is the dialog I’ve just opened and it makes a flat ‘boink’ noise at me and tells me I’ve got some open dialog box somewhere without just bringing it to the foreground so I have to go off searching amongst numerous open windows (dialogs), Outlook does this when I’ve left an address-book search open.
Just don’t nest your dialogs. don’t Don’t don’t, be creative, there must be a creative solution, you can do it….
First thing that came to mind for me is that PostMessage doesn’t wait for the thread to return, so the dialog initialization will continue?
I’ve done the timer solution too :P
Why is the WM_APP solution bad? I’m not sure. Is it because that message gets processed immediately (therefore still too early)?
I would agree, this is starting on the wrong foot.
But, if I *had* to do something like this I’d look into the WM_ENTERIDLE message.
I always used the WM_APP approach in such cases. Maybe this typeahead thingie you mentioned in the link to the blogpost of 11/03/2004? You could dismiss the first dialog and the second won’t be displayed? just a random thought…
Maybe calling EnableWindow(hDlg, FALSE) in the first dialog’s WM_INITDIALOG handler before posting WM_APP?
You arent supposed to use WM_APP. Those are used by MFC. You are supposed to WM_USER. Where do I collect my prize?
Given the description of the typeahead thing, it’s theoretically impossible. What if the dialog box is dismissed before it is displayed?
Taking the "spec" literally, you could also just wait until the user clicked a button. Presumably they’ve seen the button if they can click on it, so it is after the dialog has been displayed. (You’d have to look for a real click to avoid the typeahead thing, and then it’d suck for accessibility. But I doubt that’s high on the list of requirements.)
The only notifications that I see that could make sense are WM_WINDOWPOSCHANGED, WM_STYLECHANGED, or maybe WM_NCACTIVATE. You might get by posting a WM_USER message from WM_PAINT, but that just seems hacky. And I’m going to guess that all of those are wrong, because I don’t actually have a clue. :)
The answer to two is to rethink why you need to pop up two dialogs and only pop up one instead. :P
El Guapo: WMP_APP is not at all an MFC-thingie. Actually IIRC you are discouraged to use (WM_USER+x), be it MFC or Win32 SDK-based, since the advent of Win32, because the new common controls of Windows 95 use WM_USER internally a lot. So if you enumerate child controls in your dialog and send them (WM_USER+x), they might behave very strange.
can OnInitDialog be used instead of the windows messages?
OnInitDialog is just MFCs method that is invoked when a WM_INITDIALOG message is received.
MFC is mostly just a wrapper around windows messaging (WTL is an even thinner wrapper).
My understanding of proper WM_USER based message usage is that they are window class specific, so if you define your own class, feel free to define your own WM_USER based messages to be received by windows using that class. The common controls being an example of a set of window classes, and thus define their control specific messages using WM_USER based messages.
So yes, enumerating child windows and sending them all the same WM_USER+x message will no doubt often result in weird behaviour.
Similarly WM_APP+x messages are intended to be meaningful within an application.
Well it looks like this we don’t actually know for sure the answer to this one…
Personnally I’d post message a WM_USER message to myself on the first WM_PAINT.
The reason why I’m more at ease with WM_PAINT is that when that message comes in, it’s REALLY because the dialog is visible to the user after that message.
The active and focus message are to consider, but really in some cases dialogs are not active and then the second dialog would come up when the user clicks on the dialog and that’s just awkward.
Actually to be perfectly honnest in my own code I try first postmessage the WM_USER from WM_InitDialog. Then go for a PostMessage on WM_PAINT. What I like about the WM_PAINT is also that the dialog is truely visible at that point.
I would never use WM_TIMER because timers to me are 99% of the time hacks and they are unreliable. The user could have time to do something in the fist dialog before the second comes up. I like WM_TIMER for their property of collapsing themselves, but you never know when you’ll get them.
I am not familiar with WM_ENTERIDLE
For an application to be able to wait for the dialog to be displayed in order to do something else, it either must have more than one thread or it must use MODELESS dialog because if you have only one thread and you call DialogBox then who is going to get you WM_* notification?
MSDN Quote Of The Day:
"DialogBox does not return control until the specified callback function terminates the modal dialog box by calling the EndDialog function."
how about CDialog::OnShowWindow
Ignor
MFC modal dialogs are actually implemented as modeless dialogs. Thge modality is simulated.
See http://www.codeproject.com/dialog/notmodaldialogs.asp
Nish,
Yes but you call dlg.DoModal(). And it is not a good idea to nest another dialog creation inside DialogProc() processing loop.
#1 is wrong because it would display second dialog regardless of the visibility state of the first one.
Why? Because:
(uMsg == WM_INITDIALOG) != ((uMsg == WM_SHOWWINDOW) && (wParam == TRUE) && (lParam == 0))
Correct solution for #2 could be to wait for WM_SHOWWINDOW with wParam == TRUE (being shown) and with lParam == 0 (by call to ShowWindow()) but as I said I would try hard to avoid creating another dialog box inside DialogProc() function because it would prevent message processing while the second dialog is shown unless you create the second one as modeless.
I was never sure about the way I handle such case in my app, but so far it appears to work well.
I have some animation and I start with it only after:
a) window had WM_PAINT and WM_ACTIVATE or
b) more than 5 seconds passed since WM_CREATE
… AND PeekMessage() returns FALSE – no more messages waiting.
Option b) is fail-safe protection, just in case. Great to see someone writing about the subject, I guess.
Oops, it won’t do. :)
I realized the same problem still exists, window is shown after processing of WM_SHOWWINDOW message.
Well then you can only use WM_WINDOWPOSCHANGED I guess and check if you have topmost Z order and if flags in WINDOWPOS say that you are visible.
Oh, and the solution I chose in that case was not to defer the message box with a timer, but to instead pass the handle of a window that was actually visible (the owner of the form getting created) :)
Raymond, does this second dialog have to prevent user from closing the first one until it is dismissed?
You mentioned typeahead possibility (for example Alt+F4) so that is why I ask.
I usually just use a timer. Especially from .NET code.
Though on that note I’ve recently tripped over the other gotcha of dialogs that Raymond’s mentioned before (*thank you*!) — passing in a nonvisible parent window. In this case, passing ‘this’ as the owner of a MessageBox while still in the OnLoad event (when the form isn’t visible yet). It results in Bad Things ™ :)
I would use my golden hammer: THREADS
Create a thread (on WM_PAINT) which creates the second dialog window. Problem solved.
What is the correct solution? You probably know this already.
41 answers thus far, and clearly we don’t ;-)
Well, obviously handling WM_WINDOWPOSCHANGED does not work. It still happens at too early a stage.
Probably the only certified way to do it without relying on arbitrary delays in handling posted messages and elapsed timers, in my opinion, would be to install a WH_FOREGROUNDIDLE thread hook and remove it immediately upon handling the request, before displaying the secondary stacked dialog box.
It seems to work on a quick test I have made, but it feels like using a hammer to crush a fly.
Answer to previous exercise.
PingBack from http://blogs.msdn.com/oldnewthing/archive/2006/09/25/770536.aspx
I’m confused. If nesting DialogBox() before the outer dialog is visible does Bad Things ™, does that mean that anyone who wants to nest a dialog based on a keypress is out of luck if the key is pressed during the typeahead processing?
So what’s the "good" Answer for #2? (or did I miss it in the comments?)
I do
dlg1.OnInitDialog:
// control initializaiton
ShowWindow(SW_SHOW)
dlg2.DoModal();
It should work with the typeahead (unless the first dialog doesn’t get painted when it is "inside" Alt+Space)
Sometimes this seems to need a CenterWindow, too, which puzzles me.
nevermind, just noticed the new blog entry