• The Old New Thing

    The scratch window

    • 14 Comments

    Sometimes you need a quick and dirty window and you don't want to go through all the hassle of registering a class for it. For example, you might need a window to do a brief snippet of DDE, or you just need a window to own a message box.

    To save yourself the trouble of registering a class for every single weenie thing you might need a window for, you can get lazy and register a single "scratch window" class and simply subclass it on an as-needed basis.

    ATOM RegisterScratchWindowClass(void)
    {
      WNDCLASS wc = {
            0,                              // style
            DefWindowProc,                  // lpfnWndProc
            0,                              // cbClsExtra
            0,                              // cbWndExtra
            g_hinst,                        // this file's HINSTANCE
            NULL,                           // hIcon
            LoadCursor(NULL, IDC_ARROW),    // hCursor
            (HBRUSH)(COLOR_BTNFACE+1),      // hbrBackground
            NULL,                           // lpszMenuName
            TEXT("Scratch"),                // lpszClassName
      };
    
      return RegisterClass(&wc);
    }
    
    HWND
    CreateScratchWindow(HWND hwndParent, WNDPROC wp)
    {
      HWND hwnd;
      hwnd = CreateWindow(TEXT("Scratch"), NULL,
                          hwndParent ? WS_CHILD : WS_OVERLAPPED,
    	              0, 0, 0, 0, hwndParent, NULL, NULL, NULL);
      if (hwnd) {
        SubclassWindow(hwnd, wp);
      }
      return hwnd;
    }
    

    Now if you need a quick one-off window, you can just create a scratch window instead of creating a custom window class just to handle that specific task.

    We'll see the scratch window in action soon.

  • The Old New Thing

    Suggestion Box 2

    • 191 Comments

    Post suggestions for future topics here instead of posting off-topic comments. Note that the suggestion box is emptied and read periodically so don't be surprised if your suggestion vanishes. (Note also that I am under no obligation to accept any suggestion.)

    Topics I are more inclined to cover:

    • Windows history (particularly the Windows 95 era).
    • Windows user interface programming in Win32, and shell programming in particular.
    • General programming topics (selectively).
    • Issues of general interest.
    • My personal hobbies.

    Topics I am not inclined to cover:

    • The blog software itself.  You can send feedback about .Text to its author, Scott Watermasysk.
    • Internet Explorer. You can try the IE folks.
    • Visual Studio.  You can try one of the Visual Studio blogs.
    • Managed code. This is not a .NET blog. I do not work on .NET technologies. As far as .NET is concerned, I'm just another programmer like you. Occasionally I touch a .NET-related topic, but I do not bring any expertise to the subject.
    • Non-software Microsoft topics, such as product support policies, marketing tactics, and hiring policy.
    • Microsoft software that isn't Windows. (Exchange, Office, ...)
    • Windows topics outside user interface programming. (Plug and Play, Terminal Services, Windows Messenger, Outlook Express, SQL, IIS, remoting, SOA...)
    • User interface programming in anything other than Win32. (Because I know nothing about it.)
    • Debugging a specific problem. (Not of general interest.)
    • Legal issues.
    • Predictions for the future. (What's the title of this blog again?)

    (Due to the way the blog server is set up, a new suggestion box gets set up every 30 days, assuming I don't forget to create a new one. If I forget, you can send me a reminder via the Contact page. You can also peek at the previous suggestion box.)

  • The Old New Thing

    Modality, part 7: A timed MessageBox, the cheap version

    • 34 Comments

    As we noted at the end of part 3, now that you know the conventions surrounding the WM_QUIT message you can put them to your advantage.

    The more robust you want the TimedMessageBox function to be, the more work you need to do. Here's the cheap version, based on the sample in the Knowledge Base, but with some additional bug fixes.

    static BOOL s_fTimedOut;
    static HWND s_hwndMBOwnerEnable;
    
    void CALLBACK
    CheapMsgBoxTooLateProc(HWND hWnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime)
    {
        s_fTimedOut = TRUE;
        if (s_hwndMBOwnerEnable) EnableWindow(s_hwndMBOwnerEnable, TRUE);
        PostQuitMessage(42); // value not important
    }
    
    // Warning! Not thread-safe! See discussion.
    int CheapTimedMessageBox(HWND hwndOwner, LPCTSTR ptszText,
        LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout)
    {
        s_fTimedOut = FALSE;
        s_hwndMBOwnerEnable = NULL;
        if (hwndOwner && IsWindowEnabled(hwndOwner)) {
          s_hwndMBOwnerEnable = hwndOwner;
        }
        UINT idTimer = SetTimer(NULL, 0, dwTimeout, CheapMsgBoxTooLateProc);
        int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType);
        if (idTimer) KillTimer(NULL, idTimer);
        if (s_fTimedOut) {			// We timed out
    	MSG msg;
    	// Eat the fake WM_QUIT message we generated
    	PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
    	iResult = -1;
        }
        return iResult;
    }
    

    This CheapTimedMessageBox function acts just like the MessageBox function, except that if the user doesn't respond within dwTimeout milliseconds, we return -1. The limitation is that only one timed message box can be active at a time. If your program is single-threaded, this is not a serious limitation, but if your program is multi-threaded, this will be a problem.

    Do you see how it works?

    The global static variable s_fTimedOut tells us whether we generated a fake WM_QUIT message as a result of a timeout. When the MessageBox function returns, and we indeed timed out, we use the PeekMessage function to remove the fake WM_QUIT message from the queue before returning.

    Note that we remove the WM_QUIT message only if we were the ones who generated it. In this way, WM_QUIT messages generated by other parts of the program remain in the queue for processing by the main message loop.

    Note also that when we decide that the timeout has occurred, we re-enable the original owner window before we cause the message box to bail out of its message loop by posting a quit message. Those are the rules for the correct order for disabling and enabling windows.

    Note also that we used a thread timer rather than a window timer. That's because we don't own the window being passed in and therefore don't know what timer IDs are safe to use. Any timer ID we pick might happen to collide with a timer ID being used by that window, resulting in erratic behavior.

    Recall that when you pass NULL as the hwnd parameter to the SetTimer function and also pass zero as the nIDEvent parameter, then the SetTimer function creates a brand new timer, assigns it a unique ID, and returns the ID. Most people, when they read that part of the specification for SetTimer, scratch their heads and ask themselves, "Why would anybody want to use this?"

    Well, this is one scenario where this is exactly what you want.

    Next comes the job of making the function a tad more robust. But before we do that, we'll need two quick sidebars.

  • The Old New Thing

    Modality, part 6: Interacting with a program that has gone modal

    • 14 Comments

    Earlier we saw the importance of setting the right owner window for modal UI. It is also important, when manipulating a window, to respect its modality. For example, consider the program we ended up with last time, the one which calls the MessageBox function to display a modal dialog. If we wanted to get that program to exit and sent a WM_CLOSE message to the main window instead of its modal popup, the main window would likely exit and leave the message box stranded, resulting in the same stack trace without support we saw in part 4.

    Respect the modality of a window. If it is disabled, don't try to get it to do things; it's disabled because it doesn't want to do anything right now. You can go hunting for its modal pop-up and talk to that pop-up. (Unless, of course, that pop-up is itself disabled, in which case you get to keep on hunting.)

  • The Old New Thing

    "Support our troops" wristbands provide only 1/3 support

    • 43 Comments

    Over at Slate, Timothy Noah notes how much money from so-called "awareness bracelets" actually go to the causes they purport to champion.

    In summary,

    Bracelet Vendor Cause Portion that
    benefits cause
    Yellow "Livestrong" Lance Armstrong Foundation supporting cancer survivors all
    Cinnamon Tsunami Relief Red Cross, UNICEF, CARE 1/2
    Camo-green 7-11 USO 1/3
    Light blue Awareness Depot Tsunami disaster relief
    (unspecified charity)
    1/10
    Yellow Awareness Depot Support our troops 0

    I find it ironic that the "Support our troops" bracelet doesn't actually support our troops. And is yellow really the right color to use to demonstrate support for the military?

  • The Old New Thing

    Two brief reminiscences on the Windows XP "Comments?" button

    • 35 Comments

    In beta versions of Windows XP, there was special code in the window manager to give every window a link in the upper right corner called "Comments?" which if clicked on displayed a dialog that allowed you to submit feedback to Microsoft about that window.

    Since this was a beta release, there was no anonymity when you submitted feedback. (You signed away your anonymity when you agreed to the special beta license agreement and typed in your beta ID number.) Yet we got more than one feedback submission that begin, "Hi, I pirated this copy of Windows XP, and here's some feedback."

    In its initial incarnation, the word in the title bar was "Lame", but people with a weaker sense of humor changed it to the less confrontational "Comments?". The name "Lame" came from a recurring sketch on local comedy show Almost Live! called "The Lame List, or What's Weak This Week (brought to you with the help of Seattle's Heavy Metal community)".

  • The Old New Thing

    Modality, part 5: Setting the correct owner for modal UI

    • 10 Comments

    Here is the very simple fix for the buggy program we presented last time.

    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        MessageBox(hwnd, TEXT("Message"), TEXT("Title"), MB_OK);
        if (!IsWindow(hwnd)) MessageBeep(-1);
        break;
      }
    }
    

    We have fixed the problem by passing the correct owner window for the modal UI. Since MessageBox is modal, it disables the owner while the modal UI is being displayed, thereby preventing the user from destroying or changing the owner window's state when it is not expecting it.

    This is why all the shell functions that can potentially display UI accept a window handle as one of its parameters. They need to know which window to use as the owner for any necessary UI dialogs. If you call such functions from a thread that is hosting UI, you must pass the handle to the window you want the shell to use as the UI owner. If you pass NULL (or worse, GetDesktopWindow), you may find yourself in the same bad state that our buggy sample program demonstrated.

    If you are displaying a modal dialog from another modal dialog, it is important to pass the correct window as the owner for the second dialog. Specifically, you need to pass the modal dialog initiating the sub-dialog and not the original frame window. Here's a stack diagram illustrating:

     MainWindow
      DialogBox(hwndOwner = main window) [dialog 1]
       ... dialog manager ...
        DlgProc
         DialogBox(hwndOwner = dialog 1) [dialog 2]
    

    If you mess up and pass the main window handle when creating the second modal dialog, you will find yourself back in a situation analogous to what we had last time: The user can dismiss the first dialog while the second dialog is up, leaving its stack frames orphaned.

  • The Old New Thing

    Modality, part 4: The importance of setting the correct owner for modal UI

    • 19 Comments

    If you decide to display some modal UI, it is important that you set the correct owner for that UI. If you fail to heed this rule, you will find yourself chasing some very strange bugs.

    Let's return to our scratch program and intentionally introduce a bug related to incorrect owner windows, so that we can see the consequences.

    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        // Wrong!
        MessageBox(NULL, TEXT("Message"), TEXT("Title"), MB_OK);
        if (!IsWindow(hwnd)) MessageBeep(-1);
        break;
      }
    }
    
    // Add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    

    Run this program, press the space bar, and instead of dismissing the message box, click the "X" button in the corner of the main window. Notice that you get a beep before the program exits.

    What happened?

    The beep is coming from our call to the MessageBeep function, which in turn is telling us that our window handle is no longer valid. In a real program which kept its state in per-window instance variables (instead of in globals like we do), you would more likely crash because all the instance variables would have gone away when the window was destroyed. In this case, the window was destroyed while inside a nested modal loop. As a result, when control returned to the caller, it is now a method running inside an object that has been destroyed. Any access to an instance variable is going to access memory that was already freed, resulting in memory corruption or an outright crash.

    Here's an explanation in a call stack diagram:

     WinMain
      DispatchMessage(hwnd, WM_CHAR)
       OnChar
        MessageBox(NULL)
         ... modal dialog loop ...
         DispatchMessage(hwnd, WM_CLOSE)
          DestroyWindow(hwnd)
           WndProc(WM_DESTROY)
            ... clean up the window ...
    

    When you clean up the window, you typically destroy all the data structures associated with the window. But notice that you are freeing data structures that are still being used by the OnChar handler deeper in the stack. Eventually, control unwinds back to the OnChar, which is now running with an invalid instance pointer. (If you believe in C++ objects, you would find that its "this" pointer has gone invalid.)

    This was caused by failing to set the correct owner for the modal MessageBox call, allowing the user to interact with the frame window at a time when the frame window isn't expecting to have its state changed.

    Even more problematic, the user can switch back to the frame window and hit the space bar again. The result: Another message box. Repeat another time and you end up with a stack that looks like this:

     WinMain
      DispatchMessage(hwnd, WM_CHAR)
       OnChar
        MessageBox(NULL)
         ... modal dialog loop ...
         DispatchMessage(hwnd, WM_CHAR)
           OnChar
    	MessageBox(NULL)
    	 ... modal dialog loop ...
    	 DispatchMessage(hwnd, WM_CHAR)
    	   OnChar
    	    MessageBox(NULL)
    	     ... modal dialog loop ...
    

    There are now four top-level windows, all active. If the user dismisses them in any order other than the reverse order in which they were created, you're going to have a problem on your hands. For example, if the user dismisses the second message box first, the part of the stack corresponding to that nesting level will end up returning to a destroyed window when the third message box is finally dismissed.

    The fix is simple, and we'll pick up there next time.

  • The Old New Thing

    Modality, part 3: The WM_QUIT message

    • 23 Comments

    After our two quick introductions to modality, we're now going to dig in a little deeper.

    The trick with modality is that when you call a modal function, the responsibility of message dispatch is handled by that function rather than by your main program. Consequently, if you have customized your main program's message pump, those customizations are lost once you lose control to a modal loop.

    The other important thing about modality is that a WM_QUIT message always breaks the modal loop. Remember this in your own modal loops! If ever you call the PeekMessage function or The [typo fixed 10:30am] GetMessage function and get a WM_QUIT message, you must not only exit your modal loop, but you must also re-generate the WM_QUIT message (via the PostQuitMessage message) so the next outer layer will see the WM_QUIT message and do its cleanup as well. If you fail to propagate the message, the next outer layer will not know that it needs to quit, and the program will seem to "get stuck" in its shutdown code, forcing the user to terminate the process the hard way.

    In a later series, we'll see how this convention surrounding the WM_QUIT message is useful. But for now, here's the basic idea of how your modal loops should re-post the quit message to the next outer layer.

    BOOL WaitForSomething(void)
    {
      MSG msg;
      BOOL fResult = TRUE; // assume it worked
      while (!SomethingFinished()) {
        if (GetMessage(&msg, NULL, 0, 0)) {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        } else {
          // We received a WM_QUIT message; bail out!
          CancelSomething();
          // Re-post the message that we retrieved
          PostQuitMessage(msg.wParam);
          fResult = FALSE; // quit before something finished
          break;
        }
      }
      return fResult;
    }
    

    Suppose your program starts some operation and then calls WaitForSomething(). While waiting for something to finish, some other part of your program decides that it's time to exit. (Perhaps the user clicked on a "Quit" button.) That other part of the program will call PostQuitMessage(wParam) to indicate that the message loop should terminate.

    The posted quit message will first be retrieved by the GetMessage in the WaitForSomething function. The GetMessage function returns FALSE if the retrieved message is a WM_QUIT message. In that case, the "else" branch of the conditional is taken, which cancels the "Something" operation in progress, then posts the quit message back into the message queue for the next outer message loop to handle.

    When WaitForSomething returns, control presumably will fall back out into the program's main message pump. The main message pump will then retrieve the WM_QUIT message and do its exit processing before finally exiting the program.

    And if there were additional layers of modality between WaitForSomething and the program's main message pump, each of those layers would retrieve the WM_QUIT message, do their cleanup, and then re-post the WM_QUIT message (again, via PostQuitMessage) before exiting the loop.

    In this manner, the WM_QUIT message gets handed from modal loop to modal loop, until it reaches the outermost loop, which terminates the program.

    "But wait," I hear you say. "Why do I have to do all this fancy WM_QUIT footwork? I could just have a private little global variable named something like g_fQuitting. When I want the program to quit, I just set this variable, and all of my modal loops check this variable and exit prematurely if it is set. Something like this:

    BOOL MyWaitForSomething(void) // code in italics is wrong
    {
      MSG msg;
      while (!SomethingFinished()) {
        if (g_fQuitting) {
        CancelSomething();
          return FALSE;
        }
        if (GetMessage(&msg, NULL, 0, 0)) {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
      }
      return TRUE;
    }
    

    And so I can solve the problem of the nested quit without needing to do all this PostQuitMessage rigamarole."

    And you'd be right, if you controlled every single modal loop in your program.

    But you don't.

    For example, when you call the DialogBox function, the dialog box code runs its own private modal loop to do the dialog box UI until you get around to calling the EndDialog function. And whenever the user clicks on any of your menus, Windows runs its own private modal loop to do the menu UI. Indeed, even the resizing of your application's window is handled by a Windows modal loop.

    Windows, of course, has no knowledge of your little g_fQuitting variable, so it has no idea that you want to quit. It is the WM_QUIT message that serves this purpose of co-ordinating the intention to quit among separate parts of the system.

    Notice that this convention regarding the WM_QUIT message cuts both ways. You can use this convention to cause modal loops to exit (we'll see more of this later), but it also obliges you to respect this convention so that other components (including the window manager itself) can get your modal loops to exit.

  • The Old New Thing

    Modality, part 2: Code-modality vs UI-modality

    • 19 Comments
    Last time, we saw an example of code that was UI-modal but not code-modal. The opposite is also true: You can have code-modality without UI-modality. In fact, this is far more common than the UI-modal-but-not-code-modal scenario.

    You encounter modal loops without a visible change in UI state when you drag the scroll bar thumb, drag the window caption, display a pop-up menu, or initiate an OLE drag/drop operation, among other places. Any time a nested message loop is constructed, you have code modality.

    One example is given in MSDN, where the MsgWaitForMultipleObjects function is used to construct a message loop without any modal UI. (We discussed some of the gotchas of the MsgWaitForMultipleObjects function in an earlier entry.)

Page 372 of 439 (4,383 items) «370371372373374»