Waiting until the dialog box is displayed before doing something


Last time, I left you with a few questions. Part of the answer to the first question was given in the comments, so I'll just link to that. The problem is more than just typeahead, though. The dialog box doesn't show itself until all message traffic has gone idle. If you actually ran the code presented in the original message, you'd find that it didn't actually work!

#include <windows.h>

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {

  case WM_INITDIALOG:
    PostMessage(hwnd, WM_APP, 0, 0);
    return TRUE;

  case WM_APP:
    MessageBox(hwnd,
              IsWindowVisible(hwnd) ? TEXT("Visible")
                                    : TEXT("Not Visible"),
               TEXT("Title"), MB_OK);
    break;

  case WM_CLOSE:
   EndDialog(hwnd, 0);
   break;
  }

  return FALSE;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    DialogBox(hinst, MAKEINTRESOURCE(1), NULL, DlgProc);
    return 0;
}

When you run this program, the message box says "Not Visible", and in fact when it appears, you can see that the main dialog is not yet visible. It doesn't show up until after you dismiss the message box.

Mission: Not accomplished.

Along the way, there was some dispute over whether the private message should be WM_USER or WM_APP. As we saw before, window messages in the WM_USER range belong to the window class, and in this case, the window class is the dialog window class, i.e., WC_DIALOG. Since you are not the implementor of the dialog window class (you didn't write the window procedure), the WM_USER messages are not yours for the taking. And in fact, if you had decided to use WM_USER you would have run into all sorts of problems, because it so happens that the dialog manager already defined that message for its own purposes:

#define DM_GETDEFID         (WM_USER+0)

When the dialog manager sends the dialog a DM_GETDEFID message to obtain the default control ID, you will think it's your WM_USER message and show your dialog box. It turns out that the dialog manager uses the default control ID rather often, and as a result, you're going to display an awful lot of dialog boxes. (Even worse, your second dialog box will probably use the dialog itself as the owner, which then leads to the problem of having a dialog box with multiple modal children, which then leads to disaster when they are dismissed by the user in the wrong order.)

Okay, so we're agreed that we should use WM_APP as the private message.

Some people suggested using a timer, on the theory that timer messages are lower priority than paint messages, so the timer won't fire until all painting is done. While that's true, it also doesn't help. The relative priority of timer and paint messages comes into play only if the window manager has to choose between timers and paint messages when deciding which one to deliver first. But if there are no paint messages needed in the first place, then timers are free to go ahead.

And when the window is not visible, it doesn't need any paint messages. In a sense, the timer approach misses the point entirely: It's trying to take advantage of paint messages being higher priority precisely in the scenario where there are no paint messages!

Let's demonstrate this by implementing the timer approach, but I'm going to add a twist to make the race condition clearer:

...

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {

  case WM_INITDIALOG:
    SetTimer(hwnd, 1, 1, 0);
    Sleep(100); //simulate paging
    return TRUE;

  case WM_TIMER:
    if (wParam == 1) {
      KillTimer(hwnd, 1);
      MessageBox(hwnd,
                IsWindowVisible(hwnd) ? TEXT("Visible")
                                      : TEXT("Not Visible"),
                 TEXT("Title"), MB_OK);
    }
    break;

  case WM_CLOSE:
   EndDialog(hwnd, 0);
   break;
  }

  return FALSE;
}

If you run this program, you'll see the message "Not Visible". I inserted an artificial Sleep(100) to simulate the case where the code takes a page fault and has to wait 100ms for the code to arrive from the backing store. (Maybe it's coming from the network or a CD-ROM, or maybe the local hard drive is swamped with I/O and you have to wait that long for your paging request to become satisfied after all the other I/O requests active on the drive.)

As a result of that Sleep(), the dialog manager doesn't get a chance to empty the message queue and show the window because the timer message is already in the queue. Result: The timer fires and the dialog is still hidden.

Some people waited for WM_ACTIVATE, but that tells you when the window becomes active, which is not the same as being shown, so it doesn't satisfy the original requirements.

