This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

MIND

Windows 2000 UI Innovations: Enhance Your User's Experience with New Infotip and Icon Overlay Shell Extensions

Dino Esposito

This article assumes you're familiar with Shell Programming, ATL
Level of Difficulty     1   2   3 

Code for this article: Windows2000UI.exe (303KB)

SUMMARYWindows 2000 includes some helpful new UI features you can customize and implement in your own applications. In this article you'll see how to provide infotips for files, after making the appropriate registry entries. Then create a custom column handler extension, resulting in a new column for the Explorer's Details view. In order to further extend the shell, additional UI goodies will also be examined and implemented including: search handlers, cleanup handlers, folder customizations using property sheet handlers and icon overlays, and context menu shell extensions. All the code samples are rolled up into a handy package which we've named, by tradition, ShellToys.

I t seems inevitable that the advanced UI features of Microsoft® Windows® 2000 will be a hot topic of discussion. Some folks are already complaining that Microsoft invested too many resources in developing a cool interface, and not enough in some other parts of the operating system.
      I don't agree. There are several new UI features that I'm especially pleased with. For instance, I'm happy that scripting¯and Windows Script Host (WSH) in particular¯is going to replace the command prompt. And I'm excited about other Windows 2000 enhancements that will provide your future apps with a much better UI and a tighter integration with the system shell.
      Among the shell extensions I'll discuss in this article are infotips and other folder enhancements, search handlers, icon overlay, and the Quick Launch toolbar. Throughout the article, I'll implement a few of the new UI features (and some that were introduced previously) to enrich the functionality of the system shell. To carry on an MSJ tradition, I've called them ShellToys.

Quick Tour of Windows Shell Extensions

      A shell extension is a COM inproc server that Explorer invokes in response to certain shell-wide events. There are a few tasks that Explorer knows can be accomplished in collaboration with user-defined applications. Before starting any of these tasks, it looks for these registered modules and loads them. These modules are conceptually equivalent to callback functions¯a glorious old feature introduced with Windows 3.1 that a whole generation of programmers cut their teeth on.
      A shell extension needs to implement a couple of COM interfaces: one for providing the specific behavior and one for initialization purposes. In addition, shell extensions must follow a precise schema for registration. They must create the correct registry entries in the appropriate place so Explorer can locate and load them when necessary.
       Figure 1 lists all the types of shell extensions available today, the minimum shell version each requires, the involved interfaces, and a brief description. For example, given a certain class of files, a shell extension allows you to add new items to the context menu or insert an additional property page to the standard Properties dialog. Shell extensions are mostly written in C++ using ATL, although you can use any tool that allows you to write COM components.
      If you're a fan of Visual Basic®, things are only slightly more difficult. At the moment, you won't find special wizards or ready-made designers to help you write components that support particular COM interfaces. You'll have to write a type library describing the interface, then reference the type library in your project and use the Implements keyword to declare support for only that interface. A working demo of Visual Basic shell extensions can be found on the Visual Basic 6.0 CD.
      Shell extensions are often associated with classes of documents. This means that the behavior they introduce applies only to files with a certain extension. For more detailed information about the basics of Windows shell extensions, please refer to the MSDNTM documentation. Additional coverage is available in my book, Visual C++® Windows Shell Programming (Wrox Press, 1998).
      Before I continue, I should point out that not all the features I'll cover here are completely new. Many of them were already introduced with the Desktop Update¯a separate shell update available both for Windows 9x and Windows NT® 4.0. The Desktop Update shipped with Microsoft Internet Explorer 4.0 and Windows 98. Note that the Desktop Update is not part of Internet Explorer 5.0. So if you want to install it on Windows NT 4.0, you need to install Internet Explorer 4.0 first, making sure you select the Desktop Update option. Internet Explorer 5.0 will upgrade an existing Desktop Update on Windows NT 4.0 and Windows 95, but will not install it from scratch.

