February, 2012

  • The Old New Thing

    What was the nature of the feedback that resulted in the change to the highlighting model for Explorer navigation pane?

    • 26 Comments

    Gabe wanted to know the nature of the feedback that resulted in the change to Explorer navigation pane.

    Historically, Explorer had a navigation pane that contained a folder tree, and the navigation pane could be toggled on and off. From observations and usability studies, we observed that users in general found this toggling burdensome. People liked the folder tree as a form of browsing, but they didn't like the fact that the folder tree kept changing as they navigated through the system. In other words, they liked the fact that they could change the folder tree by expanding and collapsing nodes, but they wanted to be in control of the expansion. They didn't want the folder tree changing autonomously. The instability of the navigation pane came up repeatedly as a complaint. (A conclusion you can draw from these results is that most users do not use the folder tree to determine their current location; they use the address bar.)

    It's the same attitude I have toward a previous version of the GPS-enabled mapping system I had on my laptop. When the GPS is plugged in and working, the map centers on my current location. I can manipulate the map, zooming out for a better overview of the area, scrolling around to see what's nearby, but every three seconds, the mapping software automatically recenters the map on my current position. And then I shake my fist at the computer. (The newer version lets me disable automatic recentering.)

    Based on the feedback collected by usability research, the navigation pane in Windows 7 by default does not auto-synchronize the folder tree with your current location. Nodes expand and collapse only under your explicit command. Users who preferred the old model where the folder tree always synchronized with your current position (and who were okay with all the movement and jumping around) could return to the old behavior with a configuration switch.

    There was a lot of experimentation regarding where to put the configuration switch. Putting the switch as a top-level command was too in-your-face; setting it as a right-click option was convenient enough. And studies showed that users rarely keep switching back and forth between the two styles. They just set it and forget it.

    After Windows 7 was released, a follow-up usability study returned positive results and confirmed the value of the new design.

    Note: I am not the one who conducted this research. I'm just reporting on it, since you wanted to know, and I knew whom to ask.

  • The Old New Thing

    How can I customize which notification icons are displayed by default on a new installation?

    • 22 Comments

    Windows 7 provides a new Unattended Windows Setup setting known as NotificationArea. This setting lets you replace the default Action Center and Battery icons with two icons of your choosing.

    To specify the replacement icons, you need to provide the path to the application which is providing the replacement icon, and you need the GUID for the replacement icon.

    The path you know how to get, because it's where the application was installed. (Note that the application must also be signed.)

    But where do you get the GUID from?

    The GUID is provided by the application as part of the programmatic interface to the notification area. When the application creates its notification icon, it passes a structure known as NOTIFY­ICON­DATA, and one of the fields in that structure is the guidItem. If an application fills in the guidItem and sets the NIF_GUID flag, then that is telling the taskbar (among other things), "Hey, if an unattended setup file specifies this GUID as a replacement icon, that's me."

    Okay, that says where GUIDs are programmatically specified, but where you do you, the system administrator, get the GUID from?

    You get them by asking the application author, "Hi, we want to specify that your notification icon is displayed by default in Windows 7. Can you please tell us what GUID you are using for your notification icon?"

    Given that application authors are always angling for a bonus, they will probably be more than happy to tell you how to give their icon more guaranteed face-time with the user.

    This setting was originally designed for customization by computer manufacturers, and computer manufacturers will probably have a pretty close relationship with the companies that provide shov^H^H^H^Hvalue-added software for their systems.

  • The Old New Thing

    Why was HDS_FILTERBAR added to the common controls if nobody uses it?

    • 19 Comments

    Mike Dunn was curious about the intended purpose of HDS_FILTERBAR.

    The HDS_FILTERBAR style adds a row below the header control consisting of an edit control and a funnel icon. The funnel icon presumably represents a coffee filter, because after all, everybody in the world drinks coffee as much as people in Seattle. (Developers think they're so clever.)

    Mike points out that new features of the common controls were nearly always used by whatever version of Windows or Internet Explorer shipped that new version. The HDS_FILTERBAR style is a notable exception. What happened?

    I believe the HDS_FILTERBAR feature was originally intended for use by Active Directory; my guess is that dialogs like Find Computer would have taken advantage of it. For whatever reason, that feature was cut from Active Directory, which is why you didn't see anybody using it. However, the feature was cut after the code for the feature was already written and checked into the common controls under the style HDS_FILTERBAR.

    The Active Directory team either forgot to tell the Common Controls team, "Hey, you know that feature we asked you to write for us? Yeah, we don't need it after all," or they did, and the Common Controls team said, "Well, we already wrote it, and we don't want to take the risk that removing it won't introduce a bug, so we'll just leave it in. Maybe somebody else can find a use for it."

    The result was a feature in the header control that nobody used. And since nobody used it, I wouldn't be surprised if it's a little buggy. (We already know that it's more than little ugly.)

  • The Old New Thing

    Why does Windows keep showing the old indirect strings even after I update the binary?

    • 30 Comments

    If your application uses indirect localized string resources, and you update the application, you may find that Windows keeps using the old string from the previous version of the application.

    For example, suppose that you set the localized name for a shortcut to @C:\Program Files\Contoso\Contoso.exe,-1, and in version 1 of your program, you have

    LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Document Services"
    END
    
    LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Dokumentdienste"
    END
    

    For version 2, your marketing team decides that the program should really be called Contoso Document System, so you change the resource file to read

    LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Document System"
    END
    
    LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Dokumentsystem"
    END
    

    The user upgrades to version 2 of your program, but the shortcut on the Start menu still reads Contoso Document Services. What's going on?

    The shell keeps a cache of indirect localized strings because loading a DLL just to read a string out of it is pretty expensive. This cache is keyed by the string location specifier, and since your string location specifier hasn't changed from its previous value of @C:\Program Files\Contoso\Contoso.exe,-1, the shell continues using the value it stored away in its cache, which if the user had previously been using version 1 of your program, is the string Contoso Document Services.

    Some people, having discovered this behavior, have tried to go in and tinker with the shell's internal cache of indirect localized strings, but such a technique is doomed to failure because the location of that cache changes pretty regularly, and besides, it's an internal implementation detail. (And even if you find it and manage to fiddle with it, you only fix the problem for the current user. Other users will still have the stale cache entry.)

    The best solution is to treat indirect strings as locked: Once you decide what a string should say, you can't change it. When you issue version 2 of Contoso.exe, you can create a second string

    LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Document Services" // shortcuts from version 1.0 use this
    2 "Contoso Document System" // shortcuts from version 2.0 use this
    END
    
    LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL
    STRINGTABLE
    BEGIN
    1 "Contoso Dokumentdienste" // shortcuts from version 1.0 use this
    2 "Contoso Dokumentsystem" // shortcuts from version 2.0 use this
    END
    

    and have the installer for version 2.0 create a shortcut whose indirect localized string specifier is

    @C:\Program Files\Contoso\Contoso.exe,-2
    

    I admit that this method is rather clumsy and requires more attention on the part of the developer. Everybody wants the "cheap" way out, where the definition of "cheap" is not "cheapest for the customer" but rather "cheapest for me, the developer, because there's a new episode of Doctor Who tonight and I don't want to miss it."

    We saw last time that the format for indirect localized string resources has room for a comment. And it's the comment that we can take advantage of here. The shell uses the entire string location specifier as the key for its cache lookup, and that string includes the comment. Therefore, if you simply change the comment, that results in a cache miss, and the shell will go and re-fetch the string.

    @C:\Program Files\Contoso\Contoso.exe,-1;v2
    

    By appending a ;v2 to the string, you made it different from its predecessor, which means that the string cached by the predecessor won't be used.

    As I noted, this is cheap for the developer, but not necessarily cheap for the customer. Suppose the customer copied the shortcut to Contoso version 1 to their desktop, then upgraded to version 2. The upgrade replaces the shortcut in the Start menu, but the copy on the desktop remains unchanged. You now have a shortcut on the desktop whose indirect string is

    @C:\Program Files\Contoso\Contoso.exe,-1
    

    and a shortcut on the Start menu whose indirect string is

    @C:\Program Files\Contoso\Contoso.exe,-1;v2
    

    Since the shortcut on the desktop was created while version 1 was still installed on the computer, its name will read Contoso Document Services because that was the contents of string 1. On the other hand, the shortcut on the Start menu will read Contoso Document System because its use of the ;v2 forced the shell to go back and look again, and this time it sees the revised string. So far so good.

    But then the user does something which causes the cache to be pruned, like, say, changing their UI language to German. The shell says, "Okay, the UI language changed, I need to go reload all these indirect strings because MUI is going to change them to the new language." The shell sees the shortcut on the Start menu, reads string 1 out of Contoso.exe, and gets Contoso Dokumentsystem. The shell then sees the shortcut on the desktop, reads string 1 out of Contoso.exe, and gets... Contoso Dokumentsystem. Not Contoso Dokumentdienste.

    Notice that the name of the shortcut on the desktop was silently upgraded to Contoso version 2.

    Even if the user changes the language back to English in an attempt to get things back to the way they were, it won't work. The shell sees the shortcut on the Start menu, reads string 1 out of Contoso.exe, and gets Contoso Document System. The shell then sees the shortcut on the desktop, reads string 1 out of Contoso.exe, and gets Contoso Document System, not Contoso Document Service. The original string from the first version of Contoso.exe is already gone; the only way to get it back is to reinstall Contoso version 1.

    But at least you didn't miss your TV show.

    Bonus chatter: The one case I can think of where the cheap way out is acceptable is when you are issuing a prerelease version. For your prerelease versions, you can append ;prerelease build xxxxx to your string location specifier (where xxxxx is the build number), so that each time the user upgrades to a new build, the string is reloaded from scratch. This still has the same problem described above if the user has data left over from a previous build, but since it's a prerelease build, you can just declare that as not a supported configuration.

  • The Old New Thing

    What does the minus sign in indirect localized string resources mean?

    • 7 Comments

    The syntax for indirect localized string resources is @filename,-stringId, optionally followed by a semicolon and a comment. A customer wanted to know what the minus signs stands for.

    The minus sign doesn't "stand for" anything. It's just part of the syntax. It's like asking what the semicolon at the end of a C statement stands for. It doesn't stand for anything; it's just part of the rules for C statements. (And if the minus sign has to stand for something, what does the comma stand for?)

    Okay, so maybe the question was really "Why does the syntax for indirect localized strings include a minus sign? Isn't the comma enough?"

    From a parsing standpoint, the comma is enough. The syntax for indirect strings was influenced by the syntax for icon locations, which also takes the form filename,number. We saw some time ago that the number after the comma can be positive or negative or zero. If positive or zero, it specifies the zero-based icon index. If negative, then it specifies the (negative of the) icon ID.

    The indirect string syntax follows the same pattern, except that they don't support string indices. (As we saw earlier when we studied the format of string resources, a null string is indistinguishable from no string at all, which makes string indices largely meaningless since you can't tell whether a null string should be counted towards the index or not.) Since the only thing supported is IDs, and IDs are expressed as negative values, the first thing after the comma is always a minus sign.

    Next time, we'll take a closer look at that comment field.

  • The Old New Thing

    Instead of creating something and then trying to hide it, simply don't create it in the first place

    • 16 Comments

    A customer had a question, which was sort of I bet somebody got a really nice bonus for that feature in reverse.

    A customer is asking if there is a way to programmatically control the icons in the notification area.

    Specifically, they want the setting for their notification icon to be "Only show notifications" rather than "Show icon and notifications" or "Hide icon and notifications."

    Icons Behaviors
    Power
    Show icon and notifications
    Fully charged (100%)
    Network
    Show icon and notifications
    Fabrikam Internet access
    Volume
    Show icon and notifications
    Speakers: 10%
    Contoso Resource Notification
    Only show notifications
    No new resources found.

    It's a good thing the customer explained what they wanted, because they started out asking for the impossible part. Arbitrary control of notification icons is not programmatically exposed because all the awesome programs would just force themselves on. But they clarified that what they really want is a way to reduce the visibility of their icon so it displays only when a notification is being shown.

    And there's a way to do that, and it doesn't involve having to programmatically configure anything.

    If you don't want your notification icon to appear in the notification area, then don't show your notification icon in the first place unless you have a notification.

    • When your program starts, don't call Shell_Notify­Icon(NIM_ADD). Since you don't call the function, you don't get a notification icon.
    • When you want to display a notification, call Shell_Notify­Icon(NIM_ADD).
    • When the situation that calls for the notification has passed, call Shell_Notify­Icon(NIM_REMOVE).

    In other words, use the notification icon in the manner it was intended.

    It's sad that notification icon abuse has become so popular (and application frameworks make it so easy to create an abusive notification icon) that people forget how to create a well-behaved notification icon. Instead, they start with the abusive method, and then try to figure out how to make it less abusive.

  • The Old New Thing

    Why don't music files show up in my Recent Items list?

    • 19 Comments

    If you double-click a music file, it doesn't show up in your Recent Items list. What's so special about music files?

    The technical reason is that the file types are registered with the FTA_No­Recent­Docs flag, which means that they don't show up in the Recent Items list (formerly known as Recent Documents), and they don't show up in the Recent or Frequent section of Windows Media Player's Jump List.

    Okay, fine, but that's like answering "Why is there a door here?" with "Because the blueprints said that there should be a door there." You really want to know why the architect decided to put a door there.

    The reason why music files are not placed in the Recent Items list is that individual songs are not that interesting there. If you spend an hour listening to music, that will fill up your Recent Items list and push out everything else. The list ends up simply telling you about the thing you just finished doing. Not all that useful.

    Therefore, Windows Media Player excludes individual music files from the Recent Items list. Instead, it puts artists and albums in the Jump List, higher-level information that they figure is more interesting than just a list of the songs you just heard.

  • The Old New Thing

    What's the difference between Text Document, Text Document - MS-DOS Format, and Unicode Text Document?

    • 16 Comments

    Alasdair King asks why Wordpad has three formats, Text Document, Text Document - MS-DOS Format, and Unicode Text Document. "Isn't at least one redundant?"

    Recall that in Windows, three code pages have special status.

    1. Unicode (more specifically, UTF-16LE)
    2. CP_ACP, commonly known as the ANSI code page, although that is a misnomer
    3. CP_OEM, commonly known as the OEM code page, although that too is a misnomer.

    Three text file formats. Three encodings. Hm... I wonder...

    As you might have guessed by now, the three text file formats correspond to the three special code pages. Now it's just a matter of deciding which one matches with which. The easiest one is the Unicode one; it seems clear that Unicode Text Document matches with Unicode. Okay, we now have to figure out how Text Document and Text Document - MS-DOS Format map to CP_ACP and CP_OEM. But another piece of the puzzle is pretty clear, because MS-DOS used the so-called OEM code page. Therefore, by process of elimination, Text Document corresponds to CP_ACP.

    Now that we have puzzled out what the three text formats correspond to, we can address the question "Isn't at least one redundant?"

    Michael Kaplan explained that ACP and OEM are (usually) different. And neither is the same as Unicode. So in fact all three are (usually) different.

    In the United States, the so-called ANSI code page is code page 1252, the so-called OEM code page is code page 437, and Unicode is code page 1200. Here's the string résumé expressed in each of the three encodings.

    Description Encoding Code page
    (en-us)
    Bytes
    Text Document CP_ACP 1252 72 E9 73 75 6D E9
    Text Document - MS-DOS Format CP_OEM 437 72 82 73 75 6D 82
    Unicode Text Document UTF-16LE 1200 FF FE 72 00 E9 00 73 00
    75 00 6D 00 E9 00

    Three encodings, three different files. No redundancy.

  • The Old New Thing

    How do I find out which process has a file open?

    • 24 Comments

    Classically, there was no way to find out which process has a file open. A file object has a reference count, and when the reference count drops to zero, the file is closed. But there's nobody keeping track of which processes own how many references. (And that's ignoring the case that the reference is not coming from a process in the first place; maybe it's coming from a kernel driver, or maybe it came from a process that no longer exists but whose reference is being kept alive by a kernel driver that captured the object reference.)

    This falls into the category of not keeping track of information you don't need. The file system doesn't care who has the reference to the file object. Its job is to close the file when the last reference goes away.

    You do the same thing with your COM object reference counts. All you care about is whether your reference count has reached zero (at which point it's time to destroy the object). If you later discover an object leak in your process, you don't have a magic query "Show me all the people who called AddRef on my object" because you never kept track of all the people who called AddRef on your object. Or even, "Here's an object I want to destroy. Show me all the people who called AddRef on it so I can destroy them and get them to call Release."

    At least that was the story under the classical model.

    Enter the Restart Manager.

    The official goal of the Restart Manager is to help make it possible to shut down and restart applications which are using a file you want to update. In order to do that, it needs to keep track of which processes are holding references to which files. And it's that database that is of use here. (Why is the kernel keeping track of which processes have a file open? Because it's the converse of the principle of not keeping track of information you don't need: Now it needs the information!)

    Here's a simple program which takes a file name on the command line and shows which processes have the file open.

    #include <windows.h>
    #include <RestartManager.h>
    #include <stdio.h>
    
    int __cdecl wmain(int argc, WCHAR **argv)
    {
     DWORD dwSession;
     WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 };
     DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
     wprintf(L"RmStartSession returned %d\n", dwError);
     if (dwError == ERROR_SUCCESS) {
       PCWSTR pszFile = argv[1];
       dwError = RmRegisterResources(dwSession, 1, &pszFile,
                                     0, NULL, 0, NULL);
       wprintf(L"RmRegisterResources(%ls) returned %d\n",
               pszFile, dwError);
      if (dwError == ERROR_SUCCESS) {
       DWORD dwReason;
       UINT i;
       UINT nProcInfoNeeded;
       UINT nProcInfo = 10;
       RM_PROCESS_INFO rgpi[10];
       dwError = RmGetList(dwSession, &nProcInfoNeeded,
                           &nProcInfo, rgpi, &dwReason);
       wprintf(L"RmGetList returned %d\n", dwError);
       if (dwError == ERROR_SUCCESS) {
        wprintf(L"RmGetList returned %d infos (%d needed)\n",
                nProcInfo, nProcInfoNeeded);
        for (i = 0; i < nProcInfo; i++) {
         wprintf(L"%d.ApplicationType = %d\n", i,
                                  rgpi[i].ApplicationType);
         wprintf(L"%d.strAppName = %ls\n", i,
                                  rgpi[i].strAppName);
         wprintf(L"%d.Process.dwProcessId = %d\n", i,
                                  rgpi[i].Process.dwProcessId);
         HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                                       FALSE, rgpi[i].Process.dwProcessId);
         if (hProcess) {
          FILETIME ftCreate, ftExit, ftKernel, ftUser;
          if (GetProcessTimes(hProcess, &ftCreate, &ftExit,
                              &ftKernel, &ftUser) &&
              CompareFileTime(&rgpi[i].Process.ProcessStartTime,
                              &ftCreate) == 0) {
           WCHAR sz[MAX_PATH];
           DWORD cch = MAX_PATH;
           if (QueryFullProcessImageNameW(hProcess, 0, sz, &cch) &&
               cch <= MAX_PATH) {
            wprintf(L"  = %ls\n", sz);
           }
          }
          CloseHandle(hProcess);
         }
        }
       }
      }
      RmEndSession(dwSession);
     }
     return 0;
    }
    

    The first thing we do is call, no wait, even before we call the Rm­Start­Session function, we have the line

     WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 };
    

    That one line of code addresses two bugs!

    First is a documentation bug. The documentation for the Rm­Start­Session function doesn't specify how large a buffer you need to pass for the session key. The answer is CCH_RM_SESSION_KEY+1.

    Second is a code bug. The Rm­­StartSession function doesn't properly null-terminate the session key, even though the function is documented as returning a null-terminated string. To work around this bug, we pre-fill the buffer with null characters so that whatever ends gets written will have a null terminator (namely, one of the null characters we placed ahead of time).

    Okay, so that's out of the way. The basic algorithm is simple:

    1. Create a Restart Manager session.
    2. Add a file resource to the session.
    3. Ask for a list of all processes affected by that resource.
    4. Print some information about each process.
    5. Close the session.

    We already mentioned that you create the session by calling Rm­Start­Session. Next, we add a single file resource to the session by calling Rm­Register­Resources.

    Now the fun begins. Getting the list of affected processes is normally a two-step affair. First, you ask for the number of affected processes (by passing 0 as the nProcInfo), then allocate some memory and call a second time to get the data. But this is just a sample program, so I've hard-coded a limit of ten processes. If more than ten processes are affected, I just give up. (You can see this if you ask for all the processes that have open handles to kernel32.dll.)

    The other tricky part is mapping the RM_PROCESS_INFO to an actual process. Since process IDs can be recycled, the RM_PROCESS_INFO structure identifies a process by the combination of the process ID and the process creation time. That combination is unique because two processes cannot have the same ID at the same time. We open the handle to the process via its ID, then confirm that the start times match. (If not, then the ID refers to a process that exited during the time we obtained the list and the time we actually looked at it.) Assuming it all matches, we get the image name and print it.

    And that's all there is to enumerating all the processes that have a particular file open. Of course, a more expressive interface for managing files in use is IFileIsInUse, which I mentioned some time ago. That interface not only tells you the application that has the file open (in a friendlier format than just an executable path), you can also use it to switch to the application and even ask it to close the file. (Windows 7 first tries IFileIsInUse, and if that fails, then it goes to the Restart Manager.)

  • The Old New Thing

    Why does the DrawIcon function draw at the default icon size?

    • 17 Comments

    Miral wondered why the Draw­Icon function draws at the default icon size instead of respecting the actual icon size. After all, if you loaded a nonstandard-sized icon via Load­Image, then presumably you want to use that nonstandard size.

    The question is one of those types of questions that fails to understand history, like asking why NASA didn't send the space shuttle to rescue the Apollo 13 astronauts.

    At the time the Draw­Icon function was written, the Load­Image function didn't exist, and wouldn't exist for over a decade. The Load­Image function showed up in Windows 95, but Windows was drawing icons long before then, and for a long time, the only way to load icons was with the Load­Icon function, which always loaded icons at their default size. When the ability to create nonstandard-sized icons was added, you then had the question of how to draw them. Code which relied on the fact that all icons were the same size would call Draw­Icon expecting the result to be a 32×32 image (or whatever your icon size was). If you drew it at its actual size, you would either have this L-shaped "hole" in the application (if the actual size was smaller), or you would have an icon that overflowed some other part of the application. Either way you lose.

    Therefore, Draw­Icon always draws at the standard icon size. Think of it as Draw­Icon­Back­Compat. If you are a fancy new application that can handle icons at nonstandard sizes, then use Draw­Icon­Ex and don't pass the DI_DEFAULT­SIZE flag.

    Bonus chatter: The documentation states that the DI_COMPAT has no effect. Presumably it had an effect in some previous version of Windows?

    In Windows 95, if you used the LoadCursor to load a standard cursor (like, say, IDC_ARROW), but the standard arrow cursor was customized by the user, Windows would draw the customized cursor. Passing the DI_COMPAT flag forced the standard arrow cursor to be drawn. So far as I can tell, nobody ever passed that flag.

    Update: My claim that nobody passed that flag is incorrect. The Draw­Icon function itself passed that flag (and still does today, even though it no longer does anything).

Page 1 of 3 (21 items) 123