Others suggested waiting for WM_PAINT, but a window can be visible without painting. The WM_PAINT message arrives if the window's client area is uncovered, but the caption bar might still be visible even if the client area is covered. Furthermore, while this addresses the problem if you interpret "visible" as "results in pixels on the screen", as opposed to IsWindowVisible, you need to look behind the actual request to what the person was really looking for. (This is an important skill to have because people rarely ask for what they want, but rather for what they think they want.) The goal was to create a dialog box and have it look like the user automatically clicked a button on it to call up a secondary dialog. In order to get this look, the base dialog needs to be visible before the secondary dialog can be displayed.

One approach is to show the second dialog on receipt of the WM_SHOWWINDOW, but even that is too soon:

// In real life, this would be an instance variable
BOOL g_fShown = FALSE;

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {

  case WM_INITDIALOG:
    return TRUE;

  case WM_SHOWWINDOW:
    if (wParam && !g_fShown) {
      g_fShown = TRUE;
      MessageBox(hwnd,
                 IsWindowVisible(hwnd) ? TEXT("Visible")
                                       : TEXT("Not Visible"),
                 TEXT("Title"), MB_OK);
    }
    break;

  case WM_CLOSE:
   EndDialog(hwnd, 0);
   break;
  }

  return FALSE;
}

(Subtlety: Why do I set g_fShown = TRUE before displaying the message box?)

If you run this program, you will still get the message "Not Visible" because WM_SHOWWINDOW is sent as part of the entire window-showing process. At the time you receive it, your window is in the process of being show but it's not quite there yet. The WM_SHOWWINDOW serves a similar purpose to WM_INITDIALOG: To let you prepare the window while it's still hidden so the user won't see ugly flashing which would otherwise occur if you had done your preparation after the window were visible.

Is there a message that is sent after the window has been shown? There sure is: WM_WINDOWPOSCHANGED.

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {

  case WM_INITDIALOG:
    return TRUE;

  case WM_WINDOWPOSCHANGED:
    if ((((WINDOWPOS*)lParam)->flags & SWP_SHOWWINDOW) &&
        !g_fShown) {
      g_fShown = TRUE;
      MessageBox(hwnd,
                 IsWindowVisible(hwnd) ? TEXT("Visible")
                                       : TEXT("Not Visible"),
                 TEXT("Title"), MB_OK);
    }
    break;

  case WM_CLOSE:
   EndDialog(hwnd, 0);
   break;
  }

  return FALSE;
}

This time, we get the message "Visible", because WM_WINDOWPOSCHANGED is sent after the window positioning negotiations are complete. (The "ED" at the end emphasizes that it is delivered after the operation has been done, as opposed to the "ING" which is delivered while the operation is in progress.)

But wait, we're not out of the woods yet. Although it's true that the window position negotiations are complete, the message is nevertheless sent as part of the whole window positioning process, and there may be other things that need to be done as part of the whole window-showing bookkeeping. If you show the second dialog directly in your WM_WINDOWPOSCHANGED handler, then that clean-up won't happen until after the user exits the second dialog.

For example, the window manager notifies Active Accessibility of the completed window positioning operation after all the window positions have settled down. This reduces the likelihood that the accessibility tool will be told "Okay, the window is shown" followed by "Oh no wait, it moved again, ha ha!" If you display the second dialog inside your WM_WINDOWPOSCHANGED handler, the screen reader will receive a bizarro sequence of events:

  • Second dialog shown.
  • (User interacts with second dialog and dismisses it.)
  • Second dialog destroyed.
  • (Your WM_WINDOWPOSCHANGED handler returns.)
  • Main dialog shown.

Notice that the "Main dialog shown" notification arrives out of order because you did additional UI work before the previous operation was complete.

