August, 2005

  • The Old New Thing

    The dangers of messing with activation when handling a WM_ACTIVATE message

    • 5 Comments
    This is basically the same thing as The dangers of playing focus games when handling a WM_KILLFOCUS message, just with activation in place of focus. One developer discovered the hard way that if you mess with activation inside your WM_ACTIVATE handler, things get weird. The author noted that if he posted a message and did the work from the posted message, then everything came out okay.

    A follow-up to the original message noted that passing the SWP_NOACTIVATE flag to the SetWindowPos function solves the problem. Do you understand why?

  • The Old New Thing

    Hey, who flattened the hills?

    • 4 Comments

    This weekend, I went to a friend's house on Mercer Island to see the US Navy Blue Angels flight demonstration team perform. They do this every summer as part of Seattle's Seafair festival, but this was the first time I got to see the show up close and personal. The I-90 bridge is closed for safety reasons, and people are allowed to walk onto the highway (at least for a short distance) to get a better view of the demonstration.

    Two summers ago my friend invited me to his place, but I made the mistake of trying to drive to Mercer Island—traffic was horrific and I finally arrived just as the show was wrapping up. (I did manage to glimpse a plane pass by at very low altitude though.) This year I decided to ride my bicycle to his house. It's just an hour's ride and I don't have to worry about traffic! It seems that somebody flattened the hills along the bike path that follows highway I-90. Okay maybe it's just that I'm in better shape now but I like to think it's because they were nice enough to flatten the hills.

  • The Old New Thing

    The dangers of playing focus games when handling a WM_KILLFOCUS message

    • 8 Comments

    I had noted last year that WM_KILLFOCUS is the wrong time to do field validation. Here's another example of how messing with the focus during a WM_KILLFOCUS message can create confusion.

    Consider an edit control that displays feedback via a balloon tip. For example, password edit controls often warn you if you're typing your password while CapsLock is in effect. One of the things you probably want to do is to remove the balloon tip if the user moves focus to another control, since there's no point telling the user about a problem with something they aren't using. You might be tempted to subclass the edit control and do something like this:

    LRESULT CALLBACK EditSubclass(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      switch (uMsg) {
      ...
      case WM_KILLFOCUS:
        if (hwndBalloonTip) {
          DestroyWindow(hwndBalloonTip);
          hwndBalloonTip = NULL;
        }
        break;
      ...
      }
      return CallWindowProc(prevWndProc, hwnd, uMsg, wParam, lParam);
    }
    

    When you give this code a shot, it works great... unless the user clicks on the balloon tip itself the edit control's caret (the blinking insertion point thingie) disappears. What happened?

    What happened is that you gummed up the focus change process by destroying the window that focus was going to! The focus change process goes like this:

    • Put focus on new focus window.
    • Send WM_KILLFOCUS to old focus window (if any).
    • Send WM_SETFOCUS to new focus window (if any).

    But in the second step, we destroyed the new focus window. When the focus window is destroyed, the window manager tries to find a new focus window, and it settles upon the edit control itself. This starts a recursive focus change cycle, telling the edit control that it now has focus again.

    Let's look at the flow in this nested focus change scenario when the user clicks on the tooltip window.

    • Put focus on tooltip.
    • Send WM_KILLFOCUS to edit control.
      • EditSubclass destroys the tooltip.
        • Window manager puts focus on the edit control.
        • Nobody to send WM_KILLFOCUS to.
        • Send WM_SETFOCUS to edit control.
          • EditSubclass passes WM_SETFOCUS to the original window procedure.
      • EditSubclass passes WM_KILLFOCUS to the original window procedure.
    • Send WM_SETFOCUS to tooltip - fails (tooltip was destroyed).

    Do you see the problem yet?

    Look at the message traffic as it reaches the original edit control window procedure:

    • WM_SETFOCUS (from the nested focus change)
    • WM_KILLFOCUS (from the original focus change)

    As far as the edit control is concerned, it gained focus then lost it. Therefore, no caret, since the edit control displays a caret only when it has focus, and your recursive focus changing has resulted in the edit control thinking it doesn't have focus even though it does.

    There are many ways out of this mess.

    First, notice that you don't need to subclass the edit control; you can just react to the EN_KILLFOCUS notification. Second, you can respond to the EN_KILLFOCUS by posting yourself a message and destroying the tooltip on receipt of that posted message. By doing it via a posted message, you avoid the recursive focus change since your work is now being done outside a focus change cycle.

  • The Old New Thing

    Have you made any assignments in this space?

    • 35 Comments

    Riffing on Larry's profound distaste for the use of the word 'ask' as a noun (a distaste I share)...

    It's been three years since I heard the question "Have you made any assignments in this space?" during a meeting and I still don't know what the person was trying to say.

  • The Old New Thing

    Why does the Internet Explorer animated logo arrange its frame vertically?

    • 17 Comments

    If you ever tried to build a custom animated logo for Internet Explorer, you cetainly noticed that the frames of the animation are arranged vertically rather than horizontally. Why is that?

    Because it's much more efficient.

    Recall that bitmaps are stored as a series of rows of pixels. In other words, if you number the pixels of a bitmap like this:

    123
    456
    789
    

    then the pixels are stored in memory in the order 123456789. (Note: I'm assuming a top-down bitmap, but the same principle applies to bottom-up bitmaps.) Now observe what happens if you store your animation strip horizontally:

    12
    AB
    34
    CD
    56
    EF
    78
    GH

    These pixels are stored in memory in the order 12345678ABCDEFGH. To draw the first frame requires pixels 1, 2, A and B. The second frame takes 3, 4, C, and D. And so on. Observe that the pixels required for each frame are not contiguous in memory. This means that they occupy different cache lines at least, and for a bitmap of any significant size, they also span multiple memory pages.

    Now consider a vertically-arranged animation strip:

    12
    34
    56
    78
    AB
    CD
    EF
    GH

    Again, the pixels are stored in memory in the order 12345678ABCDEFGH, [typo fixed, 15 Aug] but this time, the pixels of the first frame are 1, 2, 3 and 4; the second frame consists of 5, 6, 7, and 8; and so on. This time, all the pixels for a single frame are adjacent in memory. This means that they can be packed into a small number of cache lines, and reading the pixels for a single image will not force you to jump across multiple pages.

    Let's illustrate with some pictures: Let's say that the large animation is a series of twelve 38x38 frames, for a total bitmap dimension of 38x456. Let's assume further, for the sake of example, that it's a 32bpp bitmap and that the page size is 4KB.

    If the bitmap were stored as a horizontal strip (456x38), then the memory layout would look like this, where I've color-coded each memory page.

    Observe that no matter which frame you draw, you will have to touch every single page since each frame containes a few bytes from each page.

    Storing the bitmap vertically, on the other hand, arranges the pixels like so:

    Notice that with the vertical strip, each frame touches only two or three pages; compare the horizontal strip, where each frame touches seventeen pages. This is quite a savings especially when you realize that most of the time, the only frame being drawn is the first one. The other frames are used only during animation. In other words, this simple change trimmed 60KB out of the normal working set.

  • The Old New Thing

    On speaking a particular language in the presence of non-speakers

    • 30 Comments

    Having grown up in a household where I didn't speak most of the languages my parents and their friends use, I'm quite accustomed to being surrounded by conversations in a language I have no chance of understanding. If people are more comfortable speaking in a particular language, I say let them.

    Some people object to this on the grounds that "They might be talking about me." Guess what: They almost certainly aren't. It may hurt your ego to learn this, but it's the truth: You're really not that fascinating to other people. It turns out that over 99.99% of all conversations in the world do not involve you to any degree whatsoever. People speak other languages; get over it.

    While we were stopped waiting for bicycle repair assistance on the way to the Company Picnic, my friend called to inform a new acquaintance who was going to meet us at the picnic that we were delayed. Since they both speak French natively and I wasn't involved in the conversation, they naturally used French.

    Later, at the picnic, my friend's girlfriend remarked in Japanese what a hot day it was. (She's not Japanese herself but has been studying it for a while.) I dug deep into my brain to pull out what little Japanese remains there and managed to produce a few words of enthusiastic agreement. At which point my friend interjected, "No speaking languages I don't understand!"

    I wasn't quite sure what to make of that remark, especially since he was speaking French in the presence of non-French speakers just a while ago.

    (By the way, a few butt pictures are now up. That's my butt in the first picture and my friend's butt in the second.)

  • The Old New Thing

    Double-clicking radio buttons

    • 9 Comments

    A subtlety that adds a level of polish to your dialogs is supporting double-clicked radio buttons as an abbreviation for "select + OK". (Or "select + Next" or "select + Finish" if the page is part of a wizard.)

    Consider the following dialog template and associated dialog procedure:

    1 DIALOGEX DISCARDABLE  32, 32, 200, 76
    STYLE DS_MODALFRAME |  WS_POPUP |
          WS_VISIBLE | WS_CAPTION | WS_SYSMENU
    CAPTION "Sample"
    FONT 8, "MS Shell Dlg"
    BEGIN
     LTEXT "A mumbler is needed.",-1,7,8,100,10
     AUTORADIOBUTTON "Do not &obtain a mumber now",
                     100,17,24,180,10
     AUTORADIOBUTTON "Obtain a mumbler auto&matically",
                     101,17,34,180,10
     AUTORADIOBUTTON "&Enter mumbler manually",
                     102,17,44,180,10
     DEFPUSHBUTTON "OK",IDOK,92,58,50,14
     PUSHBUTTON "Cancel",IDCANCEL,146,58,50,14
    END
    
    INT_PTR CALLBACK DlgProc(HWND hdlg, UINT uMsg,
                             WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
     case WM_INITDIALOG:
      CheckRadioButton(hdlg, 100, 102, 100);
      return TRUE;
     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
      case IDOK:
       for (int i = 100; i <= 102; i++) {
        if (IsDlgButtonChecked(hdlg, i)) EndDialog(hdlg, i);
       }
       break;
      case IDCANCEL:
       EndDialog(hdlg, -1);
       break;
      }
     }
     return FALSE;
    }
    

    This is pretty standard unexciting dialog box that asks the user to select an option from a list. Notice that double-clicking the radio button doesn't do anything special. We can fix that.

    1 DIALOGEX DISCARDABLE  32, 32, 200, 76
    STYLE DS_MODALFRAME |  WS_POPUP |
          WS_VISIBLE | WS_CAPTION | WS_SYSMENU
    CAPTION "Sample"
    FONT 8, "MS Shell Dlg"
    BEGIN
     LTEXT "A mumbler is needed.",-1,7,8,100,10
     AUTORADIOBUTTON "Do not &obtain a mumber now",
                     100,17,24,180,10,BS_NOTIFY
     AUTORADIOBUTTON "Obtain a mumbler auto&matically",
                     101,17,34,180,10,BS_NOTIFY
     AUTORADIOBUTTON "&Enter mumbler manually",
                     102,17,44,180,10,BS_NOTIFY
     DEFPUSHBUTTON "OK",IDOK,92,58,50,14
     PUSHBUTTON "Cancel",IDCANCEL,146,58,50,14
    END
    
    INT_PTR CALLBACK DlgProc(HWND hdlg, UINT uMsg,
                             WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
     case WM_INITDIALOG:
      CheckRadioButton(hdlg, 100, 102, 100);
      return TRUE;
     case WM_COMMAND:
      switch (GET_WM_COMMAND_ID(wParam, lParam)) {
      case IDOK:
       for (int i = 100; i <= 102; i++) {
        if (IsDlgButtonChecked(hdlg, i)) EndDialog(hdlg, i);
       }
       break;
      case IDCANCEL:
       EndDialog(hdlg, -1);
       break;
      case 100:
      case 101:
      case 102:
       if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_DBLCLK) {
        EndDialog(hdlg, GET_WM_COMMAND_ID(wParam, lParam));
       }
       break;
      }
     }
     return FALSE;
    }
    

    We added the BS_NOTIFY style to the radio buttons, which enables the extended notifications (everything other than BN_CLICKED). When we receive a WM_COMMAND message for a radio button specifying that the operation was BN_DBLCLK, we automatically click the OK button. (For a wizard, we would automatically click the Next or Finish button, as appropriate.)

    Note that double-click as a shortcut for select-and-OK should be used only for dialogs or wizard pages where the only relevant controls are radio buttons (and the OK and Cancel buttons). If there are controls on the page other than the radio button, then you have to wait for the OK because the user might want to manipulate those other controls too.

    This "double-click means select-and-OK" shortcut can also be used if the only thing on the dialog is a list box or list view from with the user is being asked to select one item. If the user double-clicks an item from the list, treat it as select-and-OK.

    The dialog boxes in Explorer are rather inconsistent in their support for double-clicked radio buttons. The Office and Money teams do a much better job.

  • The Old New Thing

    Watch out, there are Swedes in the hallway!

    • 10 Comments

    Whenever I go to IKEA Seattle, I try to speak Swedish with the people who work there, but it never works.

    Yesterday, I was heading to a colleague's office to drop off Symphony tickets. (A group of friends wanted tickets together, so the order was placed in my name, and I needed to distribute them to the people in the group.) As I neared his office, I noticed a crowd of people outside and realized that they were speaking Swedish. I tried to follow what they were saying, but of course since they were speaking among themselves, they weren't taking very much effort to enunciate clearly enough for a beginner to follow.

    The solution, therefore, is to change the subject.

    I said hello and told them I had been studying Swedish for two years. One of them asked me a question that I couldn't understand, so I apologized for my bad Swedish, but they insisted it was "jättebra" (great). How they could conclude that from one misunderstood question I'm not sure. They asked me simpler questions like whether I had ever lived in Sweden (no), and why I started studying it in the first place.

    After a brief chat, I excused myself to deliver the tickets. Afterwards, I realized that I forgot to ask them whether they were Microsoft employees or whether they were visiting (and if visiting, from what company).

  • The Old New Thing

    Drawing a monochrome bitmap with transparency

    • 6 Comments
    Last time, I left you with a brief puzzle. Here are two approaches. I am not a GDI expert, so there may be even better solutions out there. To emphasize the transparency, I'll change the window background color to the application workspace color.

     BOOL WinRegisterClass(WNDCLASS *pwc)
     {
      pwc->hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
      return __super::WinRegisterClass(pwc);
     }
    

    Method 1: A big MaskBlt.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     HDC hdcMem = CreateCompatibleDC(pps->hdc);
     if (hdcMem) {
      int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
      int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
      HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
      if (hbmMono) {
       HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
       if (hbmPrev) {
        RECT rc = { 0, 0, cxCheck, cyCheck };
        DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
        COLORREF clrTextPrev = SetTextColor(pps->hdc,
                                         GetSysColor(COLOR_MENUTEXT));
        // COLORREF clrBkPrev = SetBkColor(pps->hdc,
        //                                  GetSysColor(COLOR_MENU));
        MaskBlt(pps->hdc, 0, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, hbmMono, 0, 0
               MAKEROP4(0x00AA0029, SRCCOPY));
        // SetBkColor(pps->hdc, clrBkPrev);
        SetTextColor(pps->hdc, clrTextPrev);
        SelectBitmap(hdcMem, hbmPrev);
       }
       DeleteObject(hbmMono);
      }
      DeleteDC(hdcMem);
     }
    }
    

    This has the least amount of typing but feels like overkill to me, using a quaternary raster operation as if were a ternary, just because I didn't want to create a pattern brush. (The raster operation 0x00AA0029 is the NOP operator; it leaves the destination alone. I didn't have this memorized; I looked it up in the documentation.) The MAKEROP4 says that for each white pixel in the mask, do nothing (NOP), and for each black pixel, do a SRCCOPY.

    Notice that the background color is never used (since it's supposed to be transparent); consequently, we can delete the code that sets and restores the DC's background color.

    Method 2: The traditional two-step.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     HDC hdcMem = CreateCompatibleDC(pps->hdc);
     if (hdcMem) {
      int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
      int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
      HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
      if (hbmMono) {
       HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
       if (hbmPrev) {
        RECT rc = { 0, 0, cxCheck, cyCheck };
        DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
        COLORREF clrTextPrev = SetTextColor(pps->hdc, RGB(0,0,0));
        COLORREF clrBkPrev = SetBkColor(pps->hdc, RGB(255,255,255));
        BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, SRCAND);
        SetTextColor(pps->hdc, GetSysColor(COLOR_MENUTEXT));
        SetBkColor(pps->hdc, RGB(0,0,0));
        BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, SRCPAINT);
        SetBkColor(pps->hdc, clrBkPrev);
        SetTextColor(pps->hdc, clrTextPrev);
        SelectBitmap(hdcMem, hbmPrev);
       }
       DeleteObject(hbmMono);
      }
      DeleteDC(hdcMem);
     }
    }
    

    This is the traditional two-step blit. The first erases the pixels that are about to be overwritten by setting the foreground to black and background to white, then using SRCAND. This has the effect of erasing all the foreground pixels to zero while leaving the background intact. The second blit does the same, but with SRCPAINT. This means that the background pixels need to be treated as black, so that when they are "or"d with the destination, the destination pixels are unchanged. The foreground pixels get the desired foreground color.

    This method can be shortened by negating the first blit, reversing the sense of foreground and background, so that the color black doesn't have to move between the background color and the text color.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     HDC hdcMem = CreateCompatibleDC(pps->hdc);
     if (hdcMem) {
      int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
      int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
      HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
      if (hbmMono) {
       HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
       if (hbmPrev) {
        RECT rc = { 0, 0, cxCheck, cyCheck };
        DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
        COLORREF clrTextPrev = SetTextColor(pps->hdc, RGB(255,255,255));
        COLORREF clrBkPrev = SetBkColor(pps->hdc, RGB(0,0,0));
        BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, 0x00220326); // DSna
        SetTextColor(pps->hdc, GetSysColor(COLOR_MENUTEXT));
        BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, SRCPAINT);
        SetBkColor(pps->hdc, clrBkPrev);
        SetTextColor(pps->hdc, clrTextPrev);
        SelectBitmap(hdcMem, hbmPrev);
       }
       DeleteObject(hbmMono);
      }
      DeleteDC(hdcMem);
     }
    }
    

    Whether this shortening is actually an overall improvement is difficult to tell. It's possible that some display drivers have a highly optimized SRCAND handler whereas they are less likely to have an optimized 0x00220326 handler.

    (Exercise: Why can't you instead reverse the second blit, converting it to a MERGEPAINT?)

  • The Old New Thing

    Rendering menu glyphs is slightly trickier

    • 11 Comments

    Last time, we saw how to draw themed and unthemed radio buttons, and I mentioned that menu glyphs are trickier. They're trickier because they are provided as raw monochrome bitmaps instead of fully-formed color-coordinated bitmaps. First, let's do it wrong in order to see what we get. Then we'll try to fix it. Start with a clean new scratch program

    class RootWindow : public Window
    {
     ...
    protected:
     void PaintContent(PAINTSTRUCT *pps);
     BOOL WinRegisterClass(WNDCLASS *pwc)
     {
      pwc->hbrBackground = (HBRUSH)(COLOR_MENU + 1);
      return __super::WinRegisterClass(pwc);
     }
     ...
    };
    
    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
     int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
     RECT rc = { 0, 0, cxCheck, cyCheck };
     DrawFrameControl(pps->hdc, &rc, DFC_MENU, DFCS_MENUCHECK);
    }
    

    This naïvely uses the DrawFrameControl function to draw the menu check mark directly into the paint DC. If you are running with the default Windows XP theme you probably won't notice anything amiss, but switch to the Windows Classic theme and you'll see that the check mark is drawn in black and white even though the Classic menu background color is gray.

    The reason for this is called out in the documentation for DrawFrameControl:

    If uType is either DFC_MENU or DFC_BUTTON and uState is not DFCS_BUTTONPUSH, the frame control is a black-on-white mask (that is, a black frame control on a white background).

    All we get from DrawFrameControl is a monochrome mask. It is our responsibility to colorize it as necessary. To do this, we draw the mask into a monochrome bitmap, and then use the BitBlt function to colorize it. Recall that when blitting from a monochrome bitmap to a color bitmap, the color black in the source bitmap becomes the destination DC's text color, and the color white in the source bitmap becomes the destination DC's background color.

    void RootWindow::PaintContent(PAINTSTRUCT *pps)
    {
     HDC hdcMem = CreateCompatibleDC(pps->hdc);
     if (hdcMem) {
      int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
      int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
      HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
      if (hbmMono) {
       HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
       if (hbmPrev) {
        RECT rc = { 0, 0, cxCheck, cyCheck };
        DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
        COLORREF clrTextPrev = SetTextColor(pps->hdc,
                                         GetSysColor(COLOR_MENUTEXT));
        COLORREF clrBkPrev = SetBkColor(pps->hdc,
                                             GetSysColor(COLOR_MENU));
        BitBlt(pps->hdc, 0, 0, cxCheck, cyCheck,
               hdcMem, 0, 0, SRCCOPY);
        SetBkColor(pps->hdc, clrBkPrev);
        SetTextColor(pps->hdc, clrTextPrev);
        SelectBitmap(hdcMem, hbmPrev);
       }
       DeleteObject(hbmMono);
      }
      DeleteDC(hdcMem);
     }
    }
    

    The key steps here are (1) drawing into a temporary monochrome bitmap to generate the mask, (2) setting the text and background colors of the destination DC, (3) using BitBlt to do the color mapping. The rest of the function is just boring bookkeeping.

    Observe that the checkmark's colors now match the system menu colors because we set them as the text and background colors for the mono-to-color blit.

    Armed with this knowledge, perhaps you can help this person, who is trying to draw the menu check marks transparently. I can think of two different solutions off the top of my head.

Page 4 of 5 (42 items) 12345