October, 2003

  • The Old New Thing

    Using the TAB key to navigate in non-dialogs

    • 4 Comments

    The IsDialogMessage function works even if you aren't a dialog. As long as your child windows have the WS_TABSTOP and/or WS_GROUP styles, they can be navigated as if they were part of a dialog box. One caveat is that IsDialogMessage will send DM_GETDEFID and DM_SETDEFID messages to your window, which are message numbers WM_USER and WM_USER+1, so you should avoid using those messages in your window procedure for some other purpose.

    These changes to our scratch program illustrate how you can use the TAB key to navigate within a non-dialog.

    HWND g_hwndLastFocus;
    
    void OnSetFocus(HWND hwnd, HWND hwndOldFocus)
    {
        if (g_hwndLastFocus) {
            SetFocus(g_hwndLastFocus);
        }
    }
    
    void OnActivate(HWND hwnd, UINT state,
                    HWND hwndActDeact, BOOL fMinimized)
    {
        if (state == WA_INACTIVE) {
            g_hwndLastFocus = GetFocus();
        }
    }
    
    // Just display a messagebox so you can see something
    void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
    {
        switch (id) {
        case 100:
            MessageBox(hwnd, TEXT("Button 1 pushed"),
                       TEXT("Title"), MB_OK);
            break;
        case 101:
            MessageBox(hwnd, TEXT("Button 2 pushed"),
                       TEXT("Title"), MB_OK);
            break;
        case IDCANCEL:
            MessageBox(hwnd, TEXT("Cancel pushed"),
                       TEXT("Title"), MB_OK);
            break;
        }
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
        HWND hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Button &1"),              /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_DEFPUSHBUTTON | BS_TEXT,     /* Style */
            0, 0, 100, 100,                 /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)100,                     /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
        g_hwndLastFocus = hwndChild;
    
        hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Button &2"),              /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_PUSHBUTTON | BS_TEXT,        /* Style */
            100, 0, 100, 100,               /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)101,                     /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
    
        hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Cancel"),                 /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_PUSHBUTTON | BS_TEXT,        /* Style */
            200, 0, 100, 100,               /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)IDCANCEL,                /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
    
        return TRUE;
    }
    
    //  Add to WndProc
    
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);
        HANDLE_MSG(hwnd, WM_SETFOCUS, OnSetFocus);
    
        // Add blank case statements for these to ensure we don't use them
        // by mistake.
        case DM_GETDEFID: break;
        case DM_SETDEFID: break;
    
    //  Change message loop
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0)) {
            if (IsDialogMessage(hwnd, &msg)) {
                /* Already handled by dialog manager */
            } else {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    

    One subtlety is the additional handling of the WM_ACTIVATE and WM_SETFOCUS messages to preserve the focus when the user switches away from the window and back. Notice also that we picked Button 1 as our initial default button by setting it with the BS_DEFPUSHBUTTON style.

    Observe that all the standard dialog accelerators now work. The TAB key navigates, the Alt+1 and Alt+2 keys act as accelerators for the two buttons, the Enter key presses the default button, and the ESC key pushes the Cancel button since its control ID is IDCANCEL.

  • The Old New Thing

    What is the Alt+Tab order?

    • 23 Comments

    What determines the order in which icons appear in the Alt+Tab list?

    The icons appear in the same order as the window Z-order. When you switch to a window, then it comes to the top of the Z-order. If you minimize a window, it goes to the bottom of the Z-order. The Alt+Esc hotkey (gosh, does anybody still use Alt+Esc?) takes the current top window and sends it to the bottom of the Z-order (and the window next in line comes to the top). The Alt+Shift+Esc hotkey (I bet you didn't know that hotkey even existed) takes the bottom-most window and brings it to the top, but does not open the window if it is minimized.

    The presence of "always on top" windows makes this a little more complicated. The basic rule is that an "always on top" window always appears on top of a "not always on top" window. So if the above rules indicate that a "not always on top" window comes to the top, it really just goes as high as it can without getting on top of any "always on top" windows.

    You may have run across the term "fast task switching". This was the term used to describe the precursor to the current Alt+Tab switching interface. The old way of switching via Alt+Tab (Windows 3.0 and earlier) was just like Alt+Esc, except that the window you switched to was automatically opened if it had been minimized. When the new Alt+Tab was added to Windows 3.1, we were concerned that people might prefer the old way, so there was a switch in the control panel to set it back to the slow way. (There is also a setting SPI_SETFASTTASKSWITCH that lets you change it programmatically.) It turns out nobody complained, so the old slow way of task switching was removed entirely and the setting now has no effect.

    This does highlight the effort we take to try to allow people who don't like the new way of doing something to go back to the old way. It turns out that corporations with 10,000 employees don't like it when the user interface changes, because it forces them to spend millions of dollars retraining all their employees. If you open up the Group Policy Editor, you can see the zillions of deployment settings that IT administrators can use to disable a variety of new Windows UI features.

  • The Old New Thing

    The more I learn about Sweden, the weirder it gets

    • 10 Comments

    It must be really boring in Sweden in the winter. Things to do:

    Military service in Sweden is compulsory for all males. When your country has not participated in any wars for two hundred years, you have to pass the time somehow. Apparently one of the exercises is to assess how Sweden would fare against its nearest neighbors. The general consensus (at least in Sweden) is that they could take on Finland and Norway, but if the Danes were to invade, Sweden would eventually succumb, but not before doing the Danish army serious damage.

    Pass the herring.
  • The Old New Thing

    Curling anyone?

    • 9 Comments

    The Granite Curling Club is having their annual open house today, October 18th 2003, from 2pm to 8pm. The Seattle facility is the only dedicated curling facility in the United States west of the Rockies. Bring sweatpants, flat-soled shoes, and $5. They'll provide the brooms and duct tape.

    I became fascinated with curling many years ago when I would run across curling tournaments on Canadian television. The only chance to see curling on television in the States is maybe if you're lucky a match or two during the Winter Olympics.

  • The Old New Thing

    Don't let Marketing mess with your slides

    • 5 Comments

    It's PDC season, so I thought I'd relate an anecdote about a conference from many years ago.

    I forget which conference it was, maybe GCDC 1996, we were all busy preparing our presentations and submitted them to the Microsoft conference representatives so they could apply the standard template, clean them up, print out copies to go into the handouts, all that stuff.

    What about that "clean them up" step?

    We didn't realize what "clean them up" meant until we showed up at the conference and looked at the handouts.

    Part of "cleaning up" was inserting "®" and "™" symbols as necessary. Which meant that they also took every occurrence of the abbreviation "VB" and changed it to "Microsoft Visual Basic®". They even did this to the presentation on vertex buffers. The abbreviation for vertex buffers is also "VB".

    You can imagine what this did to the presentation on vertex buffers.
  • The Old New Thing

    Scrollbars redux: Part 12

    • 0 Comments

    Reader Jeff Miller added Word-style scroll tips to our sample scrollbar program. Here's how he did it:

    HWND g_hwndTT;              /* tooltip control */
    TOOLINFO g_ti;              /* tooltip info struct */
    BOOL g_fInScroll;           /* Scroll state */
    
    void CancelScrollTip()
    {
        if (g_fInScroll) {
            g_fInScroll = FALSE;
            SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
                        FALSE, (LPARAM)&g_ti);
        }
    }
    
    void UpdateScrollTip()
    {
        if (!g_hwndTT) return;
        if (!g_fInScroll) {
            g_fInScroll = TRUE;
            DWORD dwPos = GetMessagePos();
            SendMessage(g_hwndTT, TTM_TRACKPOSITION, 0,
                        MAKELPARAM(GET_X_LPARAM(dwPos) +
                                   GetSystemMetrics(SM_CXVSCROLL)
                                   GET_Y_LPARAM(dwPos)));
            SendMessage(g_hwndTT, TTM_TRACKACTIVATE, TRUE,
                        (LPARAM)&g_ti);
        }
        // Reset the text to LPSTR_TEXTCALLBACK so it will get
        // re-fetched
        SendMessage(g_hwndTT, TTM_SETTOOLINFO, 0,
                    (LPARAM)&g_ti);
    }
    
    LRESULT
    OnNotify(HWND hwnd, int id, LPNMHDR pnm)
    {
        switch (pnm->code) {
        case TTN_GETDISPINFO:
            {
                NMTTDISPINFO *pnmtdi = (NMTTDISPINFO*)pnm;
                wnsprintf(pnmtdi->lpszText, 80,
                          TEXT("Line: %d"), g_yOrigin);
            }
            break;
        }
        return 0;
    }
    
    // change case statement in OnVscroll:
        case SB_THUMBTRACK:     ScrollTo(hwnd, GetTrackPos(hwnd, SB_VERT));
                                UpdateScrollTip(); break;
    // add case statement to OnVscroll:
        case SB_ENDSCROLL:      CancelScrollTip(); break;
    
    // add to OnCreate
    
        /* Create and initialize the tooltip */
        g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
                 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
                 CW_USEDEFAULT, CW_USEDEFAULT,
                 CW_USEDEFAULT, CW_USEDEFAULT,
                 hwnd, NULL, NULL, NULL);
        if (g_hwndTT) {
            g_ti.cbSize = sizeof(g_ti);
            g_ti.uFlags = TTF_TRACK;
            g_ti.hwnd = hwnd;
            g_ti.lpszText = LPSTR_TEXTCALLBACK;
            SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti);
        }
    
    // add to WndProc
        case WM_NOTIFY: return OnNotify(hwnd, (int)wParam,
                                        (LPNMHDR)lParam);
    

    A tracking tooltip is used to display the scroll tip because we don't want the tooltip to do automatic positioning or automatic show/hide.

    When a tracking scroll occurs (SB_THUMBTRACK), we update the scroll tip, displaying it if necessary. When the tracking scroll ends (SB_ENDSCROLL), we remove the tip.

    The above code mimics the Word XP style, where the tooltip appears near the original thumb position but does not follow the thumb as the user scrolls. If you prefer that the tooltip follow the thumb, remove the if (!g_fInScroll) test from the UpdateScrollTip function.

  • The Old New Thing

    The much-misunderstood "nop" action

    • 6 Comments
    Last month, the printers were reconfigured in our building and we got an announcement that went like this:

    Subject: Printer/Copier Reconfiguration

    Microsoft Digital Campus Program

    *Action Required*

    blah blah blah printers are being reconfigured blah blah blah

    Action(s) to be taken: No action is required as the print path information will remain the same.
    Sometimes you have to do nothing, and that counts as doing something.
  • The Old New Thing

    Murphy's law for school

    • 2 Comments
    If you know the answer to every question but one, the teacher will call on you to answer that one. I'm learning this rule all over again.
  • The Old New Thing

    What about BOZOSLIVEHERE and TABTHETEXTOUTFORWIMPS?

    • 58 Comments

    In a previous entry I discussed the story behind the functions with the funny names BEAR, BUNNY and PIGLET. But what about the ones with goofier names like BOZOSLIVEHERE and TABTHETEXTOUTFORWIMPS?

    For this, you need a deeper history lesson.

    Back in the old days of real-mode Windows, all callback functions had to be exported. This was necessary for complicated technical reasons I may bother to explain if anybody really cared but I doubt anybody does any more. So the window procedures for all of the standard window classes (edit controls, list boxes, check boxes, etc.) were exported from USER. So too were various other callback functions like timer procedures. This was in addition to the usual collection of internal functions so USER, KERNEL and GDI could coordinate their efforts.

    Some people reverse-engineered all these internal functions and printed books on how they worked, and as a result there were a lot of programs that actually used them. Which was quite a surprise to us because they were internal functions. And then when we wanted to redesign these internal functions (for example, to add a parameter, or if we decided that we didn't need it any more and tried to delete it), we found found that the progams stopped working.

    So we had to put the functions back, with their old behavior. The new features were were contemplating had to be redesigned, redirected, or possibly even abandoned entirely. (If we wanted to delete a function, then the work could continue, but the old function had to stay around with its old behavior. It was basically dead code from the OS's point of view, hanging around just because some random app or other decided to cheat and bypass the documented way of doing things.) But to teach people a lesson, they often got given goofy names.

    For example, BOZOSLIVEHERE was originally the window procedure for the edit control, with the rather nondescript name of EditWndProc. Then some people who wanted to use the edit control window procedure decide that GetWindowLong(GWL_WNDPROC) was too much typing, so they linked to EditWndProc directly. Then when Windows 2.0 (I think) removed the need to export window procedures, we removed them all, only to find that programs stopped working. So we had to put them back, but they got goofy names as a way of scolding the programs that were doing these invalid things.

    Things got even worse in Windows 95, when all our window procedures were converted to 32-bit versions. The problem is that the old window procedures were only 16-bit. So we couldn't even simply export the 32-bit window procedure under the name BOZOSLIVEHERE. We had to write a conversion function that took an illegal 16-bit function call and converted it to the corresponding illegal 32-bit function call.

    This is just the tip of the iceberg with respect to application compatibility. I could probably write for months solely about bad things apps do and what we had to do to get them to work again (often in spite of themselves). Which is why I get particularly furious when people accuse Microsoft of maliciously breaking applications during OS upgrades. If any application failed to run on Windows 95, I took it as a personal failure. I spent many sleepless nights fixing bugs in third-party programs just so they could keep running on Windows 95. (Games were the worst. Often the game vendor didn't even care that their program didn't run on Windows 95!)
  • The Old New Thing

    Why don't notification icons get a message when the user clicks the "X" button?

    • 9 Comments

    If such a notification were generated, ill-behaved programs would just react to a click on the balloon's "X" button with an annoying follow-up dialog like, "Are you sure you want to ignore my wonderful message?" So there was a conscious decision not to give them the chance.

    In the Before Time, software was trusted not to be actively evil, not to second-guess a user's action, not to invade a user's private space.

    Over the years, we've learned that this was a naïve position to take. So now, when we decide that something is an end-user setting, we actively avoid giving programmatic access to it, so programs won't be tempted to weasel themselves into it.
Page 2 of 4 (37 items) 1234