As another example, the window may have been shown as part of a multiple-window window positioning operation such as one created by DeferWindowPos. All the affected windows will get their WM_WINDOWPOSCHANGED notifications one at a time, and if your window happened to go first, then those other windows won't know that they were repositioned until after the user finishes with the nested dialog. This may manifest itself in those other windows appearing to be "stuck" since your dialog is holding up the subsequent notifications with your nested dialog. For example, a window might be trying to do exactly what you're trying to do here, but since you're holding up the remainder of the notifications, that other window won't display its secondary dialog until the user dismisses yours. From the user's standpoint, that other window is "stuck" for no apparent reason.

Therefore, we need one more tweak to our solution.

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {

  case WM_INITDIALOG:
    return TRUE;

  case WM_WINDOWPOSCHANGED:
    if ((((WINDOWPOS*)lParam)->flags & SWP_SHOWWINDOW) &&
        !g_fShown) {
      g_fShown = TRUE;
      PostMessage(hwnd, WM_APP, 0, 0);
    }
    break;


  case WM_APP:
      MessageBox(hwnd,
                 IsWindowVisible(hwnd) ? TEXT("Visible")
                                       : TEXT("Not Visible"),
                 TEXT("Title"), MB_OK);
      break;

  case WM_CLOSE:
   EndDialog(hwnd, 0);
   break;
  }

  return FALSE;
}

When we learn that the dialog is being shown for the first time, we post a message to ourselves to display the secondary dialog and return from the WM_WINDOWPOSCHANGED handler. This allows the window positioning operation to complete. Everybody gets their notifications, they are all on board with the state of the windows, and only after everything has stabilized do we display our message box.

This is a common thread to many types of window management. Many window messages are notifications which are delivered while the operation is still in progress. You do not want to display new UI while handling those notifications because that holds up the completion of the original UI operation that generated the notification in the first place. Posting a message to yourself to complete the user interaction after the original operation has stabilized is the standard solution.

