How do I cancel autoplay from a wizard page?


A customer wanted to suppress autoplay from their wizard. They looked at the documentation and followed the dialog procedure example in their own wizard page dialog procedure:

// ... in the dialog procedure ...
  default:
    if (g_uQueryCancelAutoPlay == 0) {
      g_uQueryCancelAutoPlay =
        RegisterWindowMessage(TEXT("QueryCancelAutoPlay"));
      if (ChangeWindowMessageFilterEx(hwndDlg,
                                      g_uQueryCancelAutoPlay,
                                      MSGFLT_ALLOW, 
                                      NULL) == FALSE) {
        MessageBox(NULL, L"ChangeWindowMessageFilterEx failed",
                   L"error", MB_OK);
      }
    } 
    if (uMsg && uMsg == g_uQueryCancelAutoPlay) { 
      SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE);          
      return TRUE;
    }

However, the code didn't seem to work. The Query­Cancel­Auto­Play message was never received. What's wrong with the code? It was copied almost directly from MSDN!

Well, one of the things that's wrong is that the code doesn't change the window message filter until after it receives the message, which creates a bit of a chicken-and-egg problem: You don't change the filter until after you get the message, but you won't get the message until you change the filter! That's easy to fix: Initialize the g_uQuery­Cancel­Auto­Play variable and change the message filter immediately after creating the dialog box. That allows the Query­Cancel­Auto­Play message to come through when the system generates it.

But even with that fix, the message won't get through.

What's wrong with the code is that wizard dialog pages are not top-level windows. The Query­Cancel­Auto­Play message is sent to the foreground window, but wizard pages are child dialogs inside the outer wizard frame and therefore can never be the foreground window. Activation and foreground are a properties of top-level windows.

Since the Query­Cancel­Auto­Play message is sent to the foreground window, you need to listen for the message on the foreground window. The standard way of doing this is subclassing, and the cleanest way to do this is with a function like Set­Window­Subclass:

LRESULT CALLBACK CancelAutoPlaySubclassProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
    UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
  if (uMsg == g_uQueryCancelAutoPlay) {
    return TRUE;
  }
  return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

// when you want to start blocking autoplay
SetWindowSubclass(hwndDlg, CancelAutoPlaySubclassProc, 0, 0);

// when you want to stop blocking autoplay
RemoveWindowSubclass(hwndDlg, CancelAutoPlaySubclassProc, 0);

I discussed the Set­Window­Subclass family of functions some time ago.

There is a fine point here: You want to set the subclass only when you want to start blocking autoplay, and remove it when you want to allow it again. And you should block autoplay only when your page is the active page in the wizard. In other words, the earliest you should block autoplay is in your PSN_SET­ACTIVE notification handler, and the latest you should remove the block is in your PSN_KILL­ACTIVE notification handler.

If you mess up and block autoplay when your page is not the active page, then you will be blocking autoplay even when there is nothing in the wizard that needs to block autoplay. Consider:

  • Page 1: "Welcome to the XYZ Wizard."
  • Page 2: "Insert the CD."
  • Page 3: "Please wait while we copy stuff from the CD."
  • Page 4: "All done. This completes the XYZ Wizard."