The Infotip Shell Extension

      A typical shell extension is meant to help a user work with a category of documents. Let's suppose you're mainly interested in BMP files. The Desktop Update (shell version 4.72 or higher) offers a thumbnail-size preview of the file once you select it from the file list, but there is no system-provided way to know about the size of the image and its colors. If your application manipulates bitmaps extensively, then a few shell extensions can be really helpful for you and your users.
      The first solution that comes to mind is to add a new property page for BMP files. Actually, this isn't a very elegant approach since the user must right-click, select the Properties menu item, and choose the correct tab to read the information. A much better way of getting the same result is by using infotips.
      An infotip is a short bit of text that a tooltip control displays when the mouse hovers over a file of a certain type. This text snippet is meant to provide details about the content of that particular file. The infotips feature is automatically enabled for Microsoft Excel and Word documents. These infotips tell you the title, author, and subject of the file. The infotip feature is the result of a particular type of shell extension that differs from the others in its registration schema. Still, an infotip extension receives a reference to the file currently selected and can process it to extract the information needed.
Figure 2 An Infotip for BMP Files
Figure 2 An Infotip for BMP Files

       Figure 2 shows an infotip extension for BMP files. To build this extension you need to create a COM inproc server that implements IQueryInfo and IPersistFile. IQueryInfo is required to provide the runtime text to the shell. IPersistFile is used by Explorer to let the extension know about the specific file currently under the mouse pointer. I've defined a couple of minimal ATL base classes (IQueryInfoImpl.h and IPersistFileImpl.h) derived from those interfaces and use them to build more specialized classes (see Figure 3). You can also see the declaration for the coclass that embodies this shell extension.
      In the m_szFile member variable, the Load method of IPersistFile stores the name of the file that the extension is working on. This method gets silently invoked by Explorer during the initialization process of the extension. IQueryInfo includes only two functions, one of which (GetInfoFlags) is not yet supported and must simply return E_NOTIMPL. Actually, once you get the minimal implementation that IQueryInfoImpl.h and IPersistFileImpl.h provide, writing an infotip extension is as easy as building a new ATL inproc object and filling out the body of the sole IQueryInfo::GetInfoTip method.

  HRESULT CBmpTip::GetInfoTip(DWORD dwFlags, LPWSTR* ppwszTip) 
  

 

      The dwFlags argument is currently unused, while ppwszTip is a pointer to a Unicode string buffer designed to return the runtime text for the file. It's worth noticing that the ppwszTip buffer must be allocated using the standard shell memory allocator. This buffer is allocated by the application and freed by the shell. To ensure that everything happens in a thread-safe way, use SHGetMalloc to get the pointer to the shell's memory allocator¯an object implementing IMalloc. Then, use IMalloc's Alloc method to allocate the memory needed to hold the Unicode representation of the infotip text.
      The following code snippet shows how the shell extension retrieves and returns the text:

  CComBSTR bstrInfo; GetBitmapInfo((CComBSTR *)&bstrInfo); *ppwszTip =
  
(WCHAR*) m_pAlloc->Alloc( (bstrInfo.Length() +1) * sizeof(WCHAR)); if (*ppwszTip)
wcscpy(*ppwszTip, (WCHAR*)(BSTR)bstrInfo);

 

GetBitmapInfo is an internal member that simply prepares a string with the dimensions and colors of the image. To get this information, the function opens the BMP file and reads the bitmap header.

Registering an Infotip Extension

      The infotip extension follows a nonstandard schema for registration. In general, file-based extensions create their own subtree under the registry key that identifies the file class. For example, the class name for .ext files is the content of the default value of the .ext registry key under HKEY_CLASSES_ROOT. However, infotips for BMP files must create the following entries:

  HKCR \.bmp \shellex \{00021500-0000-0000-C000-000000000046} 
  

 

      The default value for this key must evaluate to the CLSID of the COM object implementing the shell extension. The CLSID specified here in the registry path is the IID of the IQueryInfo interface rather than the file class of BMP files. That's why this registration schema is nonstandard. On the other hand, this schema applies whichever application is registered to handle BMP files. In fact, applications that register themselves as the default handler for a certain class of files may also change the class name, invalidating all the registered extensions. In particular, the class name for BMP files is Paint.Picture if Microsoft Paint is the default handler. It may be different if you use another program to edit and view bitmaps.
      Shell extensions aren't a new feature of Windows 2000, so the following warning shouldn't surprise many of you. Anyway, to work properly under Windows NT and Windows 2000, shell extensions must be registered and approved by the system administrator. Many developers undervalue this point since they mostly log on to their Windows NT boxes as administrator. In this case, as well as under Windows 9x, there's no real need to have extensions approved. Regardless, here's the registry entry responsible for extension approval:

  HKLM \SOFTWARE \Microsoft \Windows \CurrentVersion \Shell Extensions \Approved
  

 

