{"id":36273,"date":"2005-03-04T07:00:00","date_gmt":"2005-03-04T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2005\/03\/04\/modality-part-8-a-timed-messagebox-the-better-version\/"},"modified":"2005-03-04T07:00:00","modified_gmt":"2005-03-04T07:00:00","slug":"modality-part-8-a-timed-messagebox-the-better-version","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050304-00\/?p=36273","title":{"rendered":"Modality, part 8: A timed MessageBox, the better version"},"content":{"rendered":"<p>\n<a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2005\/03\/01\/382380.aspx\">\nA few days ago, we saw a simple version of a timed message box\nwhich had a limitation that it could be used from only one thread\nat a time<\/a>.\nToday we&#8217;ll work to remove that limitation.\n<\/p>\n<p>\nAs you may recall, the reason why it could be used from only one thread\nat a time was that we kept the &#8220;Did the message box time out?&#8221; flag\nin a global.  To fix it, we will move the flag to a per-instance location,\nnamely a helper window.\n<\/p>\n<p>\nStart with\n<a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nthe scratch program<\/a>,\nadd the code for\n<a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2005\/03\/02\/383562.aspx\">\nthe scratch window class<\/a>,\n<i>change the name of the scratch window class<\/i> so it doesn&#8217;t\nconflict with the class name of the scratch program\n(<a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2005\/03\/02\/383562.aspx#385251\">thanks to reader Adrian for pointing this out<\/a>),\nthen add the following:\n<\/p>\n<pre>\n#define IDT_TOOLATE     1\ntypedef struct TOOLATEINFO {\n BOOL fTimedOut;\n HWND hwndReenable;\n} TOOLATEINFO;\nvoid CALLBACK\nMsgBoxTooLateProc(HWND hwnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime)\n{\n  TOOLATEINFO *ptli = reinterpret_cast&lt;TOOLATEINFO*&gt;(\n    GetWindowLongPtr(hwnd, GWLP_USERDATA));\n  if (ptli) {\n    ptli-&gt;fTimedOut = TRUE;\n    if (ptli-&gt;hwndReenable) {\n       EnableWindow(ptli-&gt;hwndReenable, TRUE);\n    }\n    PostQuitMessage(42);\n  }\n}\nint TimedMessageBox(HWND hwndOwner, LPCTSTR ptszText,\n    LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout)\n{\n  TOOLATEINFO tli;\n  tli.fTimedOut = FALSE;\n  BOOL fWasEnabled = hwndOwner &amp;&amp; IsWindowEnabled(hwndOwner);\n  tli.hwndReenable = fWasEnabled ? hwndOwner : NULL;\n  HWND hwndScratch = CreateScratchWindow(hwndOwner, DefWindowProc);\n  if (hwndScratch) {\n      SetWindowLongPtr(hwndScratch, GWLP_USERDATA,\n                       reinterpret_cast&lt;LPARAM&gt;(&amp;tli));\n      SetTimer(hwndScratch, IDT_TOOLATE, dwTimeout, MsgBoxTooLateProc);\n  }\n  int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType);\n  if (hwndScratch) {\n    KillTimer(hwndScratch, IDT_TOOLATE);\n    if (tli.fTimedOut) { \/\/ We timed out\n      MSG msg;\n      \/\/ Eat the fake WM_QUIT message we generated\n      PeekMessage(&amp;msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);\n      iResult = -1;\n    }\n    DestroyWindow(hwndScratch);\n  }\n  return iResult;\n}\nvoid OnChar(HWND hwnd, TCHAR ch, int cRepeat)\n{\n  switch (ch) {\n  case ' ':\n    TimedMessageBox(hwnd, TEXT(\"text\"), TEXT(\"caption\"),\n                    MB_OK, 2000);\n    break;\n  }\n}\n\/\/ add to WndProc\n    HANDLE_MSG(hwnd, WM_CHAR, OnChar);\n\/\/ add to InitApp\n    RegisterScratchWindowClass();\n<\/pre>\n<p>\nThis is basically the same as the previous cheap version,\njust with slightly different bookkeeping.\n<\/p>\n<p>\nThe state of the timed message box is kept in the structure\n<code>TOOLATEINFO<\/code>.  But how to pass this state to the\ntimer callback?  You can&#8217;t pass any parameters to timer callbacks.\n<\/p>\n<p>\nAha, but timer callbacks do get a window handle.\nBut as we discovered a few days ago, we can&#8217;t just hang the callback\noff the <code>hwndOwner<\/code> window because we don&#8217;t know how\nto pick a timer ID that doesn&#8217;t conflict with an existing one.\n<\/p>\n<p>\nThe solution: Hang it on a window of our own window creation.\nThat way, we get a whole new space of timer IDs to play in,\nseparate from the timer IDs that belong to <code>hwndOwner<\/code>.\nThe scratch window is a convenient window to use.\nWe don&#8217;t pass an interesting window procedure to\n<code>CreateScratchWindow<\/code> because there is no need;\nall we wanted was a window to own our timer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A few days ago, we saw a simple version of a timed message box which had a limitation that it could be used from only one thread at a time. Today we&#8217;ll work to remove that limitation. As you may recall, the reason why it could be used from only one thread at a time [&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,143],"class_list":["post-36273","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code","tag-modality"],"acf":[],"blog_post_summary":"<p>A few days ago, we saw a simple version of a timed message box which had a limitation that it could be used from only one thread at a time. Today we&#8217;ll work to remove that limitation. As you may recall, the reason why it could be used from only one thread at a time [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36273","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=36273"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36273\/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=36273"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=36273"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=36273"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}