{"id":92761,"date":"2016-01-01T07:00:00","date_gmt":"2016-01-01T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=92761"},"modified":"2019-03-13T10:28:27","modified_gmt":"2019-03-13T17:28:27","slug":"20160101-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20160101-00\/?p=92761","title":{"rendered":"If you want to receive a message that is broadcast to top-level windows, you need a top-level window"},"content":{"rendered":"<p>A customer wanted to suppress autorun from a wizard page. They started with <a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb776825.aspx\">this page on MSDN<\/a> but found that their wizard page did not receive the <code>Query&shy;Cancel&shy;Auto&shy;Play<\/code> message. <\/p>\n<pre>\ndefault: \n  if (g_uQueryCancelAutoPlay == 0) { \n    g_uQueryCancelAutoPlay = \n      RegisterWindowMessage(TEXT(\"QueryCancelAutoPlay\"));\n  } \n  if (uMsg &amp;&amp; uMsg == g_uQueryCancelAutoPlay) { \n    SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE);          \n    return TRUE;    \n  }\n  break;\n<\/pre>\n<p>The customer reported that their dialog procedure never received the <code>Query&shy;Cancel&shy;Auto&shy;Play<\/code> message. They even called <code>Change&shy;Window&shy;Message&shy;Filter&shy;Ex<\/code> to explicitly allow the <code>g_uQuery&shy;Cancel&shy;Auto&shy;Play<\/code> message to be received, but that didn&#8217;t help. <\/p>\n<p>The original code had other issues which distracted me from the actual problem. The source of the problem is hidden  in their opening statement: They are trying to receive this message <i>in a wizard<\/i>, which means that this dialog procedure does not correspond to a top-level window. It is a child dialog inside the wizard. <\/p>\n<p>Since broadcast messages go only to top-level windows, you need to have a top-level window that can receive the message. Creating your own top-level window will not work in this case, because <code>Query&shy;Cancel&shy;Auto&shy;Play<\/code> is sent only to the foreground window. Therefore, you will have to subclass your existing top-level window so you can snoop in on the messages. You can find the top-level window by calling <code>Get&shy;Ancestor(hwnd, GA_ROOT)<\/code>, and you can use <code>Set&shy;Window&shy;Subclass<\/code> to intercept the messages. <\/p>\n<p>You need to be careful about this, because you don&#8217;t want to reject autoplay unconditionally. You should reject autoplay only when your page is the active page. It would be bad if the &#8220;Please insert the DVD&#8221; page suppressed autorun even before the user reached that page, or after the used moved beyond that page all the way to &#8220;Congratulations. Everything is ready to go.&#8221; <\/p>\n<p>Therefore, you need to listen for the <code>PSN_SET&shy;ACTIVE<\/code> and <code>PSN_KILL&shy;ACTIVE<\/code> notifications. When your page becomes active, you subclass the root window; when it is no longer active, you remove the subclass. Alternatively, you can set and clear a flag, and have the subclass function check the flag to see whether it should respond to the message or let it go through. <\/p>\n<p>Okay, let&#8217;s do it. First, let&#8217;s have a three-page wizard that doesn&#8217;t try to do anything fancy with autoplay. <\/p>\n<pre>\n#include &lt;windows.h&gt;\n#include &lt;commctrl.h&gt;\n\nHINSTANCE g_hinst;\n\n\/\/ For demonstration purposes only. In real life, of course,\n\/\/ you would check if the correct DVD is inserted.\n\nbool IsCorrectDVD()\n{\n  SYSTEMTIME st;\n  GetSystemTime(&amp;st);\n  return st.wMinute % 2 == 0;\n}\n\nINT_PTR CALLBACK WelcomeDlgProc(\n    HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)\n{\n  switch (message) {\n  case WM_NOTIFY:\n    {\n      LPNMHDR pnmh = (LPNMHDR)lParam;\n      switch (pnmh-&gt;code) {\n      case PSN_SETACTIVE:\n        PropSheet_SetWizButtons(pnmh-&gt;hwndFrom, PSWIZB_NEXT);\n        return TRUE;\n      }\n    }\n    break;\n  }\n  return FALSE;\n}\n\nINT_PTR CALLBACK InsertDlgProc(\n    HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)\n{\n  switch (message) {\n  case WM_NOTIFY:\n    {\n      LPNMHDR pnmh = (LPNMHDR)lParam;\n      switch (pnmh-&gt;code) {\n      case PSN_SETACTIVE:\n        PropSheet_SetWizButtons(pnmh-&gt;hwndFrom, PSWIZB_BACK | PSWIZB_NEXT);\n        return TRUE;\n      case PSN_WIZNEXT:\n        if (!IsCorrectDVD()) {\n           MessageBox(hdlg, TEXT(\"Please insert the correct DVD.\"),\n                      TEXT(\"Error\"), MB_OK);\n           SetWindowLongPtr(hdlg, DWLP_MSGRESULT, -1);\n           return TRUE;\n        }\n        break;\n      }\n    }\n    break;\n  }\n  return FALSE;\n}\n\nINT_PTR CALLBACK FinishedDlgProc(\n    HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)\n{\n  switch (message) {\n  case WM_NOTIFY:\n    {\n      LPNMHDR pnmh = (LPNMHDR)lParam;\n      switch (pnmh-&gt;code) {\n      case PSN_SETACTIVE:\n        PropSheet_SetWizButtons(pnmh-&gt;hwndFrom, PSWIZB_FINISH);\n        return TRUE;\n      }\n    }\n    break;\n  }\n  return FALSE;\n}\n\nHPROPSHEETPAGE CreateWizardPage(PCTSTR dialogTemplate, DLGPROC dlgProc)\n{\n  PROPSHEETPAGE psp = { sizeof(psp) };\n  psp.hInstance = g_hinst;\n  psp.lParam = 0;\n  psp.dwFlags = PSP_DEFAULT;\n  psp.pszTemplate = dialogTemplate;\n  psp.pfnDlgProc = dlgProc;\n  return CreatePropertySheetPage(&amp;psp);\n}\n\nint WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,\n                   LPSTR lpCmdLine, int nShowCmd)\n{\n  HPROPSHEETPAGE pages[3] = {\n    CreateWizardPage(MAKEINTRESOURCE(1), WelcomeDlgProc),\n    CreateWizardPage(MAKEINTRESOURCE(2), InsertDlgProc),\n    CreateWizardPage(MAKEINTRESOURCE(3), FinishedDlgProc),\n  };\n\n  PROPSHEETHEADER psh = { sizeof(psh) };\n  psh.hInstance = hinst;\n  psh.dwFlags = PSH_WIZARD;\n  psh.pszCaption = TEXT(\"Awesome Wizard\");\n  psh.phpage = pages;\n  psh.nPages = 3;\n  PropertySheet(&amp;psh);\n  return 0;\n}\n\n\/\/ scratch.rc\n#include &lt;windows.h&gt;\n#include &lt;commctrl.h&gt;\n\n1 DIALOGEX 0, 0, WIZ_CXDLG, WIZ_CYDLG\nSTYLE DS_SHELLFONT | WS_CHILD\nFONT 8, \"MS Shell Dlg 2\"\nBEGIN\n    LTEXT \"Welcome.\", -1, 0, 8, 216, 38\nEND\n\n2 DIALOGEX 0, 0, WIZ_CXDLG, WIZ_CYDLG\nSTYLE DS_SHELLFONT | WS_CHILD\nFONT 8, \"MS Shell Dlg 2\"\nBEGIN\n    LTEXT \"Insert DVD.\", -1, 0, 8, 216, 38\nEND\n\n3 DIALOGEX 0, 0, WIZ_CXDLG, WIZ_CYDLG\nSTYLE DS_SHELLFONT | WS_CHILD\nFONT 8, \"MS Shell Dlg 2\"\nBEGIN\n    LTEXT \"Finished.\", -1, 0, 8, 216, 38\nEND\n<\/pre>\n<p>So far, so boring. The Welcome page enables the Next button; the Insert page has both Back and Next buttons; the Finished page has a Finish button. If the user clicks Next on the Insert page, but the correct DVD is not inserted, then display an error message and stay on the page. <\/p>\n<p>Okay, now the problem: If the user inserts the DVD when instructed, it will autoplay, so let&#8217;s suppress autoplay while the Insert page is displayed. <\/p>\n<pre>\n<font COLOR=\"blue\">LRESULT CALLBACK MainWindowSubclassProc(\n    HWND hwnd,\n    UINT message,\n    WPARAM wParam,\n    LPARAM lParam,\n    UINT_PTR uIdSubclass,\n    DWORD_PTR dwRefData)\n{\n  static UINT wmQueryCancelAutoPlay;\n\n  if (wmQueryCancelAutoPlay == 0) {\n    wmQueryCancelAutoPlay =\n      RegisterWindowMessage(TEXT(\"QueryCancelAutoPlay\"));\n  }\n\n  if (message &amp;&amp; message == wmQueryCancelAutoPlay) {\n    HWND hwndPropSheet = (HWND)dwRefData;\n    PropSheet_PressButton(hwndPropSheet, PSBTN_NEXT);\n    return TRUE;\n  }\n  return DefSubclassProc(hwnd, message, wParam, lParam);\n}<\/font>\n\nINT_PTR CALLBACK InsertDlgProc(\n    HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)\n{\n  <font COLOR=\"blue\">HWND hwndRoot;<\/font>\n  switch (message) {\n  case WM_NOTIFY:\n    {\n      LPNMHDR pnmh = (LPNMHDR)lParam;\n      switch (pnmh-&gt;code) {\n      case PSN_SETACTIVE:\n        PropSheet_SetWizButtons(pnmh-&gt;hwndFrom, PSWIZB_BACK | PSWIZB_NEXT);\n        <font COLOR=\"blue\">hwndRoot = GetAncestor(hdlg, GA_ROOT);\n        SetWindowSubclass(hwndRoot, MainWindowSubclassProc, 0, (DWORD_PTR)pnmh-&gt;hwndFrom);\n        SetWindowLongPtr(hdlg, DWLP_USER, (LPARAM)hwndRoot);<\/font>\n        return TRUE;\n      case PSN_WIZNEXT:\n        if (!IsCorrectDVD()) {\n           MessageBox(hdlg, TEXT(\"Please insert the correct DVD.\"),\n                      TEXT(\"Error\"), MB_OK);\n           SetWindowLongPtr(hdlg, DWLP_MSGRESULT, -1);\n           return TRUE;\n        }\n        break;\n      <font COLOR=\"blue\">case PSN_KILLACTIVE:\n        hwndRoot = (HWND)SetWindowLongPtr(hdlg, DWLP_USER, 0);\n        RemoveWindowSubclass(hwndRoot, MainWindowSubclassProc, 0);<\/font>\n        break;\n      }\n    }\n    break;\n  }\n  return FALSE;\n}\n<\/pre>\n<p>Our subclass procedure receives the property sheet window as its reference data. It checks whether the message is <code>Query&shy;Cancel&shy;Auto&shy;Play<\/code>. If so, then it presses the Next button in the property sheet and returns <code>TRUE<\/code> to cancel the autoplay. <\/p>\n<p><b>Exercise<\/b>: I thought you returned a value from a dialog procedure by using <code>Set&shy;Window&shy;Long&shy;Ptr<\/code> with <code>DWLP_MSG&shy;RESULT<\/code>, but here, we&#8217;re just returning <code>TRUE<\/code> directly. What&#8217;s going on? <\/p>\n<p>The dialog procedure for the page installs the subclass when the page is activated, and it removes the subclass when the page is deactivated. The window to be subclassed is the root window for the wizard. We save that in our <code>DWLP_USER<\/code> so we know which window to unsubclass when we lose activation. Note also that we pass the property sheet as the reference data for <code>Set&shy;Window&shy;Subclass<\/code>, so that the subclass procedure has the correct window handle for the <code>Prop&shy;Sheet_Press&shy;Button<\/code> macro. <\/p>\n<p>By the way, as a courtesy, the property sheet manager forwards the following messages to all pages: <\/p>\n<ul>\n<li><code>WM_DISPLAY&shy;CHANGE<\/code> \n<li><code>WM_SETTINGS&shy;CHANGE<\/code> \n<li><code>WM_SYS&shy;COLOR&shy;CHANGE<\/code> <\/ul>\n<p>And it forwards the following messages to the active page: <\/p>\n<ul>\n<li><code>WM_ACTIVATE&shy;APP<\/code> \n<li><code>WM_ACTIVATE<\/code> \n<li><code>WM_DEVICE&shy;CHANGE<\/code> \n<li><code>WM_ENABLE<\/code> \n<li><code>WM_END&shy;SESSION<\/code> \n<li><code>WM_PALETTE&shy;CHANGED<\/code> (but only if the     <code>wParam<\/code> is not the property sheet itself,     to     <a HREF=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/dd145214(v=vs.85).aspx\">    avoid an infinite palette realization loop<\/a>) \n<li><code>WM_QUERY&shy;END&shy;SESSION<\/code> \n<li><code>WM_QUERY&shy;NEW&shy;PALETTE<\/code> <\/ul>\n<p>But for other messages (like <code>Query&shy;Cancel&shy;Auto&shy;Play<\/code>), you&#8217;re on your own. <\/p>\n<p>Armed with this knowledge, you can answer this question, which as it happens, arrived eight months later from the same customer! <\/p>\n<blockquote CLASS=\"q\"><p>We have a scenario where we need some pages of a wizard to know that a <code>WM_POWER&shy;BROADCAST<\/code> message has arrived. What is the best way to pass this message to the wizard pages? Also, how would we ensure that this message only gets forwarded to the page that is currently active? <\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Stands to reason.<\/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-92761","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Stands to reason.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/92761","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=92761"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/92761\/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=92761"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=92761"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=92761"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}