Under this key, create a new string value with the name of the shell extension CLSID and assign it a description text.

Storing Infotip Settings

      There are countless situations for infotip use, including custom documents and those with native file classes such as DLL and EXE. Figure 4 shows the final effect of a shell extension you can download from the link at the top of this article. This infotip provides the list of the DLLs that are statically linked to the given EXE or DLL module. Notice that the same string displays on the Windows Explorer status bar.
Figure 4 Infotip for DLL and EXE Files
Figure 4 Infotip for DLL and EXE Files

      Visual Studio® 6.0 comes with a nice tool called Dependency Walker that's capable of providing this information and much more. It gives a complete snapshot of the executable header. However, the Dependency Walker is a separate executable that you need to run from the context menu.
      If you just need to know the main DLLs that a certain executable depends upon, then you should find my extension really handy. To determine the list of the statically linked libraries, my code makes use of the ImageHLP API that Matt Pietrek repeatedly featured in his Under the Hood columns in MSJ. In particular, I'll utilize BindImageEx to request the virtual address of each function that is imported. To get this, BindImageEx binds to the executable image and invokes a callback function for each imported library and function. All you need to do at this point is write a callback that simply concatenates the module names into a string.
      The dependency list is useful, but it's probably not the information you want displayed each time your mouse hesitates over a certain file item with a DLL or EXE extension. Wouldn't it be great if you could store a boolean flag somewhere to enable and disable this feature? You could use either the registry or an INI file to store this information, but one problem remains: how do you toggle that flag interactively? Using the Registry Editor or Notepad is fine, but a bit impractical. If the shell extension was part of an application, then the preferences dialog of the application would also be an excellent solution.

