March, 2006

  • The Old New Thing

    Reading the fine print, episode 2: Portable headphones


    Marketing writes the big print; lawyers write the small print.

    I bought some portable stereo headphones. The front of the box says you can use it "while in-line skating, power walking, biking, jogging, skiing, running, weightlifting, climbing and more." (Emphasis mine.)

    The back of the box says that it should not be used "while driving or cycling."

    (Episode 1.)

  • The Old New Thing

    Why is there no message for disabling the Cancel button on a wizard?


    Some people have noticed that there is no message that lets you disable the Cancel button on a wizard. This is on purpose. Usability studies reveal that users find it extremely frustrating when they get partway through a wizard and then decide they don't want to perform the operation after all, but find that the wizard doesn't give them a way to cancel. Now the user feels trapped. They can't back out of the operation; they're being forced to finish something against their will.

    Imagine if you went to an e-commerce site and started going through the checkout procedure, then decide that you didn't want to buy the item after all, yet the web page disabled the Back button and didn't have a Cancel, and when you clicked the "X" button to close the web browser, the page put up a message box saying, "You cannot cancel this operation once it has begun." Not a very pleasant experience, and I suspect you would avoid this web site in the future.

    The same principle applies to wizards. Users should always be given a way to cancel out of a wizard. Wizards should, generally speaking, collect information in stages, and apply them at the point the user clicks Finish. This is not always practical, and wizards may have need to commit partially-made decisions along the way, but that's the general idea. Under such circumstances, the wizard author should make it clear to the user whether cancelling the operation will undo the previously-committed decisions or leave them intact. If you are clever, you can do this without an annoying confirmation dialog. Instead, you can indicate this by the flow of the wizard itself. For example, you might have a wizard that goes like this:

    1. Welcome to the XYZ Setup Wizard.
    2. What type of XYZ do you want?
    3. Your XYZ is now ready to use. Would you like to set up special access rights to the XYZ?
    4. Access rights to Your XYZ has been established. This concludes the XYZ Setup Wizard.

    With this wizard flow, clicking "Next" to go from page 2 to page 3 commits the initial XYZ setup, as indicated by the text "Your XYZ is now ready to use." If the user decides to cancel out of the customization, you've made it clear that the XYZ has nevertheless been set up and cancelling will not undo it.

  • The Old New Thing

    Raymond, you even pose like a girl


    Okay, so it's bad enough that I write like a girl. (And so does Betsy.) Now, Korby Parnell reported on a little get-together of some Microsoft bloggers, and in the photograph you can see that I'm doing the "peace" sign, just like young Japanese women do in photographs. Apparently, boys are supposed to strike a superhero pose, but the peace sign is so much more fun.

  • The Old New Thing

    The consequences of invalidating the null window


    On occasion, you might notice that every window on the desktop flickers and repaints itself. One of the causes for this is a simple null handle bug.

    The InvalidateRect function is one you're probably well-familiar with. It is used to indicate to the window manager that the pixels of a particular window are no longer current and should be repainted. (You can optionally pass a rectangle that specifies a subset of the window's client area that you wish to mark invalid.) This is typically done when the state of the data underlying the window has changed and you want the window to repaint with the new data.

    If however you end up passing NULL as the window handle to the InvalidateRect function, this is treated as a special case for compatibility with early versions of Windows: It invalidates all the windows on the desktop and repaints them. Consequently, if you, say, try to invalidate a window but get your error checking or timing wrong and end up passing NULL by mistake, the result will be that the entire screen flickers.

    Even more strangely, passing NULL as the first parameter ValidateRect has the same behavior of invalidating all the windows. (Yes, it's the "Validate" function, yet it invalidates.) This wacko behavior exists for the same compatibility reason. Yet another example of how programs rely on bugs or undocumented behavior, in this case, the peculiar way a NULL parameter was treated by very early versions of Windows due to lax parameter validation. Changing nearly anything in the window manager raises a strong probability that there will be many programs that were relying on the old behavior, perhaps entirely by accident, and breaking those programs means an angry phone call from a major corporation because their factory control software stopped working.

  • The Old New Thing

    Other things happen for a reason, too


    Today's theme is a quick one: Other Microsofties explain what at first appear to be puzzling decisions.

  • The Old New Thing

    The ForceAutoLogon setting doesn't do what most people think


    The folks on the logon team wish me to remind you that the ForceAutoLogon setting does more than just log on an account automatically. They've had to deal with large numbers of people who set the key without really understanding what it does, and then getting into trouble because what they get is not what they expected.

    In addition to logging on an account automatically, the ForceAutoLogon setting also logs you back on after you log off. It is designed for machines running as kiosks or other publically-accessible scenarios where you want the kiosk account to be the only account available. Even if the user manages to fiddle with the machine and log off the kiosk user, the logon system will just log the kiosk user back on.

    As a result, setting the ForceAutoLogon setting effectively locks out all users aside from the one you are forcing. If you do this to one of your machines, you'd better have some other way of administering the machine. (Typically, this is done via remote administration.)

  • The Old New Thing

    The peculiar appeal of the Baseball Uncyclopedia


    NPR's Only a Game interviewed the authors of The Baseball Uncyclopedia, an irreverent guide to our national pastime, and it was a blast to listen to. The two authors clearly are huge baseball fans, but they bring to it a fascination not with mind-numbing statistics but with the deep history of the sport. (Plus the fact that they disagree on many things, which leads to really funny arguments.) And who can't appreciate the theory that Kevin Costner is responsible for Shoeless Joe Jackson not being in the Baseball Hall of Fame?

  • The Old New Thing

    Very late remarks on the original Chinese dictionary series


    I have not forgotten about the Chinese/English dictionary series, but I simply haven't had the motivation to sit down and write up descriptions and discussion for the code that I wrote along the way, so instead of adding to the program, I'm going to answer some questions that were asked back when I started the series but which I didn't respond to at the time since I was out of town.

    More than one commenter suggested using v.reserve() to pre-allocate the vector memory. First of all, the cost of vector reallocation really didn't factor into the performance after the first few rounds of optimization, so adding a reservation step ended up being unnecessary. Furthermore, getting the correct value to pass to v.reserve() would mean making two passes over the dictionary, one to get the number of entries in the dictionary and set the vector reservation size, and another to fill the dictionary itself. The alternative would have been to make a guess as to the number of entries in the dictionary based on the total file size and the average length of each entry. Fortunately, it never came to that.

    Another commenter suggested preprocessing the file. That is also a valid technique, but I intentionally avoided it partly for expository purposes (it would have removed much of the challenge), and partly because I wanted to be able to update the dictionary by merely replacing the dict.b5 file.

    Commenter CornedBee suggested using the wcsrchr function as an alternative to the missing std::rfind method. Note, however, that the DirctionaryEntry::Parse method takes a string in the form of a start and end; it is not a null-terminated string. Passing this to wcsrchr would have resulted in quite undesirable behavior.

  • The Old New Thing

    Martha, Martha, Martha!


    Martha Stewart was in town this past weekend signing her book on baking. Although I'm a Martha fan, I didn't attend the signing because I already have a really awesome book on baking. I've listed below some of the ground rules for the book signing, mixed in with some stuff I just plain made up. Your mission is to pick out which of these rules are for real and which are fake.

    1. Martha Stewart will sign only the baking book. No other books or memorabilia.
    2. Do not make eye contact with Martha Stewart.
    3. Do not speak to Martha Stewart unless spoken to.
    4. Do not ask Martha to write anything other than her name.
    5. Do not take pictures of Martha Stewart.
    6. If Martha Stewart becomes angry, curl up into a ball and cover your head with your hands.

    You can check your score on this web page.

  • The Old New Thing

    Restating the obvious about the WM_COMMAND message


    I'm satisfied with the MSDN documentation for the WM_COMMAND message, but for the sake of mind-numbing completeness, I'm going to state the obvious in the hope that you, dear readers, can use this technique to fill in the obvious in other parts of MSDN.

    The one-line summary of the WM_COMMAND message says, "The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated." In a nutshell, there are three scenarios that generate a WM_COMMAND message, namely the three listed above. You want to think of the menu and accelerator scenarios of the WM_COMMAND message as special cases of the control scenario.

    The high-order word of the wParam parameter "specifies the notification code if the message is from a control". What does "control" mean here? Remember that you have to take things in context. The WM_COMMAND message is being presented in the context of Win32 in general, and in the context of the window manager in particular. Windows such as edit boxes, push buttons, and list boxes are commonly called "controls", as are all the window classes in the "common controls library". In the world of the window manager, a "control" is a window whose purpose is to provide some degree of interactivity (which, in the case of the static control, might be no interactivity at all) in the service of its parent window. The fact that the WM_COMMAND is used primarily in the context of dialog boxes further emphasizes the point that the term "control" here is just a synonym for "child window".

    What does "notification code" mean here? Control notification codes are arbitrary 16-bit values defined by the control itself. By convention, they are named xxN_xxxx, where the "N" stands for "notification". Be careful, however, not to confuse this with notification codes associated with the WM_NOTIFY message. Fortunately, every notification code specifies in its documentation whether it arrives as a WM_COMMAND notification or a WM_NOTIFY notification. A modern control designer is more likely to use WM_NOTIFY notifications since they allow additional information to be passed with the notification. The WM_COMMAND message, by comparison, passes only the notification itself; the other parameters to the WM_COMMAND message are forced, as we'll see below. If WM_NOTIFY is superior to WM_COMMAND, why do some controls use WM_COMMAND? Because WM_NOTIFY wasn't available until Windows 95. Controls that were written prior to Windows 95 had to content themselves with the WM_COMMAND message.

    "If the message is from an accelerator, this value [the high-order word of the wParam parameter] is 1." Remember, we're still in the context of the window manager, and particular in the context of the WM_COMMAND message. The accelerator here refers to messages generated by the call to TranslateAccelerator in the message loop.

    "If the message is from a menu, this value is zero." If the WM_COMMAND mesage was triggered by the user selecting an item from a menu, then the high-order word of the wParam is zero.

    The low-order word of the wParam parameter "specifies the identifier of the menu item, control, or accelerator." The identifier of a menu item or accelerator is the command code you associated with it in your menu or accelerator template or (in the case of a menu item) when you manually created the menu item with a function like InsertMenuItem. (You probably named your menu item identifiers and accelerator identifiers IDM_something.) The identifier of a control is determined by the creator of the control; recall that the hMenu parameter to the CreateWindow and CreateWindowEx functions is treated as a child window identifier if you're creating a child window. It is that identifier that the control identifier. (You can retrieve the identifier for a control by calling the GetDlgCtrlID function.)

    Finally, the lParam parameter is the "handle to the control sending the message if the message is from a control. Otherwise, this parameter is NULL." If the notification is generated by a child window (with a notification code appropriate for that child window, obviously), then that child window handle is passed as the lParam. If the notification is generated by an accelerator or a menu, then the lParam is zero.

    Notice that nearly all of the parameters to the WM_COMMAND message are forced, once you've decided what notification you're generating.

    If you are generating a notification from a control, you must pass the notification code in the high word of the wParam, the control identifier in the low word of the wParam, and the control handle as the lParam. In other words, once you've decided that the hwndC window wants to send a CN_READY notification, you have no choice but to type

    SendMessage(GetParent(hwndC), WM_COMMAND,
                MAKEWPARAM(GetDlgCtrlID(hwndC), CN_READY),

    In other words, all control notifications take the form

    SendMessage(GetParent(hwndC), WM_COMMAND,
                MAKEWPARAM(GetDlgCtrlID(hwndC), notificationCode),

    where hwndC is the control generating the notification and notificationCode is the notification code. Of course, you can use PostMessage instead of SendMessage if you would rather post the notification rather than sending it.

    The other two cases (accelerators and menus) are not cases you would normally code up, since you typically let the TranslateAccelerator function deal with accelerators and let the menu system deal with menu identifiers. But if for some reason, you wanted to pretend that the user had typed an accelerator or selected a menu item, you can generate the notification manually by following the rules set out in the documentation.

    // simulate the accelerator IDM_WHATEVER
    SendMessage(hwnd, WM_COMMAND,
                MAKEWPARAM(IDM_WHATEVER, 1),

    Here, hwnd is the window that you want to pretend was the window passed to the TranslateAccelerator function, and IDM_WHATEVER is the accelerator identifier.

    Simulating a menu selection is exactly the same, except that (according to the rules above), you set the high-order word of the wParam to zero.

    // simulate the menu item IDM_WHATEVER
    SendMessage(hwnd, WM_COMMAND,
                MAKEWPARAM(IDM_WHATEVER, 0),

    Here, hwnd is the window associated with the menu. A window can be associated with a menu either by being created with the menu (having passed the menu handle to the CreateWindow or CreateWindowEx function explicitly, or having it done implicitly by including it with the class registration) or by having been passed explicitly as the window parameter to a function like TrackPopupWindow.

    One significant difference between the accelerator/menu case and the control notification case is that accelerator and menu identifiers are defined by the calling application, whereas control notifications are defined by the control.

    You may have noticed the opportunity to "pun" the control notification codes. If a control defines a notification code as zero, then it will "look like" a menu item selection, since the high-order word of the wParam in the case of a menu item selection is zero. The button control takes advantage of this pun:

    #define BN_CLICKED          0

    This means that when the user clicks a button control, the WM_COMMAND message that is generated "smells like" a menu selection notification. You probably take advantage of this in your dialog procedure without even realizing it.

    (The static control also takes advantage of this pun:

    #define STN_CLICKED         0

    but in order for the static control to generate the STN_CLICKED notification, you have to set the SS_NOTIFY style.)

    I stated at the start that the accelerator and menu scenarios are just special cases of the control scenario. If you take the pieces of the WM_COMMAND message apart, you'll see that they fall into two categories:

    • What happened? (Notification code.)
    • Whom did it happen to? (Control handle and ID.)

    In the case of a menu or an accelerator, the "What happened?" is "The user clicked on the menu (0)" or "The user typed the accelerator (1)". The "Whom did it happen to?" is "This menu ID" or "This accelerator ID". Since the notification is not coming from a control, the control handle is NULL.

    I apologize to all you Win32 programmers for whom this is just stating the obvious.

    Now that you're an expert on the WM_COMMAND message, perhaps you can solve this person's problem.

Page 4 of 5 (41 items) 12345