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.
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)
SUMMARY Windows 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. .gif)
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 MSDN
TM 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
9
x 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 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 9
x, 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
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
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
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
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
http://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 9
x 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 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 9
x and Windows NT 4.0. Knowledge Base article Q192055
contains some useful tips you can use if you're working with overlays on Windows
9
x 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
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" (
http://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 9
x and Windows NT
4.0. Windows 2000 fixes some little folder icon inconsistencies you may have encountered
with the Desktop Update in Windows 9
x. 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
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
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
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
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 9
x 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.