The Details View

      In Windows 2000, there are five possible ways of viewing the contents of a file folder. (This doesn't apply to virtual folders and namespace extensions.) There are four traditional modes: Large icons, Small icons, List, and Details. The fifth, thumbnail mode, can be quickly described as very large icons. A thumbnail takes a square of 100?100 pixels to display the regular icon for the file object. This view mode is great for folders full of images and documents that include a preview¯such as metafiles, graphic files, and Microsoft Office documents for which the preview picture option is turned on.
      Of all the possible view modes, Details is the one that provides a significant amount of information for all the files. In all other cases, additional elements such as size, type, description, and author are available only through infotips. In a Details view, the folder view is organized in columns. Before Windows 2000, typical columns were Name, Size, Type, and Modification Date, with the possibility of adding a fifth column for file attributes such as hidden, read-only, and so on. Windows 2000 introduces many more predefined columns and even the possibility of creating your own. If you right-click on the caption of any column, you'll get a context menu whose items are a subset of all the available columns. You can turn any column on or off, with the obvious exception of Name.
Figure 5 Columns of the Details View
Figure 5 Columns of the Details View

      By selecting the More option (see Figure 5), you can display the whole list of available columns. Three columns are particularly exciting to me: Created, Author, and Module Version. Created shows the original creation date of the file or folder. Author returns the name of the person who signed the document according to the content of the SummaryInformation header for compound files. Especially within a folder full of Office documents, identifying at a glance those written by someone in particular is really useful. Moreover, once you display a new column you can also sort the folder content by that column, resulting in an even more useful feature. Note that the Author information is available only for documents stored as compound files that embed a SummaryInformation header. Aside from some of the Microsoft Office document formats (Word, Microsoft Excel, or PowerPoint®), not many documents export a SummaryInformation block. FlashPix images are an interesting exception.
Figure 6 The Module Version Column
Figure 6 The Module Version Column

      Finally, the long-awaited Module Version column contains the version number of an executable. Figure 6 shows this column enabled within the system32 folder. Through the column chooser dialog, you can set a default width for the new column and select its position. Reordering columns is a feature that applies on a per-folder basis, but you can always make all the folders look the same by adjusting the features on a certain folder and then clicking the Like Current Folder button in the View tab of the Folder Option dialog box. There's also a folder setting called "Remember each folder's settings" that allows you to control whether global folder options apply to each single folder.
      What I've said so far applies only to file folders; namely, to folders that have a corresponding file system directory. Other types of folders (such as namespace extensions) define the columns themselves when not providing a completely different, non-column-based view. There are a few exceptions to this, including My Documents and Favorites. They are namespace extensions, but since their content mirrors the content of regular file folders, they provide a standard tabular view and respect the current folder settings you have selected.
      Developers who write column-based namespace extensions should provide a way to allow column customization via a context menu that appears by right-clicking on the column's caption.

Defining Custom Columns

      Windows 2000 allows you to define custom columns and adds them to the list shown in Figure 5. To define a new column, you must write and register a new type of shell extension called a column handler. In most cases, a column is meant to provide special information for a certain file class¯for example, dimensions and palette size for BMP images. However, column handlers aren't bound to file classes, but are a feature of the Folder object. In other words, you won't register a column handler under the bitmap file class subtree, but under the Folder subtree. The extension code then distinguishes the files that it knows how to handle and returns proper information for those files only.
      A column handler extension requires the implementation of a single COM interface: IColumnProvider. This makes this type of extension slightly different from others that require IPersistFile or IShellExtInit for initialization purposes. (This is due to the fact that column handlers don't apply to file classes.)

A Dimensions Column for BMP Files

      Let's start a new ATL project and insert a brand new simple object whose progID will be BmpCol.BmpColInfo. Figure 7 contains the core source code for this extension. IColumnProvider exposes three methods: Initialize, GetColumnInfo, and GetItemData. Initialize is called when the shell sets the extension to work on a certain folder. The name of the folder is passed within the SHCOLUMNINIT structure that the method receives. In most cases, there's nothing you need to do with this information. Anyway, my standard ATL class (IProviderColumnImpl.h) stores the folder name in a member variable. Unless the calling folder tells you that you need to take special action, you can forget about this method and concentrate on GetColumnInfo, which is usually the most important method.

  HRESULT GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci); 
  

 

      GetColumnInfo passes on to the shell as much information as possible about the column, including the caption, a description, the type of the data it manages, and how you want the user to interact with that data. All this information contributes to fill up the SHCOLUMNINFO structure that the function receives.
      Since a column handler can manage more than one column, you should provide a unique pair of IDs to unequivocally identify the column. These IDs are called format ID (FMTID) and property ID (PID) and form the SHCOLUMNID data structure. A FMTID is a GUID that identifies a property set, while the PID is the index of a specific property within that set. The FMTID/PID pair is an instance of a more general mechanism that implements persistent property sets within the COM structured storage.
      A typical property set is the Summary Information Property Set, whose PIDs refer to Author, Title, Keywords, Revision number, and so forth. The documentation specifies a wide range of existing columns, each identified by a FMTID and PID. You can either create a brand new column with a new FMTID/PID pair or you can associate your own column handler with an existing column.
      For example, suppose you want to display the title of an HTML document within the same Title column that a system-provided column handler utilizes for displaying the title of some Office documents. Create a column handler and then fill the SHCOLUMNID of the column

  psci->scid.fmtid = FMTID_SummaryInformation; psci->scid.pid = 2; 
  

 

where 2 is the PID of the Title property. Your extension is now ready to write under the standard Title column.
      To retrieve the title from a Web page, you don't need to invoke the HTML Document Object Model (DOM). Using a much simpler string search for <title> and </title> would suffice. As a result, you would have two or more column handlers that share the same column, reinforcing the idea of columns as system-wide properties for folder items.
      If the information you want to display on a separate column doesn't relate to any of the existing property sets, nothing prevents you from creating a new property set with one or more properties. A FMTID is just a 128-bit value GUID that you need to somehow generate or obtain. The easiest way is to use the same GUID of the COM object you're writing. ATL makes it available through the _Module global variable:

  psci->scid.fmtid = *_Module.pguidVer; psci->scid.pid = 1; 
  

 

You can use any number to identify the PID. In this case, I choose 1.
      There are several other characteristics you can select for the column. You can choose the text alignment, the type of returned data (text, number, or date), and the default width. Note that you must specify the width in characters instead of using a numerical unit of measurement. Of course, you can also specify the caption of the column (Dimensions in my example) as well as a description.
      Pay attention to the dwIndex argument of GetColumnInfo. It is just a progressive, zero-based number through which the shell enumerates the various columns your handler may provide. In other words, the shell runs pseudocode that looks something like this:

  dwColIndex = 0; while (true) { SHCOLUMNINFO shci; hr = pColumnProvider->GetColumnInfo(dwColIndex++,
  
&shci); if (FAILED(hr)) break; // other shell-specific code }

 

You should check dwIndex against a constant representing the number of different columns you want to provide. If you provide just one column handler, then use something like this:

  if (dwIndex >= 1) return S_FALSE; 
  

 

Otherwise, your module will enter an infinite loop.
      If you want to support multiple columns, remember that GetColumnInfo gets invoked repeatedly until you stop it by returning S_FALSE. Each time it's invoked, you get an increased index to denote a new column. So a plain old switch statement can help you sort this out.

  switch(dwIndex) { case 0: InitColumn_Dimensions(pshci); case 1: InitColumn_Title(pshci);
  
// other code }

 

      Once you properly implement GetColumnInfo, the new column appears at the bottom of the dialog box as soon as you click on the More context menu item. The same dialog can be reached through the Explorer's View | Choose Columns menu item.
      To provide the runtime text for the column, you should also take care of IColumnProvider's GetItemData method. It takes three arguments. The first is a SHCOLUMNID structure that identifies the specific column via a FMTID and a PID. The second is a SHCOLUMNDATA structure that contains information about the specific file for which information should be retrieved. The third argument is an output VARIANT buffer that the extension fills with the display data.
Figure 8 The Dimensions Column for BMP Files
Figure 8 The Dimensions Column for BMP Files

      The sample application in Figure 8 displays the Dimensions column, which contains the width and the height of a BMP file in addition to the bits per pixel determining the image's color depth.

Registering a Column Handler Extension

      A column handler is not bound to a specific file class. This should come as no surprise since a column is more of a folder-related feature than a file-related characteristic. Whether you create a brand new column, or you associate with an existing one, you can use it with more than one file class. For instance, in the previous sample I considered only BMP files, but I could have taken into account other graphic formats, the number of slides of a PowerPoint document, or the word count of a Word file. The only way to quickly discard all the file objects you aren't interested in is to check the file extension (or the fully qualified path name) within the body of GetItemData. This is also demonstrated in the source code shown in Figure 7.
      A column handler is a COM object and a shell extension, hence it requires the usual registration for those types of modules, including the entry under the Approved key. To associate your extension with all the folders, enter the following settings in the ATL registrar script RGS file:

  HKCR { NoRemove Folder { NoRemove Shellex { NoRemove ColumnHandlers { ForceRemove
  
<CLSID> = s 'description' } } } }

 

Don't forget to replace <CLSID> with the actual CLSID value of your component.
      If you define custom columns with a custom FMTID it's important that you document it so that other developers can write their own handlers that show information within the same column.
      While column handlers are one of the most exciting new features of the Windows 2000 user interface, there's much more that's worth exploring.

Search Handlers

      A search handler is a module that integrates with the shell's user interface and allows you to locate objects such as files, printers, and messages. These handlers are available from the Start menu via the Search submenu. (This menu was called Find in previous versions of Windows.) Before Windows 2000, Explorer's Tools menu duplicated the same submenu, providing an alternative way of accessing the same functionality.
      In Windows 2000, Explorer implements its own search panel through a band object. There's no way to add your own search panel unless you write a brand new band object. Band objects were covered by Paul DiLascia in the November 1999 issue of MSJ (see https://www.microsoft.com/msj/1199/bandobj/bandobj.htm). The new Search panel is completely based on Dynamic HTML and constitutes a full replacement for the Find dialog available before Windows 2000. You can run it with the same code you used under Windows 9x or Windows NT:

  ShellExecute(NULL, "find", NULL, NULL, NULL, 0); 
  

 

      Adding a new item to the Search menu is another story. That menu is designed to read entries from the registry under the following key:

  HKLM \SOFTWARE \Microsoft \Windows \CurrentVersion \Explorer \FindExtensions
  

 

      There are two types of menu items: static and dynamic. Static menu items are loaded only when needed, while dynamic extensions are bound to the shell lifecycle, loading during the shell's startup and terminating when the shell process ends. According to the terminology in use, in most cases you only need to write static extensions. Dynamic extensions must be registered under the node I just mentioned. Static extensions must be grouped under a common key called Static, placed underneath FindExtensions.
      What is a search handler, anyway? It's a very simplified type of context menu shell extension. To write a search handler, just write the skeleton of a context menu shell extension. Among other things, this means that you have to implement IContextMenu and IShellExtInit. IContextMenu exposes three functions that add one or more menu items (QueryContextMenu), provide a description for them (GetCommandString), and execute some code in response to the user's clicking (InvokeCommand). For a search handler, only InvokeCommand is needed and the other two functions are simply ignored.
Figure 9 Using the Find Process Search Handler
Figure 9 Using the Find Process Search Handler

       Figure 9 shows a Find Process search handler in action, with its full list of processes running at a certain moment in time. Windows 2000 also supports the ToolHelp API to get system information about the running processes and modules. ToolHelp is supported under Windows 9x, but not under Windows NT 4.0. (Under Windows NT 4.0 you should use an alternative API called PSAPI.) The source code for the search handler shown in Figure 9. It can be found in this month's archive. It includes a DLL that blurs the distinction between the platforms, detects the underlying operating system, and utilizes the proper API. Hence, the handler works on any Win32® platform.

Other Types of Shell Extensions

      The list of new Windows 2000 shell extensions doesn't end here. There are three other types you should consider: shell execution, icon overlay, and cleanup handlers.
      The shell execution extension is a module that exposes the IShellExecuteHook interface and causes your code to be invoked just before a certain command line is processed by the shell via Explorer or the Run dialog box. IShellExecuteHook has nothing to do with the WH_SHELL hook, since your code executes before the target program is launched and always within the shell's address space.
      Windows 2000 is the first release of Windows to fully support icon overlay. There are two interfaces involved with this: IShellIconOverlay and IShellIconOverlayIdentifier. IShellIconOverlay is reserved for namespace extensions that want to display overlays. IShellIconOverlayIdentifier is the main interface for a shell extension that allows you to define custom images to be used as icon overlays for folder items. An icon overlay is a small image that the shell, under certain conditions, automatically draws at the lower-left corner of the icon that represents a folder item. Typical examples are the arrow icon that identifies a shortcut and the hand that holds shared folders. The final icon a user sees is the result of the combined effect of two overlaid icons. This mechanism has been generalized and made open with Windows 2000.
      When drawing an icon for a folder item, Explorer tries to obtain a pointer to IShellIconOverlay from the namespace extension that fuels that particular type of folder. If this interface is present, then the namespace extension is given a chance to use overlays for its custom items. Even if the Windows 2000 Platform SDK documentation doesn't mention it, IShellIconOverlay and IShellIconOverlayIdentifier have actually been around for a while¯since the introduction of the Desktop Update for Windows 9x and Windows NT 4.0. Knowledge Base article Q192055 contains some useful tips you can use if you're working with overlays on Windows 9x and Windows NT 4.0 or earlier.

Cleanup Handlers

      Starting with Windows 98, Microsoft made a utility called Disk Cleanup (see Figure 10) available with the OS. The goal of this system tool is to get rid of unused files by deleting, compressing, or backing them up. To recover disk space, the Disk Cleanup utility cleans a few standard folders such as Recycle Bin, Downloaded Program Files, and Temporary Internet Files.
Figure 10 Disk Cleanup Handler
Figure 10 Disk Cleanup Handler

      By writing a disk cleanup extension, you can add a new entry to the dialog shown in Figure 10 just to manage a specific and well-known set of files that your own application may have created during its activity. Disk Cleanup has a modular structure and is composed of some system-provided handlers, plus you can write and register your own. Each extension implements a few COM interfaces to facilitate the communication with the Disk Cleanup manager. Writing a cleanup extension is just a matter of creating a COM object that exposes the IEmptyVolumeCache2 interface. There are slight differences between a cleanup extension for Windows 98 and one for Windows 2000. Those for Windows 98 must provide IEmptyVolumeCache, while those for Windows 2000 must also provide IEmptyVolumeCache2. IEmptyVolumeCache2 is a superset of IEmptyVolumeCache and just adds the InitializeEx method.
       Figure 11 shows the source code for a very basic cleanup extension that is able to free up to 1MB of space. The standard implementation provides you with message boxes that help you understand how and in what order the various methods are invoked. Note that there's an error in the current MSDN documentation about the prototype of the Deactivate method. As you can probably figure out from the emptyvc.h header file, the correct prototype is:

  STDMETHOD(Deactivate)(DWORD *pdwFlags) 
  

 

      The documentation considers the argument as a DWORD. The Windows 2000-specific InitializeEx method is supposed to provide facilities for localizing the extension. Under Windows 98, the button text, the description, and the display name of the handler are read from the registry. They can be determined by the extension code under Windows 2000. A cleanup handler can show a dialog box that will typically be used to provide a preview of the deletable files. The ShowProperties method is responsible for opening this window. To enable it you should provide button text and set the EVCF_HASSETTINGS flag from within InitializeEx.

Folder Utilities

      In my August 1999 MSJ article, "Logo Requirements and More: Solutions for Writing Great Windows 2000-based Apps" (https://www.microsoft.com/msj/0899/logo/logo.htm), I explained how to assign a custom icon to a folder. This feature has been introduced by the Desktop Update and is also available on Windows 9x and Windows NT 4.0. Windows 2000 fixes some little folder icon inconsistencies you may have encountered with the Desktop Update in Windows 9x. In particular, the custom icons are now displayed on both the left and right Explorer panes and by the shell view object used by the Open/Save dialogs. You can also specify a description for any folder that the shell will display through a tooltip or a label in the Explorer's right pane.
      The key to these UI enhancements is a text file called desktop.ini. Put a file with that name in a folder, mark the folder itself as read-only, and the shell will automatically assign a special meaning to desktop.ini. If the folder is not marked read-only, then desktop.ini is treated as a normal file and its content is not used to enhance the folder appearance. (Note that you can still copy and delete files from a read-only folder.)
      If you right-click within the area of a folder, you can start the "Customize this folder" wizard. Among other things, it allows you to set a background image for the folder and to specify an HTML template for rendering the content. In addition, you can specify a comment for the folder. This comment can be any HTML text that will be saved under a subfolder called Folder Settings with the name comment.htt. In other words, you can embed an entire HTML page in the Explorer's right pane to describe a folder (see Figure 12). Remember that both desktop.ini and the Folder Settings folder are hidden files, so you can't see them unless you've checked the show all files setting in the Folder Options dialog box.
Figure 12 Customizing a Folder
Figure 12 Customizing a Folder

      The customization wizard is the only interactive tool that the shell provides to enhance the folder's user interface. There's no user interface support to help you assign a custom icon and a folder description. To make up for this, let's write a folder's property page handler¯a shell extension that inserts an additional page into the folder's Properties dialog box. This new page will have a tab called Customize and will look like the one in Figure 13.
Figure 13 Property Sheet Handler
Figure 13 Property Sheet Handler

      You can enter a description text and choose an icon to replace the standard folder bitmap. A property sheet shell extension requires you to arrange a dialog template and all the code necessary to put it to work. Plus, you must implement the IShellExtInit and the IShellPropSheetExt interfaces. Actually, this can be resolved by writing two functions: Initialize and AddPages.
      Initialize tells you about the folder whose properties are going to be shown. AddPages lets you add a new property page through the PROPSHEETPAGE structure. Since you're working with one of the Windows 95 common controls, remember to add a call to InitCommonControls before working with the property page data structures. The source code for the shell extension looks in the current directory for a desktop.ini file and extracts its content using an old, faithful API such as GetPrivateProfileString. Typical content looks like this:

  [.ShellClassInfo] Infotip=Contains all the articles I've written for MSJ.
  
IconFile=D:\My Pictures\ICON\Special\msj.ico IconIndex=0

 

      The comment text is shown in two ways. It is the tooltip that appears when the mouse hovers over the directory name in Explorer's right pane, and it's the text displayed under the directory name when the folder is selected. A property sheet shell extension needs to be registered under

  HKCR \Folder \Shellex \PropertySheetHandlers \{CLSID} 
  

 

where {CLSID} is the actual CLSID of the object you've created. Of course, you may have multiple property sheet handlers for each folder. The actual script code used by the ATL registrar component looks like this:

  HKCR { NoRemove Folder { NoRemove Shellex { NoRemove PropertySheetHandlers
  
{ ForceRemove {1F8F343A-1DE0-4B26-97C9-18A39FFC9880} } } } }

 

      According to the shell's organization, a folder is any container of items and objects you may have within the overall namespace. A directory is a special folder that contains files and represents a file-system directory. You can also apply the shell extension to directories only. To do so, just replace the string "Folder" with "Directory" in the previous script.
      Registering the shell extension for all folders will also add the Customize page to some special folders, such as the root folder of any drive. It doesn't apply to namespace extensions such as the My Documents or the Recycle Bin folders. Through the desktop.ini file, you can associate a description with a drive root folder, but you cannot change its icon. Changing the default icon of the root folder means changing the icon of the drive, and this is something that can only be accomplished by tweaking the system registry. Here is the registry path that is responsible for the drive's custom icons:

  HKLM \Software \Microsoft \Windows \CurrentVersion \Explorer \DriveIcons
  

 

Under the DriveIcons key you should create a subkey whose name is the letter of the drive you want to customize. If you want to change the icon of the drive called D, then create a subtree like this:

  ... \DriveIcons \D \DefaultIcon 
  

 

      The default value of the DefaultIcon key must point to a comma-separated string whose first part is the icon file name and whose second part is the icon index. Notice that you can also use the resource ID to identify an icon within an executable module. If the specified index is a negative number, the shell automatically interprets it as a resource ID and attempts to locate the icon whose resource ID equals the modulus of the index. For example,

  mylib.dll,-204 
  

 

points to the icon with a resource ID of 204. Figure 14 shows my D drive and its custom sleeping moon icon.
Figure 14 My D Drive's Custom Icon
Figure 14 My D Drive's Custom Icon

      Employing colorful icons can help identify folders quickly, especially on very structured and large drives. On the other hand, too many custom icons can significantly increase the user's confusion and make it even harder to identify the correct folder. So use this Customize page selectively.

Send To

      Send To is a special folder that Windows 2000 moved under the main folder called Document and Settings and the current user folder. It contains shortcuts to applications that receive the name of the selected folder item on the command line and process it.
      The Send To menu has a couple of special features: it displays a little bitmap near the menu item and unrolls a submenu of options. The tools you need to enable the features in your own extensions have been around for a long time, even though very few commercial applications exploited them. From within a context menu shell extension, there's virtually no difference between adding a single item or a popup menu. Instead of calling InsertMenu or AppendMenu to pass a single item, just create and insert a popup submenu and you're done!
Figure 15 Customizing the Context Menu
Figure 15 Customizing the Context Menu

      There is another functionality I'd like to see available in the folder's context menu: creating a subfolder and opening the command prompt from the folder itself, as shown in Figure 15. By clicking on the New Folder menu item, you'll be presented with a dialog box to accept the name of the child folder. To create a new folder, you can take advantage of a little-known API called MakeSureDirectoryPathExists. It takes a path name and does what its name implies: it makes sure that all the needed directories exist and creates those that are missing. In this way, you could enter a string like

  one\two\three 
  

 

and have the shell extension create a subtree of folders for you.
      As for the command prompt, you should use the Comspec environment variable to get the user's default command prompt. If that is not available, use command.com on Windows 9x or cmd.exe on Windows NT and Windows 2000. Using CreateProcess, you can specify the initial directory. All of this can be done using the code in Figure 16.
      As you can see, each menu item shows up as a little bitmap. This is the result of a combined effort: the Win32 standard owner-draw mechanism and the IContextMenu3 interface. A shell extension that wants to use bitmapped items must implement either IContextMenu3 or IContextMenu2. Both inherit from the standard IContextMenu and, within reason, IContextMenu3 is built on top of IContextMenu2. These interfaces were bound to special versions of the shell, but finally with Windows 2000 they can be considered a native part of the Windows shell. Today's applications must focus on IContextMenu3. In addition to IContextMenu's methods, IContextMenu3 has a function called HandleMenuMsg2. Of all the messages the menu receives from the system, HandleMenuMsg2 allows the extension to process four: WM_INITMENUPOPUP, WM_MENUCHAR, WM_MEASUREITEM, and WM_DRAWITEM. By handling these messages you can draw the menu item yourself and use the bitmap and the fonts that you like. Don't forget to turn on the MF_OWNERDRAW flag when you insert a menu item to be drawn by the shell extension.

About the Source Code

      The FolderExt project you'll find in this month's source code puts all these features together, resulting in ShellToys that make your next Windows 2000-based applications richer and more attractive. The concept of shell extensions is reinforced with Windows 2000 and the new features (infotips, column, cleanup, and search handlers) significantly increase your UI programming power. In this article I've only mentioned the Desktop Update occasionally and I haven't said much about the real-world possibility offered by hypertext templates (HTT). In a future article, I'll dig all this out. Have fun with ShellToys in the meantime!

Dino Esposito is a senior consultant based in Rome. He authored Visual C++ Windows Shell Programming (WROX, 1999) and cofounded www.vb2themax. Reach Dino at desposito@vb2themax.com.

From the March 2000 issue of MSDN Magazine.