January, 2007

  • The Old New Thing

    Unexpected consequences of writing a book: Public appearances

    • 31 Comments

    One of the consequences of being a minor book author is that you can't be a private person any more; you're automatically a public personality. If somebody asks you for an interview, you can't just reject it as a matter of principle. You're now doing it not for yourself but in the service of your book. (Major authors might be able to get away with the whole "reclusive author" shtick, but you have to be pretty darn huge in order to pull it off.)

    This means that you're going to see me popping up more often than before. Today, my interview with .Net Rocks! went up a week ahead of schedule. (I had originally planned to write up this introduction over the weekend, so this entry is a bit of a rush job. Notice that their schedule of upcoming interviews along the right hand side lists me for January 30th, not January 23rd.)

    Interviews are exhausting because you're not just having a friendly conversation. First, you have to prepare for it, thinking up some stories or at least preparing answers to likely questions. Of course, the answers you prepare rarely match the questions you receive, but you have to be ready for them anyway. The interview itself is an improv performance before a microphone. You and the interviewer (assuming the interviewer is friendly to your cause) are working together to produce entertainment and insight simultaneously. Meanwhile, everything you say is being recorded for posterity, so you have to run your brain's "Should I say this?" filter on maximum power. There are no take-backs.

  • The Old New Thing

    Non-psychic debugging: If you can't find something, make sure you're looking in the right place

    • 21 Comments

    This isn't psychic debugging, but it's the sort of dumb mistake everybody makes and which you can't see when re-reading your code because your brain shows you what you want to see, not what's really there.

    I'm trying to respond to the PSN_QUERYINITIALFOCUS notification, but it's not working. What am I doing wrong?
    INT_PTR CALLBACK DlgProc(HWND hdlg, UINT uMsg,
                             WPARAM wParam, LPARAM lParam)
    {
     switch (uMsg) {
     ...
     case WM_NOTIFY:
      switch (reinterpret_cast<NMHDR*>(lParam)->code) {
      ...
      case PSN_QUERYINITIALFOCUS:
       {
        PSHNOTIFY *ppsn = reinterpret_cast<PSHNOTIFY*>(lParam);
        SetWindowLongPtr(ppsn->hdr.hwndFrom, DWLP_MSGRESULT,
                        (LPARAM)GetDlgItem(ppsn->hdr.hwndFrom, IDC_MYCONTROL));
       }
       return TRUE;
      ...
      }
      break;
     }
     return FALSE;
    }
    

    You can stare at this code for ages and completely overlook that the wrong window handle is being passed to GetDlgItem and SetWindowLongPtr. The hwndFrom of a WM_NOTIFY message is the window that is generating the notification; in this case, the PSN_QUERYINITIALFOCUS is generated from the property sheet frame window. But the result of a dialog message needs to be stored in the extra bytes of the dialog that's receiving the message, not the one that's sending it. And when you call GetDlgItem, the window manager searches for the control among the children of the window you pass as the first parameter—but here, IDC_MYCONTROL is a child of the property sheet page, not the property sheet frame.

    The correct code should read

        SetWindowLongPtr(hdlg, DWLP_MSGRESULT,
                        (LPARAM)GetDlgItem(hdlg, IDC_MYCONTROL));
    

    This phenomenon of missing the obvious because your brain shows you what you want to see (rather than what's actually there) reminds me of a time one of my colleagues called me into his office to help figure out why one of his loops was iterating only once. He called the function up on the screen and talked me through it. "Okay, now the variables are set up for the loop, so while we haven't found the entry, we grab the next item from the list..."

    I hesitatantly interrupted. "Um, 'while'? The code says 'if'."

    "Oops. Um, nevermind. Nothing to see here. Move along now."

    This is the same reason you want to have somebody else proofread your writing. Since you wrote it, your brain will show you what you meant to write, not necessarily what you actually wrote.

  • The Old New Thing

    The cost of continuously-visible affordances with dynamic states

    • 18 Comments

    Serge Wautier asks, "Why are the copy/cut/paste buttons not disabled when there's nothing to copy/cut/paste?", noting that the back/forward buttons do disable themselves when navigation is not possible in that direction.

    To get to this question, we'll first go back in time a bit to a world without toolbars. In those early days, these dynamic options such as copy/cut/paste appeared solely on the Edit menu. Since the contents of Edit menu were visible only when the user clicked on it, the cut/copy/paste options needed to be updated only when the menu was visible. In other words, during WM_INITMENUPOPUP handling.

    This is also why it is somewhat risky to post WM_COMMAND messages which correspond to a menu item to a window which is not prepared for it. The only way an end-user can generate that WM_COMMAND message is by going through the menu: clicking the top-level menu to show the drop-down menu, then clicking on the menu item itself. Most programs do not maintain the menu item states when the menu is closed since there's no point in updating something the user can't see. Instead, they do it only in response to the WM_INITMENUPOP message. Lazy evaluation means that the user doesn't pay for something until they use it. In this case, paying for the cost of calculating whether the menu item should be enabled or not. Depending on the program, calculating whether a menu item should be enabled can turn out to be rather expensive, so it's natural to avoid doing it whenever possible. ("I can do nothing really fast.")

    When toolbars showed up, things got more complicated. Now, the affordances are visible all the time, right there in the toolbar. How do you update something continuously without destroying performance?

    The navigation buttons disable and enable themselves dynamically because the conditions that control their state satisfy several handy criteria.

    • The program knows when the state has potentially changed. (The program maintains the navigation history, so it knows that the button states need to be recalculated only when a navigation occurs.)
    • Computing the state is relatively cheap. (All the program has to check is whether there is a previous and next page in the navigation history Since the navigation history is typically maintained as a list, this is easy to do.)
    • They change in proportion to user activity within the program. (Each state change can be tied to a user's actions. They don't change on their own.)
    • They change rarely. (Users do not navigate a hundred times per second.)

    Since the program knows when the navigation stack has changed, it doesn't have to waste its time updating the button states when nothing has changed. Since recalculating the state is relatively cheap, the end user will not see the main user interface slow down while the program goes off to determine the new button state after each navigation. And finally, the state changes rarely, so that this cheap calculation does not multiply into an expensive one.

    The copy/cut/paste buttons, on the other hand, often fail to meet these criteria. First, the copy and cut options:

    • The program knows when the state has potentially changed. (Whenever the selection changes.) — good
    • Computing the state is not always cheap. (For example, determining whether an item in Explorer can be cut or copied requires talking to its namespace handler, which can mean loading a DLL. If the item on the clipboard is a file on the network, you may have to access a computer halfway around the world.) — often bad
    • It changes in proportion to user activity within the program. (Each state change can be traced to the user changing the selection.)
    • They change with high frequency. (Dragging a rectangle to make a group selection changes the selection each time the rectangle encloses a new item.) — bad

    Paste is even worse.

    • The program doesn't know when the state has potentially changed. (The clipboard can change at any time. Yes, the program could install a clipboard viewer, but that comes with its own performance problems.) — bad
    • Computing the state is not cheap. (The program has to open the clipboard, retrieve the data on it, and see whether it is in a format that can be pasted. If the clipboard contents are delay-rendered, then the constant probing of the clipboard defeats the purpose of delay-rendered clipboard data, which is to defer the cost of generating clipboard data until the user actually wants it. For Explorer, it's even worse, because it has to take the data and ask the selected item whether it can accept the paste. Doing this means talking to the namespace handler, which can mean loading a DLL. And if the file on the clipboard is on the network, the paste handler may need to open the file to see if it is in a format that can be pasted.) — bad
    • It can change out of proportion to user activity. (Any time any other program copies something to the clipboard, the toolbar has to update itself. Then can happen even when the user is not using the program that has the toolbar! Imagine if Explorer started saturating your network because you copied a lot of UNC paths to the clipboard while editing some text file.) — bad
    • The frequency of change is unknown. (The clipboard is a shared resource, and who knows what other people might be using it for.) — bad

    This is one of those balancing acts you have to do when designing a program. How much performance degredation are you willing to make the user suffer through in order to get a feature they may never even notice (except possibly in a bad way)?

  • The Old New Thing

    2006 storm aftermath: A look back

    • 34 Comments

    It's been about a month since the windstorm that brought the Seattle area to a standstill. Puget Sound Energy has posted a recap of the storm, including what I consider to be a wonderful euphemism:

    We thank those customers who called to update us with valuable outage status information.

    Translation: "We would like to acknowledge all the people who called in to complain."

    Not surprisingly, the storm got a lot of coverage in the local paper. Conspiracy theorists will be woefully dissatisfied with this explanation of how the utility companies decide which lines to repair first. I heard in a radio story that another factor is that a small outage may get fixed out of priority order if a repair crew happens to be nearby (presumably working on a higher priority repair) and the problem can be fixed quickly. It's a fascinating optimization problem, deciding how to deploy limited resources most efficiently, and a problem I am glad it's not my job to solve.

    Many local governments are looking at low-tech solutions to communications problems, since the power outage highlighted our dependence on electronic communications. One of my friends told me about a local government official who appeared on the radio to announce the opening of shelters for people who were out of power and needed a place to stay. When the local official said, "A list of all the locations can be found on our web site," the show host replied, "Um, people without electricity can't check the web site."

    A different friend told me about a caller to a radio talk show from one of the outlying areas who complained about the glacial pace at which municipal services were being restored. The host opined, "Yeah, well, that's what happens when you live in a rural area, I guess."

    The caller answered, "Well, I used to live in Seattle, but I left because the taxes were too high."

  • The Old New Thing

    What does the fCreate parameter to SHCreateStreamOnFileEx mean?

    • 9 Comments

    The documentation for the fCreate parameter for the SHCreateStreamOnFileEx function covers the issue, but since people seem to really like charts and tables, I'll present the same information in tabular form.

    grfMode fCreate File exists? Behavior
    STGM_CREATE (ignored) Yes File is opened (old contents lost)
    STGM_CREATE (ignored) No File is created
    STGM_FAILIFTHERE FALSE Yes File is opened (old contents preserved)
    STGM_FAILIFTHERE FALSE No Fails
    STGM_FAILIFTHERE TRUE Yes Fails
    STGM_FAILIFTHERE TRUE No File is created
  • The Old New Thing

    Email tip: Choose a subject line that is meaningful to the recipient, not to the sender

    • 40 Comments

    Presumably you want the recipient to read the message. That's why you sent it. It would behoove you to select a subject line which conveys to your reader the purpose of your message. Otherwise your reader is likely to ignore it for being too vague and uninteresting.

    Here are some actual bad subject lines I've seen.

    • Customer question
    • Question about Windows XP
    • New question
    • Help needed
    • Any help will be most appreciated
    • SR#314159276358
    • A tough problem!
    • Some questions on Windows
    • Urgent: Query regarding Windows
    • Request for information - URGENT!!
    • URGENT URGENT URGENT

    Suppose your Inbox had a hundred messages that all looked like this. Would you bother reading even one of them?

    I'm sure the subject line makes perfect sense to the person who sent the message. They have only one customer with a question, so "Question from my customer" captures the issue perfectly. But if you send this message to a list with 500 members, those other 499 people most likely will not know what your message is about based solely on the subject line.

    A good subject line would include enough information about the question so that the recipient can decide whether to read further or whether it's something they can't help with. For example, "Question about generics," or even better, "Question about covariant types in generics."

    Remember, choose a subject line that is meaningful to the person you're sending it to. It's only polite.

  • The Old New Thing

    Iced-over roads + people who can't drive = very expensive (and dangerous) game of billiards

    • 64 Comments

    Unbelievable video of people in Portland, OR who should know better trying to drive down an icy road. (Direct WMV link. Interview with the person who shot the video, but you're going to have to put up with the inane local news-anchor chatter. That last link includes images of cars that, yup, struck the fire truck.)

    It's as if these people had lost control of all rational thought. When your car slowly skids to a halt after crashing into a half dozen stopped cars and other roadside obstacles, your brain should be telling you, "Boy, that was a really stupid idea. I should stop now before I kill myself or a pedestrian." You do not punch the gas and accelerate into other cars like it's a demolition derby.

    I hope their insurance company sees this video.

  • The Old New Thing

    Should all windows appear in the taskbar?

    • 63 Comments

    No new content today, just some follow-up discussion on the topic of windows that don't appear in the taskbar. The rules for which windows appear in the taskbar have been documented in MSDN for years, so changing the rules now would mean doing so after the game has ended. Consequently, this is not the sort of change that can be made lightly.

    First point is that the taskbar is called the "taskbar" and not something like the "open windows bar". The name already suggests that the purpose is to display tasks, not open windows. I find it interesting that people, in their zeal to turn the taskbar into a "windows bar" end up removing other features, such as "How do I make a window appear in the Alt+Tab list but not in the taskbar?" Are the people who want to create such a window "just plain wrong"? (There are so many of these people that the Windows Forms folks added a ShowInTaskbar property just for this purpose!) Think about it the next time you complain that Windows doesn't let you do something—there is somebody just like you on the other side who said, "No, that's just plain wrong". Besides, if you decided that all windows must appear in the taskbar, that means that the taskbar would be cluttered with tooltip windows, floating toolbars, desktop toolbars (those things that stick to the edge of the desktop), all sorts of stuff. Do you really want that?

    "The point of the taskbar is to show you what you have open." Strange, I don't remember you at the design meetings or the usability sessions.

    One commenter noted that it took "several service packs" before Microsoft "fixed" the "problem" where property sheets didn't appear in the Alt+Tab list. That was actually a design decision. I remember asking this question at the internal meeting when the new user interface was revealed. The answer was, "If you want to re-open the property sheet from the keyboard, just go back to the original object and ask to see its properties again." You may not like that decision (I sure didn't), but it wasn't a bug. It was on purpose. (A decision which was revisited and changed in Windows 2000, by the way, not a service pack.)

    Another commenter argued that any windows that don't appear in the taskbar should be always-on-top. I suspect a lot of people would be upset that all property sheets suddenly became always-on-top. Among other things, it would mean that you would have to close all your property sheets and control panels before you ran a business presentation or played a game.

  • The Old New Thing

    One Armstrong = 13.5 mph

    • 42 Comments

    Out of curiosity, I wanted to know how fast Tour de France riders go up l'Alpe D'Huez, the legendary mountain climb. Using information from the Wikipedia page, I calculated that Lance Armstrong's 2004 ascent had an average speed of approximately 13.5 mph (22 kph). Consequently, I invented the Armstrong, a unit of bicycle velocity, with one Armstrong equal to 13.5 mph. (If I were being fair, I would have used 1 Pantani = 14mph, since Marco Pantani holds the record for the fastest ascent of l'Alpe d'Huez. But I'm not being fair.)

    The day after I made this fantastic calculation, I glanced down at my speedometer and realized that my speed on flat ground was significantly less than one Armstrong. That's right, Lance Armstrong went up l'Alpe D'Huez faster than I rode to work on flat ground.

    Fortunately, subsequent investigation revealed that my bicycle's speedometer sensor had wiggled out of position and was reporting only about two thirds of my actual speed. My unofficial goal is to be able to go 13.5 mph up the comparatively tiny hills that I have to cross on my way to and from work. It's not l'Alpe d'Huez, but then again, I'm not Lance Armstrong.

  • The Old New Thing

    EnumChildWindows already enumerates recursively

    • 15 Comments

    I often see people write code that goes something like this:

    // do not use - see discussion
    void DoSomethingToWindowTree(HWND hwndRoot)
    {
     // first do it to the window passed in
     DoSomething(hwndRoot);
     // now do it to all the children
     EnumChildWindows(hwndRoot, DoSomethingHelper, 0);
    }
    
    BOOL CALLBACK DoSomethingHelper(HWND hwnd, LPARAM lParam)
    {
     DoSomethingToWindowTree(hwnd);
     return TRUE;
    }
    

    The intent here was to perform the operation on all the windows in a window tree by operating on the root, then operating on each of the children. Operating on the children is in turn performed recursively, so that we eventually see every window in the tree.

    Except that if you actually run this function on a vaguely interesting window tree, you'll find that items get counted multiple times.

    The reason is that the EnumChildWindows function already does the recursion:

    If a child window has created child windows of its own, EnumChildWindows enumerates those windows as well.

    If you add your own recursion, then you end up counting grandchildren twice, great-grandchildren four times, and so on. The recursion is already done by EnumChildWindows; just use it.

    void DoSomethingToWindowTree(HWND hwndRoot)
    {
     // first do it to the window passed in
     DoSomething(hwndRoot);
     // now do it to all the descendants (children, grandchildren, etc.)
     EnumChildWindows(hwndRoot, DoSomethingHelper, 0);
    }
    
    BOOL CALLBACK DoSomethingHelper(HWND hwnd, LPARAM lParam)
    {
     DoSomething(hwnd);
     return TRUE;
    }
    
Page 2 of 4 (35 items) 1234