{"id":29603,"date":"2006-09-25T10:00:02","date_gmt":"2006-09-25T10:00:02","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/09\/25\/waiting-until-the-dialog-box-is-displayed-before-doing-something\/"},"modified":"2006-09-25T10:00:02","modified_gmt":"2006-09-25T10:00:02","slug":"waiting-until-the-dialog-box-is-displayed-before-doing-something","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060925-02\/?p=29603","title":{"rendered":"Waiting until the dialog box is displayed before doing something"},"content":{"rendered":"<p>\nLast time,\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2006\/09\/22\/766168.aspx\">\nI left you with a few questions<\/a>.\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2006\/09\/22\/766168.aspx#766941\">\nPart of the answer to the first question\nwas given in the comments<\/a>, so I&#8217;ll just link to that.\nThe problem is more than just typeahead, though.\nThe dialog box doesn&#8217;t show itself until all message traffic\nhas gone idle.\nIf you actually ran the code presented in the original message,\nyou&#8217;d find that it didn&#8217;t actually work!\n<\/p>\n<pre>\n#include &lt;windows.h&gt;\nINT_PTR CALLBACK\nDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n  switch (uiMsg) {\n  case WM_INITDIALOG:\n    PostMessage(hwnd, WM_APP, 0, 0);\n    return TRUE;\n  case WM_APP:\n    MessageBox(hwnd,\n              IsWindowVisible(hwnd) ? TEXT(\"Visible\")\n                                    : TEXT(\"Not Visible\"),\n               TEXT(\"Title\"), MB_OK);\n    break;\n  case WM_CLOSE:\n   EndDialog(hwnd, 0);\n   break;\n  }\n  return FALSE;\n}\nint WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,\n                   LPSTR lpCmdLine, int nShowCmd)\n{\n    DialogBox(hinst, MAKEINTRESOURCE(1), NULL, DlgProc);\n    return 0;\n}\n<\/pre>\n<p>\nWhen you run this program, the  message box says &#8220;Not Visible&#8221;,\nand in fact when it appears, you can see that the main dialog\nis not yet visible.\nIt doesn&#8217;t show up until after you dismiss the message box.\n<\/p>\n<p>\nMission: Not accomplished.\n<\/p>\n<p>\nAlong the way, there was some dispute over whether the\nprivate message should be <code>WM_USER<\/code> or\n<code>WM_APP<\/code>.\nAs we saw before,\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/12\/02\/55914.aspx\">\nwindow messages in the <code>WM_USER<\/code> range belong to the\nwindow class<\/a>,\nand in this case, the window class is the dialog window class,\n<i>i.e.<\/i>, <code>WC_DIALOG<\/code>.\nSince you are not the implementor of the dialog window class\n(you didn&#8217;t write the window procedure),\nthe <code>WM_USER<\/code> messages are not yours for the taking.\nAnd in fact, if you had decided to use <code>WM_USER<\/code>\nyou would have run into all sorts of problems,\nbecause it so happens that the dialog manager\nalready defined that message for its own purposes:\n<\/p>\n<pre>\n#define DM_GETDEFID         (WM_USER+0)\n<\/pre>\n<p>\nWhen the dialog manager sends the dialog a <code>DM_GETDEFID<\/code>\nmessage to obtain the default control ID,\nyou will think it&#8217;s your <code>WM_USER<\/code> message and show your\ndialog box.\nIt turns out that the dialog manager uses the default control ID\nrather often, and as a result, you&#8217;re going to display an awful\nlot of dialog boxes.\n(Even worse, your second dialog box will probably use the dialog\nitself as the owner, which then leads to the problem of\nhaving a dialog box with multiple modal children<\/a>,\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2005\/02\/23\/378866.aspx\">\nwhich then leads to disaster when they are dismissed by the\nuser in the wrong order<\/a>.)\n<\/p>\n<p>\nOkay, so we&#8217;re agreed that we should use <code>WM_APP<\/code>\nas the private message.\n<\/p>\n<p>\nSome people suggested using a timer,\non the theory that timer messages are lower priority than\npaint messages,\nso the timer won&#8217;t fire until all painting is done.\nWhile that&#8217;s true, it also doesn&#8217;t help.\nThe relative priority of timer and paint messages comes\ninto play only if the window manager has to choose between\ntimers and paint messages when deciding which one to deliver\nfirst.\nBut if there are no paint messages needed in the first place,\nthen timers are free to go ahead.\n<\/p>\n<p>\nAnd when the window is not visible,\nit doesn&#8217;t need any paint messages.\nIn a sense, the timer approach misses the point entirely:\nIt&#8217;s trying to take advantage of paint messages being higher priority\nprecisely in the scenario where there are no paint messages!\n<\/p>\n<p>\nLet&#8217;s demonstrate this by implementing the timer approach,\nbut I&#8217;m going to add a twist to make the race condition clearer:\n<\/p>\n<pre>\n...\nINT_PTR CALLBACK\nDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n  switch (uiMsg) {\n  case WM_INITDIALOG:\n    SetTimer(hwnd, 1, 1, 0);\n    Sleep(100); \/\/simulate paging\n    return TRUE;\n  case WM_TIMER:\n    if (wParam == 1) {\n      KillTimer(hwnd, 1);\n      MessageBox(hwnd,\n                IsWindowVisible(hwnd) ? TEXT(\"Visible\")\n                                      : TEXT(\"Not Visible\"),\n                 TEXT(\"Title\"), MB_OK);\n    }\n    break;\n  case WM_CLOSE:\n   EndDialog(hwnd, 0);\n   break;\n  }\n  return FALSE;\n}\n<\/pre>\n<p>\nIf you run this program, you&#8217;ll see the message &#8220;Not Visible&#8221;.\nI inserted an artificial <code>Sleep(100)<\/code> to simulate\nthe case where the code takes a page fault and has to wait\n100ms for the code to arrive from the backing store.\n(Maybe it&#8217;s coming from the network or a CD-ROM,\nor maybe the local hard drive is swamped with I\/O and\nyou have to wait that long for your paging request to\nbecome satisfied after all the other I\/O requests active\non the drive.)\n<\/p>\n<p>\nAs a result of that <code>Sleep()<\/code>,\nthe dialog manager doesn&#8217;t get a chance to empty the message\nqueue and show the window because the timer message is already\nin the queue.\nResult: The timer fires and the dialog is still hidden.\n<\/p>\n<p>\nSome people waited for <code>WM_ACTIVATE<\/code>, but that\ntells you when the window becomes active, which is not the\nsame as being shown, so it doesn&#8217;t satisfy the original\nrequirements.\n<\/p>\n<p>\nOthers suggested waiting for <code>WM_PAINT<\/code>,\nbut a window can be visible without painting.\nThe <code>WM_PAINT<\/code> message arrives if the window&#8217;s\nclient area is uncovered, but the caption bar might still be\nvisible even if the client area is covered.\nFurthermore, while this addresses the problem if you interpret\n&#8220;visible&#8221; as &#8220;results in pixels on the screen&#8221;,\nas opposed to <code>IsWindowVisible<\/code>,\nyou need to look behind the actual request to what the person\nwas really looking for.\n(This is an important skill to have because people rarely ask\nfor what they want, but rather for what they think they want.)\nThe goal was to create a dialog box and have it look like the\nuser automatically clicked a button on it to call up a secondary dialog.\nIn order to get this look, the base dialog needs to be visible\nbefore the secondary dialog can be displayed.\n<\/p>\n<p>\nOne approach is to show the second dialog on receipt of the\n<code>WM_SHOWWINDOW<\/code>, but even that is too soon:\n<\/p>\n<pre>\n\/\/ In real life, this would be an instance variable\nBOOL g_fShown = FALSE;\nINT_PTR CALLBACK\nDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n  switch (uiMsg) {\n  case WM_INITDIALOG:\n    return TRUE;\n  case WM_SHOWWINDOW:\n    if (wParam &amp;&amp; !g_fShown) {\n      g_fShown = TRUE;\n      MessageBox(hwnd,\n                 IsWindowVisible(hwnd) ? TEXT(\"Visible\")\n                                       : TEXT(\"Not Visible\"),\n                 TEXT(\"Title\"), MB_OK);\n    }\n    break;\n  case WM_CLOSE:\n   EndDialog(hwnd, 0);\n   break;\n  }\n  return FALSE;\n}\n<\/pre>\n<p>\n(Subtlety: Why do I set <code>g_fShown = TRUE<\/code> before\ndisplaying the message box?)\n<\/p>\n<p>\nIf you run this program, you will still get the message\n&#8220;Not Visible&#8221; because <code>WM_SHOWWINDOW<\/code> is sent\nas part of the entire window-showing process.\nAt the time you receive it, your window is <strong>in the\nprocess of being show<\/strong> but it&#8217;s not quite there yet.\nThe <code>WM_SHOWWINDOW<\/code> serves a similar purpose\nto <code>WM_INITDIALOG<\/code>: To let you prepare the window\nwhile it&#8217;s still hidden so the user won&#8217;t see ugly flashing\nwhich would otherwise occur if you had done\nyour preparation after the window were visible.\n<\/p>\n<p>\nIs there a message that is sent after the window has been shown?\nThere sure is: <code>WM_WINDOWPOSCHANGED<\/code>.\n<\/p>\n<pre>\nINT_PTR CALLBACK\nDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n  switch (uiMsg) {\n  case WM_INITDIALOG:\n    return TRUE;\n  case WM_WINDOWPOSCHANGED:\n    if ((((WINDOWPOS*)lParam)-&gt;flags &amp; SWP_SHOWWINDOW) &amp;&amp;\n        !g_fShown) {\n      g_fShown = TRUE;\n      MessageBox(hwnd,\n                 IsWindowVisible(hwnd) ? TEXT(\"Visible\")\n                                       : TEXT(\"Not Visible\"),\n                 TEXT(\"Title\"), MB_OK);\n    }\n    break;\n  case WM_CLOSE:\n   EndDialog(hwnd, 0);\n   break;\n  }\n  return FALSE;\n}\n<\/pre>\n<p>\nThis time, we get the message &#8220;Visible&#8221;,\nbecause <code>WM_WINDOWPOSCHANGED<\/code> is sent after\nthe window positioning negotiations are complete.\n(The &#8220;ED&#8221; at the end emphasizes that it is delivered\nafter the operation has been done, as opposed to the &#8220;ING&#8221;\nwhich is delivered while the operation is in progress.)\n<\/p>\n<p>\nBut wait, we&#8217;re not out of the woods yet.\nAlthough it&#8217;s true that the window position negotiations\nare complete, the message is nevertheless sent as part\nof the whole window positioning process,\nand there may be other things that need to be done\nas part of the whole window-showing bookkeeping.\nIf you show the second dialog directly in your\n<code>WM_WINDOWPOSCHANGED<\/code> handler,\nthen that clean-up won&#8217;t happen until after the user\nexits the second dialog.\n<\/p>\n<p>\nFor example, the window manager notifies Active Accessibility\nof the completed window positioning operation after all\nthe window positions have settled down.\nThis reduces the likelihood that the accessibility tool will be told\n&#8220;Okay, the window is shown&#8221; followed by\n&#8220;Oh no wait, it moved again, ha ha!&#8221;\nIf you display the second dialog inside your\n<code>WM_WINDOWPOSCHANGED<\/code> handler,\nthe screen reader will receive a bizarro sequence of events:\n<\/p>\n<ul>\n<li>Second dialog shown.\n<li>(User interacts with second dialog and dismisses it.)\n<li>Second dialog destroyed.\n<li>(Your <code>WM_WINDOWPOSCHANGED<\/code> handler returns.)\n<li>Main dialog shown.\n<\/ul>\n<p>\nNotice that the &#8220;Main dialog shown&#8221; notification arrives out of\norder because you did additional UI work before the previous operation\nwas complete.\n<\/p>\n<p>\nAs another example, the window may have been shown as part\nof a multiple-window window positioning operation\nsuch as one created by <code>DeferWindowPos<\/code>.\nAll the affected windows will get their <code>WM_WINDOWPOSCHANGED<\/code>\nnotifications one at a time,\nand if your window happened to go first,\nthen those other windows won&#8217;t know that they were repositioned\nuntil after the user finishes with the nested dialog.\nThis may manifest itself in those other windows appearing to\nbe &#8220;stuck&#8221; since your dialog is holding up the subsequent\nnotifications with your nested dialog.\nFor example, a window might be trying to do\n<strong>exactly what you&#8217;re trying to do here<\/strong>,\nbut since you&#8217;re holding up the remainder of the notifications,\nthat other window won&#8217;t display its secondary dialog until\nthe user dismisses yours.\nFrom the user&#8217;s standpoint, that other window is &#8220;stuck&#8221;\nfor no apparent reason.\n<\/p>\n<p>\nTherefore, we need one more tweak to our solution.\n<\/p>\n<pre>\nINT_PTR CALLBACK\nDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n  switch (uiMsg) {\n  case WM_INITDIALOG:\n    return TRUE;\n  case WM_WINDOWPOSCHANGED:\n    if ((((WINDOWPOS*)lParam)-&gt;flags &amp; SWP_SHOWWINDOW) &amp;&amp;\n        !g_fShown) {\n      g_fShown = TRUE;\n      PostMessage(hwnd, WM_APP, 0, 0);\n    }\n    break;\n  case WM_APP:\n      MessageBox(hwnd,\n                 IsWindowVisible(hwnd) ? TEXT(\"Visible\")\n                                       : TEXT(\"Not Visible\"),\n                 TEXT(\"Title\"), MB_OK);\n      break;\n  case WM_CLOSE:\n   EndDialog(hwnd, 0);\n   break;\n  }\n  return FALSE;\n}\n<\/pre>\n<p>\nWhen we learn that the dialog is being shown for the first time,\nwe post a message to ourselves to display the secondary dialog\nand return from the <code>WM_WINDOWPOSCHANGED<\/code> handler.\nThis allows the window positioning operation to complete.\nEverybody gets their notifications, they are all on board\nwith the state of the windows,\nand only after everything has stabilized do we display our\nmessage box.\n<\/p>\n<p>\nThis is a common thread to many types of window management.\nMany window messages are notifications which are delivered\n<strong>while the operation is still in progress<\/strong>.\nYou do not want to display new UI while handling those\nnotifications because that holds up the completion of the\noriginal UI operation that generated the notification in the\nfirst place.\nPosting a message to yourself to complete the user interaction\nafter the original operation has stabilized is the standard solution.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last time, I left you with a few questions. Part of the answer to the first question was given in the comments, so I&#8217;ll just link to that. The problem is more than just typeahead, though. The dialog box doesn&#8217;t show itself until all message traffic has gone idle. If you actually ran the code [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-29603","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Last time, I left you with a few questions. Part of the answer to the first question was given in the comments, so I&#8217;ll just link to that. The problem is more than just typeahead, though. The dialog box doesn&#8217;t show itself until all message traffic has gone idle. If you actually ran the code [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/29603","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=29603"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/29603\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=29603"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=29603"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=29603"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}