Autoplay should be disabled only on page 2, because that's the only place the user expects autoplay to be blocked. When the user is on the welcome page, they haven't started anything yet, and inserting a CD should start playing music (if that's how the user configured the system). Similarly, when the user is on the "All done" page, the wizard is basically finished, and inserting a CD at that point should also start playing music.

Armed with this knowledge, you can answer this question from a customer:

We have a scenario where we need a child page of an AeroWizard to know that a WM_POWER­BROADCAST message has arrived. What is the best way to get this message from the main window procedure to the child pages of the wizard? Also, how would I ensure that this message only gets forwarded to the page that is currently active?

Bonus chatter: I didn't realize it at the time the question is asked, but writing up this article just now, I noticed that the customer who asked the above question about the WM_POWER­BROADCAST message is the exact same customer who asked the original question about the Query­Cancel­Auto­Play message! The questions were eight months apart.

Comments (13)
  1. Medinoc says:

    I guess it could also be possible to set the subclass for the whole duration of the wizard, but determine whether to suppress Autoplay dynamically during the message's processing. I wonder which would be the "safest".

    1. skSdnW says:

      If there are 3rd-party components involved then it would be safer to keep the subclass for the lifetime of the window. If some other code just used SetWindowLongPtr to subclass then the list of subclassers maintained by *WindowSubclass can get out of sync and somebody might get unwillingly desubclassed...

      1. Zarat says:

        > If some other code just used SetWindowLongPtr to subclass then the list of subclassers maintained by *WindowSubclass can get out of sync <

        In contrary, thats the whole point of using SetWindowSubclass, so it can properly manage the "chain of subclasses" when multiple subclasses exist and descublass in unexpected orders. (It does the whole complicated mess of desubclassing when you are not the first window proc so you don't have to turn your window proc into a no-op until the time comes where you can properly remove the subclass, at latest during window destruction. Since most people probably don't get that scenario right anyways SetWindowSubclass takes over that management for you by wrapping your window proc.)

        At least thats how I understand it.

        1. Medinoc says:

          I think skSdnW's point is that SetWindowSubclass() relies on all subclassers using it. If some third-party code use SetWindowLongPtr() instead, you may lose the safety guarantee.

          1. Zarat says:

            > I think skSdnW’s point is that SetWindowSubclass() relies on all subclassers using it. If some third-party code use SetWindowLongPtr() instead, you may lose the safety guarantee. <

            Thats what I was saying. Where do you (or skSdnW) take that information from? It's not document that SetWindowSubclasses requires all subclassers using it. As I was saying the whole point of that function (as far as I know) is to implement safe subclassing, so making the assumption that everyone has to use it kinda would defeat the purpose because then nobody could use it except when it is trivial, and in the trivial case you don't need it in the first place.

        2. skSdnW says:

          I'll admit that I have not inspected these functions in years but they started off as ordinal-only undocumented functions. They are implemented in comctl32 and not user32, that is your clue that the window manager is not in on the game. They exist as a convenience to you so you don't have to figure out how to store your data that is connected to the HWND. Is it nice that they try to unwind correctly, yeah sure but it was not bulletproof the last time I looked...

          1. This is discussed in the linked entry. "RemoveWindowSubclass does all the work to do the right thing if you are not the window procedure at the top of the chain."

          2. skSdnW says:

            @Raymond: Yes RemoveWindowSubclass is able to tell when the internal SetWindowSubclass WNDPROC is not the current GWLP_WNDPROC but SetWindowSubclass is not able to deal with the reverse situation:

            g_oldWndProc = SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR) SubOld);
            SetWindowSubclass(hWnd, SubCC6, something, whatever);
            ...
            SetWindowLongPtr(hWnd, GWLP_WNDPROC, g_oldWndProc);

            ...when GWLP_WNDPROC gets restored to g_oldWndProc then SubCC6 will no longer receive messages even though it was never explicitly removed. My original comment is correct, you should not mix and match SetWindowLongPtr and SetWindowSubclass.

  2. Mike Caron says:

    "Insert the CD."

    Old New Thing indeed!

  3. henke37 says:

    I feel as if this is a repeat.

  4. DWalker07 says:

    Was the MSDN documentation wrong, or just easy to misinterpret or misuse?

    1. Scarlet Manuka says:

      The example doesn't play games with message filters, so that part is on the customer.
      For the other part, the documentation does state (at least now, but it's talking about Windows 95 so I expect it hasn't been updated too recently): "Your application's window must be in the foreground to receive this message." The customer probably did not realise that their wizard page was not the foreground window. This part is likely on the customer too -- you certainly can't expect the AutoPlay documentation to mention it; I would assume that this is properly pointed out in the documentation on wizards, but I haven't checked.

  5. JDG says:

    My gut instinct would be to create a new, invisible top-level window to receive those types of messages. It feels wrong -- a violation of segregation of concerns -- to start fiddling with the configuration of whatever window might be hosting my wizard/property page. Using my own window avoids any issue of incompatible interaction between my code and other code that might also make the (in my opinion) poor decision to reconfigure its host's implementation details (with the exception of a potential very rude component that enumerates all windows and fiddles with them indiscriminately).

    Are there any serious downsides to creating a dedicated window for the purpose, rather than redirecting the WndProc of someone else's window?

Comments are closed.

Skip to main content