Things you already know: How do I wait until my dialog box is displayed before doing something?


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 WM_INITDIALOG message was premature, because as we all know, the WM_INITDIALOG is sent before the dialog box is displayed. The question therefore is, "How do I want until my dialog box is displayed before doing something?"

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;
  1. Why is this wrong? Hint: You definitely know the answer to this already.

  2. What is the correct solution? You probably know this already.
Comments (53)
  1. John Elliott says:

    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.

  2. Orion Adrian says:

    "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.

  3. Mike Dimmick says:

    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.

  4. Mark Ingram says:

    1) There will be a short delay before the 2nd dialog is displayed (due to other message processing?)

    2) WM_ACTIVATE?

  5. Maxime LABELLE says:

    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?

  6. Doug says:

    Because your main line window processor does not run until the dialog exits.

  7. Mike says:

    But it doesn’t need to – DialogBox would have a modal message loop in it, so posted messages would still get through.

  8. Matthew Douglass-Riley says:

    "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…

  9. Joe says:

    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!

  10. Shawn says:

    Seems that it’s not a modal dialog so WM_ENTERIDLE does not work.

    WM_ACTIVATE could not work either.

    WM_SETFOCUS?

  11. gid says:

    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.

  12. Mike Jones says:

    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).

  13. Boogie says:

    I don’t use dialogs, ever.

  14. Richard says:

    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 :)

  15. Christian says:

    I had a similar problem with .NET and want to know the answer. I used a timer bad then and felt very guilty.

  16. :: Wendy:: says:

    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….

  17. Andrew says:

    Why is this wrong? Hint: You definitely know the answer to this already.

    I didn’t see an answer to this, so here goes. The WM_APP message is being posted when WM_INITDIAG is processed. Raymond already said that the dialog is not displayed when WM_INITDIAG is sent so why would the posted WM_APP message be processed after it is displayed?

  18. Chris says:

    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?

  19. glonq says:

    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)?

  20. Peter Ritchie says:

    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.

  21. Stefan Kuhr says:

    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…

    [Yes, the answer to the question is the typeahead thing. Imagine if the user typed-ahead an Alt+Space. The second dialog would show while the first dialog was still waiting for the menu operation to complete before it can show itself. Now on to the second question… -Raymond]
  22. Stefan Kuhr says:

    Maybe calling EnableWindow(hDlg, FALSE) in the first dialog’s WM_INITDIALOG handler before posting WM_APP?

  23. El Guapo says:

    You arent supposed to use WM_APP. Those are used by MFC. You are supposed to WM_USER. Where do I collect my prize?

  24. josh says:

    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.  :)

  25. Ross Bemrose says:

    The answer to two is to rethink why you need to pop up two dialogs and only pop up one instead. :P

  26. Stefan Kuhr says:

    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.

  27. alex@zoosmart.us says:

    can OnInitDialog be used instead of the windows messages?

  28. Tim Smith says:

    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).

  29. martin says:

    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.

  30. Ulric says:

    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

  31. Igor says:

    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."

  32. alex@zoosmart.us says:

    how about CDialog::OnShowWindow

  33. Nish says:

    Ignor

    MFC modal dialogs are actually implemented as modeless dialogs. Thge modality is simulated.

    See http://www.codeproject.com/dialog/notmodaldialogs.asp

  34. Igor says:

    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.

    [? People create modal dialogs from DlgProc all the time. I bet you do it too. -Raymond]
  35. Igor Delovski says:

    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.

  36. Igor says:

    ? People create modal dialogs from DlgProc all the time. I bet you do it too.

    You are right, I do it. MessageBox() and PropertySheet() still count as a dialog boxes.

    But I was somehow under impression that this application needs to continue some processing while dialogs are displayed. It turns out that it is just an annoying popup before user is allowed to work with the first dialog.

    Then it is easy, this should do the trick:

    static BOOL spam_user = TRUE;

    case WM_SHOWWINDOW:

       if (spam_user && (wParam == TRUE) && (lParam == 0)) {

           … display the second dialog …

           spam_user = FALSE;

       }

       break;

  37. Igor says:

    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.

    [Why are you checking for topmost in the Z-order? You can be visible without being topmost. Indeed, if your window is not WS_EX_TOPMOST and the user is running, say, Task Manager, then you will never find yourself at the top of the Z-order! -Raymond]
  38. Miral says:

    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) :)

  39. Igor says:

    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.

  40. Miral says:

    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 ™ :)

  41. Threadder says:

    I would use my golden hammer: THREADS

    Create a thread (on WM_PAINT) which creates the second dialog window. Problem solved.

  42. Will says:

    What is the correct solution? You probably know this already.

    41 answers thus far, and clearly we don’t ;-)

  43. Maxime LABELLE says:

    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.

  44. Answer to previous exercise.

  45. Neil says:

    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?

    [There’s nothing wrong with it technically, as long as you get the window ownership right. The issue here is just that it looks ugly. -Raymond]
  46. Igor says:

    Why are you checking for topmost in the Z-order?

    Oops, I meant top, not topmost.

    [A window can be visible without being top. -Raymond]
  47. Igor says:

    A window can be visible without being top.

    Are we speaking of human-visible window or just window manager visible window?

    Because if it is the former and the window is small it may be hidden behind say task manager and then showing a dialog box won’t help to accomplish anything, because the new dialog box will still be invisible for human being working with the computer. In other words:

    g_fShown = TRUE; // g_fShown != g_fSeen;

    That is why I wanted to check for Z order too.

    [Even if the starting dialog is covered, the secondary dialog might peek out from under Task Manager. If you look behind the question to what the customer really wants, they just want the dialog to look like the user clicked on a button that calls up the second dialog. -Raymond]
  48. Igor says:

    Even if the starting dialog is covered, the

    >secondary dialog might peek out from under

    >Task Manager.

    You said that right, it might peek if:

    a) Task Manager is not larger than both

    b) User hasn’t switched to full screen

    c) Yhere is no TV/Video overlay window

    Anyway, new dialog will have Z order directly above its parent so that may be behind any other window (Solitaire, Minesweeper) currently visible to the user.

    There is another hidden issue there, new dialog may not be the one to get to the foreground when user switches back to the application (via Alt+Tab) which launched it. It happens all the time, we have to minimize windows one by one until we find that nasty dialog which prevents us from closing an application, saving file, etc.

    >they just want the dialog to look like the

    >user clicked on a button that calls up the

    >second dialog.

    They obviously never consulted any users. I hate when things start popping up at me and I bet everyone does.

    The issue at hand is simple:

    a) people want control

    b) people hate losing control

    c) people who lose control get frustrated

    d) frustrated people are less productive and more violent

    Computer doing things on its own leaves an impression that operator doesn’t have any control over it.

    For example, last thing I want is for Acrobat Reader MSI installer package to defragment my drive without asking because it does that by using crippled built-in Windows defragmenter which only messes up what my regular use of PerfectDisk has achieved.

    But wait, there is another, perfect example for this kind of annoyance — Windows’ Send To|Mail Recipient. That nasty popup covering “Send Mail” dialog which blindly asks to make all your pictures smaller even if they are small already or if you rename .zip to .jpg and try to send it that way.

    Note that there is no way to turn that brutally useless feature off so you have a trade-off.

    Either do not use Send To which alone is very usefull feature or get acustomed to dismissing another useless dialog every time you use Send To.

    Those programmers who want nested dialogs should at least make an option in application preferences to disable it or many users will really hate their guts.

    [“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.” -Raymond]
  49. Igor says:

    Personally, I think…

    Yes, I read that statement. It is just that you might have given few examples of why it counts as starting off on the wrong foot, as well as listing at least one viable alternative.

    Many programmers do bad things unintentionally. They just use the simplest possible solution to their problem which is logical because they are not paid to worry about this and that, just to write the code.

    On the other hand, if they can recognize the scenario as bad from a live example they would most likely go through some extra effort to do it the right way.

    [A different topic for a different day. I try to limit myself to one topic per day. -Raymond]
  50. peterchen says:

    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.

  51. peterchen says:

    nevermind, just noticed the new blog entry

Comments are closed.