• The Old New Thing

    A timed context menu

    • 25 Comments

    This is sort of in the same spirit as our previous exercise in writing a timed message box, but this is much easier. Here, we use the handy-dandy WM_CANCELMODE message to get us out of menu mode.

    void CALLBACK
    MenuTooLateProc(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
    {
      SendMessage(hwnd, WM_CANCELMODE, 0, 0);
    }
    
    BOOL
    TimedTrackPopupMenuEx(HMENU hMenu, UINT uFlags, int x, int y,
        HWND hwnd, LPTPMPARAMS pTpm, DWORD dwTimeout)
    {
        UINT idTimer = SetTimer(NULL, IDT_TOOLATE, dwTimeout, MenuTooLateProc);
        BOOL fResult = TrackPopupMenuEx(hMenu, uFlags, x, y, hwnd, pTpm);
        if (idTimer) KillTimer(NULL, idTimer);
        return fResult;
    }
    

    Before displaying the menu, we set a timer. (And we use a thread timer because we don't own the hwnd window and therefore don't know what timer IDs are safe to use.) If the timer fires, we send ourselves a WM_CANCELMODE message to cancel menu mode. This causes the system to act as if the user had dismissed the menu without selecting anything, either by hitting ESC or clicking outside the menu. The call to the TrackPopupMenuEx function returns once the user has selected something (or the timeout has elapsed), at which point we clean up by destroying our timer before returning.

  • The Old New Thing

    Modality, part 8: A timed MessageBox, the better version

    • 16 Comments

    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'll work to remove that limitation.

    As you may recall, the reason why it could be used from only one thread at a time was that we kept the "Did the message box time out?" flag in a global. To fix it, we will move the flag to a per-instance location, namely a helper window.

    Start with the scratch program, add the code for the scratch window class, change the name of the scratch window class so it doesn't conflict with the class name of the scratch program (thanks to reader Adrian for pointing this out), then add the following:

    #define IDT_TOOLATE     1
    
    typedef struct TOOLATEINFO {
     BOOL fTimedOut;
     HWND hwndReenable;
    } TOOLATEINFO;
    
    void CALLBACK
    MsgBoxTooLateProc(HWND hwnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime)
    {
      TOOLATEINFO *ptli = reinterpret_cast<TOOLATEINFO*>(
        GetWindowLongPtr(hwnd, GWLP_USERDATA));
      if (ptli) {
        ptli->fTimedOut = TRUE;
        if (ptli->hwndReenable) {
           EnableWindow(ptli->hwndReenable, TRUE);
        }
        PostQuitMessage(42);
      }
    }
    
    int TimedMessageBox(HWND hwndOwner, LPCTSTR ptszText,
        LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout)
    {
      TOOLATEINFO tli;
      tli.fTimedOut = FALSE;
      BOOL fWasEnabled = hwndOwner && IsWindowEnabled(hwndOwner);
      tli.hwndReenable = fWasEnabled ? hwndOwner : NULL;
    
      HWND hwndScratch = CreateScratchWindow(hwndOwner, DefWindowProc);
      if (hwndScratch) {
          SetWindowLongPtr(hwndScratch, GWLP_USERDATA,
                           reinterpret_cast<LPARAM>(&tli));
          SetTimer(hwndScratch, IDT_TOOLATE, dwTimeout, MsgBoxTooLateProc);
      }
      int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType);
      if (hwndScratch) {
        KillTimer(hwndScratch, IDT_TOOLATE);
        if (tli.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;
        }
        DestroyWindow(hwndScratch);
      }
      return iResult;
    }
    
    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        TimedMessageBox(hwnd, TEXT("text"), TEXT("caption"),
                        MB_OK, 2000);
        break;
      }
    }
    
    // add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    
    // add to InitApp
        RegisterScratchWindowClass();
    

    This is basically the same as the previous cheap version, just with slightly different bookkeeping.

    The state of the timed message box is kept in the structure TOOLATEINFO. But how to pass this state to the timer callback? You can't pass any parameters to timer callbacks.

    Aha, but timer callbacks do get a window handle. But as we discovered a few days ago, we can't just hang the callback off the hwndOwner window because we don't know how to pick a timer ID that doesn't conflict with an existing one.

    The solution: Hang it on a window of our own window creation. That way, we get a whole new space of timer IDs to play in, separate from the timer IDs that belong to hwndOwner. The scratch window is a convenient window to use. We don't pass an interesting window procedure to CreateScratchWindow because there is no need; all we wanted was a window to own our timer.

  • The Old New Thing

    Raymond's random walk, from Swedish designers to Mr. Monkey

    • 9 Comments

    My random walk began at Svenska Dagbladet and the article Svensk designer upprör skottar, about Swedish designer Johanna Larson whose T-shirt depicting the traffic-cone-wearing Duke of Wellington has sparked a debate in the city.

    The article mentions that the tradition of placing a traffic cone on the head of the Duke is twenty years old, and my search for further information on this ongoing prank led me to Richard Leyton's musings on whether the traffic cone should be glued in place permanently.

    At the end of his entry, he links to the wonderful web site of Mr Monkey and in particular Mr Monkey's visit to the statue to see it for himself.

    The Mr Monkey web site is one of the hidden treasures of the Internet. Sure, it may not be entirely original, but Mr Monkey himself is so goofy-looking, the pictures are for the most part quite well-composed, and the subtitles are the icing on the cake.

  • The Old New Thing

    The bonus window bytes at GWLP_USERDATA

    • 35 Comments

    The window manager provides a pointer-sized chunk of storage you can access via the GWLP_USERDATA constant. You pass it to the GetWindowLongPtr function and the SetWindowLongPtr function to read and write that value. Most of the time, all you need to attach to a window is a single pointer value anyway, so the free memory in GWLP_USERDATA is all you need.

    Note that this value, like the other window extra bytes and the messages in the WM_USER range, belongs to the window class and not to the code that creates the window.

  • The Old New Thing

    Curling proves itself much more exciting than hockey this year

    • 14 Comments

    NPR reports on an amazing last-rock shot at the Canadian women's curling championship (includes link to CBC video). It's odd hearing Melissa Block discussing curling in decidedly non-curling terms (for the benefit of the decidedly non-curling US listenership). One of the advantages of living in the Seattle area is that you can get all the curling coverage you want from the CBC just over the border.

    The National Post has a report loaded with curling jargon, for those who prefer their curling coverage filled with discussion of draw weight, stolen deuces, and in-turns.

  • 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?

Page 374 of 441 (4,408 items) «372373374375376»