Comments (30)
  1. Bruce Dickinson says:

    Woweee wow wow wow – thats alot of cowbell!!

  2. BryanK says:

    (Subtlety: Why do I set g_fShown = TRUE before displaying the message box?)

    Guessing here, but:  Because during MessageBox, you may get another WM_SHOWWINDOW message?  I’m not sure how that would work, or why it would happen, but it’s the only reason that I can come up with that makes any sense.

  3. Mike Jones says:

    If WM_USER should not be used by -er- users could it please be renamed.  Or a compiler warning given.

  4. HitScan says:

    The compiler doesn’t and shouldn’t know what various #defines are and aren’t safe to use in various situations. That’s what the API documentation is for. You can use WM_USER, just not in a dialog class. (and possibly other situations)

  5. Garry Trinder says:

    (Subtlety: Why do I set g_fShown = TRUE before displaying the message box?)

    All you goota do is check the docs.  WM_SHOWWINDOW will also be sent if the window is revealed by restoring a maximized window, and other reasons.

  6. Garry Trinder says:

    > If WM_USER should not be used by -er- users could it please be renamed.

    It should have been given a differnet name originally, but changing it now will break existing code.

  7. Adrian says:

    Raymond wrote:  “Many window messages are notifications which are delivered while the operation is still in progress.”

    Could that explain this problem I described in your suggestion box?

    http://blogs.msdn.com/oldnewthing/articles/382765.aspx#382868

    I don’t see how, since it’s a posted message.  The symptom seems related to the issue you describe in this post, but the solution doesn’t seem to apply.

    [Repeating a question just makes me less likely to answer it. -Raymond]
  8. Igor says:

    So I was the closest with WM_WINDOWPOSCHANGED. Too bad I didn’t have time yesterday night to finish, but nevertheless nice to know that I was on the right track :)

  9. DanT says:

    (Subtlety: Why do I set g_fShown = TRUE before displaying the message box?)

    WM_WINDOWPOSCHANGED can be reentrant while the modal dialog is up – obvious example is while the message box is up, the user hits win-d and minimizes everything. It is also sent when the z order changes, etc.

  10. DanT says:

    Actually I think win-d only changes the zorder, so I meant to say win-m

  11. Mihai says:

    A while ago I had a related problem: validating a dialog after it is done displaying.

    This might include detecting resizing problems, double or invalid quick-keys, spell-checking, etc.

    So all controls have to be in the final state (visible or not).

    I have a solution, but the main problem are now tabbed dialogs.

    How do you know when a tab change is done? Because everything is just a collection of show/hide, no clue when is over.

    And, by the way, I am "outside" the application I am checking :-)

    (using hooks)

  12. Vipin says:

    I wanted to comment on the message pump and modal dialogs, but since that may take this particular thread on a different tangent,

    I posted that in my blog.

    For the curious, checkout:-

    http://blogs.explorewindows.com

  13. Ulric says:

    Why did you choose a postmessage in WM_WINDOWPOSCHANGED instead of a postmessage in WM_SHOWWINDOW?  Seems all the same thing if you’re set to use PostMessage.

    btw, about the early dismissal of WM_PAINT…  The "window" could be visible because it’s frame is painted already – that’s technically true, but it isn’t the effect I would have want for the user.  I would have wanted him to see the dialog, not just the frame. In WM_PAINT a PostMessage would have been used because I wouldn’t have left the window hanging in the middle of its WM_PAINT

    I did not know that WM_USER isn’t safe in dialog boxes, although I understand why, it’s totally logical.  Still … the dialog manager should have set its private messages in the system message range.  I grepped our few hundred thousans lines of code and while we use WM_USER a lot, luckily it isn’t in dialog boxes.  It was usually offsetted by a large value, like WM_USER+1000.  Thank you for reminding us of these win32 items.

  14. stefang says:

    Interesting stuff.

    I think it is quite surprising that PostMessage(WM_APP) from WM_INITDIALOG did not work.

    As far as I understand this must mean that DialogBox contains a PeekMessage loop that drains all messages before it calls ShowWindow() and falls down to the regular message loop.

    Of course, this also means that any controls in a dialog must be prepared to accept keyboard events even before the dialog is shown. I am sure that this surprises a lot of people.

    I dont think I have ever seen this behavior documented anywhere before – not even in your own Dialog box series about a year ago.

    [I did mention that dialog boxes hold off display until all typeahead is processed, and since posted messages come before input, they have to churn through all posted messages too. I admit it wasn’t explicit, but I thought everybody knew this already. Maybe the newer Win32 programming books don’t mention it any more. -Raymond]
  15. Ulric says:

    Hum, I don’t think it’s obvious that posted messages are going to be processed before the keyboard messages already in the queue.  As a windows programmer since 1994, I’m completely aware that mouse, and paint, messages are low priority, but that doesn’t necessarily equate to me that a posted message will be processed before already-queued keyboard input, and that furthermore this will all have to be processed before the dialog box is displayed.

    [This was covered as part 5 of the “Five Things Every Win32 Developer Should Know”. Posted messages and input come from different queues. -Raymond]
  16. Miral says:

    Ok, I know this isn’t really a .NET blog, but what’s the .NET equivalent of this?  OnLoad is definitely before it’s visible, and OnVisibleChanged seems like the equivalent of WM_SHOWWINDOW.

    Not that you can really post messages in .NET anyway.  Maybe we’re just screwed…

  17. Norman Diamond says:

    > If WM_USER should not be used by -er- users

    >> could it please be renamed.

    >

    > It should have been given a differnet name

    > originally, but changing it now will break

    > existing code.

    I’ll bet it would mostly break broken code.  I recommend that users not be blamed for writing broken code based on an inference that user means user, but still it is broken code that needs fixing.  Ulric’s later example of code that uses numbers like WM_USER+1000 is code that luckily avoided being broken yet, so yes a rename would be a breaking change for this poor victim, but I’ll bet a vastly larger number of other poor victims would suddenly be informed of what they have to fix.

    > Why did you choose a postmessage in

    > WM_WINDOWPOSCHANGED instead of a postmessage

    > in WM_SHOWWINDOW?  Seems all the same thing

    > if you’re set to use PostMessage.

    A message posted during WM_SHOWWINDOW will be read after finishing the computations of what to show in the window but still before showing is completed.  A message posted during WM_WINDOWPOSCHANGED will be read after showing is completed.

    I would feel more comfortable with WM_ENTERIDLE, but Windows CE doesn’t have it.

    > Maybe the newer Win32 programming books

    > don’t mention it any more.

    Richter was 1999 and Petzold was 1998, right?  Any recommendations for newer Win32 programming books?

  18. josh says:

    Hey, I knew if I guessed enough things, at least one of them would be part of the answer.  :)

    So how about WM_STYLECHANGED?

  19. stefang says:

    > Why did you choose a postmessage in

    >> WM_WINDOWPOSCHANGED instead of a postmessage

    >> in WM_SHOWWINDOW?  Seems all the same thing

    >> if you’re set to use PostMessage.

    >A message posted during WM_SHOWWINDOW will be read after finishing the computations of what to show in the window but still before showing is completed.  A message posted during WM_WINDOWPOSCHANGED will be read after showing is completed.

    >I would feel more comfortable with WM_ENTERIDLE, but Windows CE doesn’t have it.

    I see no reason why posting the message in WM_SHOWWINDOW should not work.

    The code in DialogBox probably looks something like this pseudocode:

    CreateWindow

    SendMessage(WM_INITDIALOG)

    // Dispatch messages until idle

    while PeekMessage

      anyMessagesDispatched = true

      DispatchMessage

    if anyMessagesDispatched

      SendMessage(WM_ENTERIDLE)

    ShowWindow

    While dialog is active

      GetMessage

      DispatchMessage

    Note that both WM_SHOWWINDOW and WM_WINDOWPOSCHANGED are sent from inside the ShowWindow call, so the effect of posting a message from either event should be the same.

    WM_ENTERIDLE is sent to the owner of the newly created dialogbox – not to the dialog box itself. The documentation also suggests that WM_ENTERIDLE is only sent if the peekmessage loop found any messages to dispatch.

    Unfortunately, the docuemntation for WM_ENTERIDLE does not specify if it is sent before or after ShowWindow()

    Raymond, does my pseudocode look accurate to you ?

  20. Norman Diamond says:

    Sorry for a second message box in a row, but I just noticed some source code that would benefit from editing, in winuser.h:

    #define WM_APP                          0x8000

    /*

    * NOTE: All Message Numbers below 0x0400 are RESERVED.

    *

    * Private Window Messages Start Here:

    */

    #define WM_USER                         0x0400

    May I recommend:

    #define WM_USER                         0x0400

    /*

    * NOTE: All Message Numbers below 0x0800 are RESERVED.

    *

    * Private Window Messages Start Here:

    */

    #define WM_APP                          0x8000

    [The new comment is misleading. Messages between 0x400 and 0x8000 are available for use by the class implementor; they aren’t reserved by the system. -Raymond]
  21. Norman Diamond says:

    > Messages between 0x400 and 0x8000 are

    > available for use by the class implementor;

    > they aren’t reserved by the system.

    OK, I got it.  Hmm, I misedited my previous message, changing 0x0400 to 0x0800 which should have been 0x8000, but your response still addressed it correctly and I understand.

    I retract my previous statement “but I’ll bet a vastly larger number of other poor victims would suddenly be informed of what they have to fix”.  The number of users who validly used the WM_USER range in private classes might be larger than the number of users who luckily happened to avoid conflicts with the use of the WM_USER range in dialog boxes, windows that contain standard controls, etc.

    I still think it would be helpful to add a comment though:

    * Message numbers in the range 0x0400 to 0x8000

    * can be used in private window classes, but

    * should be treated as reserved when using

    * dialog boxes or standard Windows controls.

    [But that’s just a specific version of the more general statement that messages in the 0x0400 to 0x7FFF range belong to the window class, whether that window class be the dialog box class, a standard Windows control, or a custom control you bought from another company. At any rate, comments in header files are really a bad place for discussion. -Raymond]
  22. Mike Dimmick says:

    Norman: there really aren’t any newer Windows programming books.

    It would help a lot if Richter’s books weren’t out of print! "Programming Applications for Windows" is currently selling for between $125 and $200 on Amazon Marketplace.

  23. BryanK says:

    Miral — In the non-compact framework, you can override your form’s WndProc to get at both WM_WINDOWPOSCHANGED and WM_APP.  Between that and p/invoke with PostMessage, you can do everything required.

    Yes, it’s a pain, but it should work.

    (Although I doubt that dialog boxes in .net are really dialog-box-class windows.  Maybe that doesn’t matter though.)

  24. Threadder says:

    Posting a message to yourself to complete the user interaction after the original operation has stabilized is the standard solution.

    Why should an app interact with the window system to be able to send a message to itself? As you shown, it’s clearly a source of errors.

    [I don’t understand the question. To send a window message you have to use the window system. That’s just a tautology. -Raymond]

    > If WM_USER should not be used by -er- users could it please be renamed.  Or a compiler warning given.

    And thats why you should use threading instead of message passing.

  25. David Walker says:

    Um, from a naive standpoint, what is it about the underlying tools available to the programmer that makes all of this so complicated?  

    Would better or differently architected  tools (APIs) in the OS make the job easier to understand, easier to implement, clearer, and harder to mess up?

  26. Vipin says:

    Mike,

        I have a copy of "Programming Applications for Windows".Its mainly meant to get an understanding of the operating system from a application programmer(win32) perspective. Nice book to have on the shelf. You won’t get to read much on GUI(user32/gdi) in that book.

  27. BryanK says:

    Um, from a naive standpoint, what is it about the underlying tools available to the programmer that makes all of this so complicated?

    It’s two things.  First, people want to do some action after their window is shown.  Second, there is no “your window has completed showing itself” message sent by the system.

    [Um, WM_WINDOWPOSCHANGED is the message that says “Here’s what happened (past tense) to your window. It’s all over. Now I’m just telling everybody about it..” -Raymond]
  28. Threadder says:

    [I don’t understand the question. To send a window message you have to use the window system. That’s just a tautology. -Raymond]

    Any message doesn’t necessary have to be a window message. An app could (on appropritate event – WM_WINDOWPOSCHANGED) send an internally “syncronisation message” to another thread that shows the second dialog window.

    [Then I guess I don’t understand the question. You asked, “Why should an app interact with the window system to be able to send a message to itself?” and then here you gave an example of not doing it. Oh, I see, the “should” here was not in the sense of “Why am I forced to do it this way” but rather “Why are you recommending it?” Well, for one thing, cross-thread UI is extremely difficult to get right. -Raymond]
  29. BryanK says:

    > [Um, WM_WINDOWPOSCHANGED is the message that says "Here’s what happened (past tense) to your window. It’s all over. Now I’m just telling everybody about it.." -Raymond]

    Right.  I guess I meant that the name of the message constant doesn’t make it very obvious that it’s what you should use to trigger stuff that needs to happen after the window is shown.

    The docs for WM_WINDOWPOSCHANGED don’t really (explicitly) mention this, either.  That’s why I’m not all that surprised that people can’t find the appropriate message to trigger "after-window-show" behavior from.  They search MSDN for some string they think is appropriate, and nothing comes back.

    Now, I’m not sure the docs can even be changed.  Certainly it’d be a bad idea to try to explicitly document the state of the window in each message (it would help, but it would be a *huge* undertaking).  Perhaps a new message — WM_WINDOWSHOWN or something — would help make it obvious what to watch for, but that would probably require a new version of Windows (i.e., it likely won’t be added to Vista, let alone XP or 2K).

    Yes, WM_WINDOWPOSCHANGED is equivalent to this WM_WINDOWSHOWN (as far as the window’s state is concerned), and I know that now.  But if I didn’t know that, I’d have no idea what message to use.

Comments are closed.