Contents
Introduction
Windows 7 has added many neat enhancements to Explorer, and specifically to the Taskbar. One prominent new feature is the jump list. This is a context-menu-like UI element that is associated with an application, and is accessed by right-clicking the application's Taskbar button. This article introduces jump lists, shows how to access the jump list from C++, and demonstrates some simple customizations you can do to your app's jump list.
The sample code is built with Visual Studio 2008, WTL 8.0, and the beta Windows 7 SDK. As with my earlier Vista Goodies articles, I've classified this article as Intermediate because I won't be covering the basics of Win32 and WTL. See my series of articles on WTL if you need to get up to speed on WTL. I also won't be going step-by-step through the Visual Studio wizards, which were also covered in the WTL series. (Those articles show the wizards in VC 2003, but the wizards in 2008 are similar.)
Some important points to note right off the bat:
- "Jump list" is a relatively recent term. During development, Microsoft used the term "destination list," and that is the term that appears in various places in header files (for example the
ICustomDestinationList
interface). - In order for an app's jump list to work correctly, it must be a registered handler for a file type. That doesn't necessarily mean it has to be the default handler, just a handler. We'll see how to register as file type handlers later.
- Registering as a file type can be done in
HKEY_CLASSES_ROOT
(for machine-wide associations), or in HKEY_CURRENT_USER\Software\Classes
(for per-user associations). For the sake of brevity, this article will refer to HKEY_CLASSES_ROOT
, but downloadable the sample code uses HKEY_CURRENT_USER
. This is done because writing to HKEY_CLASSES_ROOT
requires elevation, and I wanted the sample programs to run without needing elevation.
What are Jump Lists?
As mentioned above, you bring up an app's jump list by right-clicking its Taskbar button. If the app is present in the Start menu's list of apps - either by being pinned there, or because it's been used recently - the jump list is also accessible through the Start menu item.
The jump list has two categories of items: destinations and tasks. Destinations are items (usually files) that the application can operate on. This is similar to MRU file lists that some apps maintain on their own. However, with destinations, the app and Windows can work together to manage one central list of files. Also, an app doesn't have to do anything in order to get destinations; Windows will try to figure out on its own which files the app is operating on and use that information to build a jump list. The app can also add files to this list if it wants to.
Tasks are commands, such as "create a new document" or "play an album of MP3 files." A task is represented by an instance of IShellLink
, so a task must be an operation that can be invoked via the command line. When writing an app, you will plan out in advance what your command line arguments will be, and which operations you want to add to your app's jump list. Because tasks are inherently application-specific, Windows does not provide any default tasks. The bottom of the jump list contains a few commands for managing windows and pinning/unpinning the app to the Taskbar, but those are not considered tasks. Those are simply window-management controls.
Default Jump Lists for Older Applications
Let's start out by seeing what Windows 7 does for older apps that have no knowledge of jump lists. The first sample program is a bare-bones file viewer that's associated with the .aly
extension. The app, NaiveAlyViewer, doesn't actually do anything with the files, it just demonstrates the various ways that Windows determines what files should appear in the app's jump list.
Ever since Windows 95, Windows has had an algorithm for filling in the Recent Documents list in the Start menu. This is based around the SHAddToRecentDocs()
API. There are three situations where SHAddToRecentDocs()
is called:
- The app uses the common file open dialog (
GetOpenFileName()
or IFileOpenDialog). When the user selects a file with these dialogs, the dialog calls SHAddToRecentDocs()
. - The user double-clicks a file in Explorer, Explorer runs the app associated with the file's extension, and calls
SHAddToRecentDocs()
for you. - An app calls
SHAddToRecentDocs()
itself. This is usually done when the app accesses a file in a way that Explorer does not automatically detect.
For a naïve app like this one, Windows 7 uses the same algorithm to determine which files the app is operating on. The difference with jump lists is that Recent Documents shows all the recently-used files in one list, while a jump list only shows the files associated with one particular app.
To see this viewer in action, first click Register as Handler to associate the app with the .aly
extension. You must do this before any jump list features will work. The app's jump list is initially empty:
To add files to the jump list, open any file with the .aly
extension, and the full path to the file will appear in the dialog. As part of the registration process, the app creates a few .aly
files in your My Documents directory for you to test with. You can also create your own test files if you want - just create a file with the extension .aly
(the file's contents are unimportant). After you open a file, right-click the Taskbar button, and that file will appear in the Recent category in the jump list:
You can also try other ways of opening files, such as double-clicking a file in Explorer, or dragging a file into the NaiveAlyViewer window. Explorer automatically provides a tooltip and a context menu for jump list items, as well as the ability to pin, unpin, and delete them. When you click a file in the jump list, Explorer uses the file association information to build a command line. Normally, this will run the app and pass the full path to the file on the command line.
Using Basic Jump List Features
Picking and using an AppID
Now that we've seen how jump lists work for older programs, let's start adding some knowledge of jump lists to our code. The sample app for this section is also a viewer for .aly
files, but it can change its jump list in some basic ways.
One important concept that's used in jump lists is the application user model ID. This is abbreviated "AppID," but it is not related to the AppIDs that are used in COM. An AppID is how the new Taskbar identifies processes and windows for the purposes of organizing Taskbar buttons.
If you don't assign your app an AppID - as was the case with the naïve viewer - then Windows will create an AppID for you. This has the disadvantage that separate copies of the viewer will be treated separately by the Taskbar. For instance, if you build a debug and release version of the viewer, the two executables will not share a Taskbar button and will have separate jump lists. Assigning an AppID fixes that problem.
Our first step is to pick an AppID. MSDN recommends using a string in the form "Company.Product.SubProduct.Version". This is similar to how ProgIDs are made, and in fact we will need to have a ProgID as well, so let's pick an AppID and a ProgID now.
AppID: MDunn.CodeProjectSamples.SimpleAlyViewer
ProgID: MDunn.CodeProjectSamples.SimpleAlyViewerProgID
We won't be using multiple versions of our app, so we can leave off the version field. Now that we have these IDs, we need to tell Windows three things:
- The AppID of our application
- The ProgID of the
.aly
file association - How to find the AppID from the ProgID
The first part can be done in a few ways, but the simplest is to call SetCurrentProcessExplicitAppUserModelID()
like this:
LPCWSTR wszAppID = L"MDunn.CodeProjectSamples.SimpleAlyViewer";
HRESULT hr = SetCurrentProcessExplicitAppUserModelID ( wszAppID );
This must be done in the app's initialization code, before it creates any windows or manipulates any jump lists.
The next step is to add our app's ProgID to the .aly
file association info. We do this by adding a key called OpenWithProgIDs
under HKEY_CLASSES_ROOT\.aly
and setting a string value whose name is our ProgID. We then create a key for our ProgID under HKEY_CLASSES_ROOT
, just as in COM. Since our viewer is also the default handler for .aly
files, the information in the ProgID key determines what happens if you double-click an .aly
file (as in the previous example), as well as what should appear in the jump list. Things get more interesting when your app is not the default handler for a file type; we will see an example of this situation in the next section.
The last piece of info in the ProgID key is the AppUserModelID
value. This tells Explorer the AppID of the app that handles .aly
files. Once Explorer knows the AppID of the handler, it can properly manage Taskbar buttons and update its internal data structures related to jump lists.
The other important place where we specify our AppID is in calls to SHAddToRecentDocs()
. Instead of just passing a file path, we use some new parameters that were added to SHAddToRecentDocs()
for Windows 7. We fill in a SHARDAPPIDINFO
struct that holds our AppID and an IShellItem
interface on the file. Creating the IShellItem
is easy: the new SHCreateItemFromParsingName()
function takes a file path and returns an IShellItem
.
LPCWSTR szFilePath = ;
HRESULT hr;
SHARDAPPIDINFO info;
CComPtr<IShellItem> pItem;
hr = SHCreateItemFromParsingName ( szFilePath, NULL,
IID_PPV_ARGS(&pItem) );
if ( SUCCEEDED(hr) )
{
info.psi = pItem;
info.pszAppID = g_wszAppID;
SHAddToRecentDocs ( SHARD_APPIDINFO, &info );
}
Clearing the list of files
Now that we have our AppID set up, we can do useful things to our jump list. The easiest thing to do is clear the list of files, leaving only the window management commands. We use the IApplicationDestinations
interface to operate on the built-in features of the jump list.
HRESULT hr;
CComPtr<IApplicationDestinations> pDests;
hr = pDests.CoCreateInstance ( CLSID_ApplicationDestinations,
NULL, CLSCTX_INPROC_SERVER );
if ( SUCCEEDED(hr) )
{
hr = pDests->SetAppID ( g_wszAppID );
if ( SUCCEEDED(hr) )
pDests->RemoveAllDestinations();
}
The first method we call is SetAppID()
to indicate which jump list we want to operate on. This must be called before any other methods. Then we call RemoveAllDestinations()
to clear the list of recently-used files. That's it!
Switching between Recent and Frequent categories
Another modification we can do is change the jump list to show frequently-used files. There are two known categories in jump lists, Recent and Frequent. By default, a jump list shows the Recent category, but we can change that by creating a new jump list.
All jump list modifications follow this general pattern:
- Create an
ICustomDestinationList
interface. - Call
SetAppID()
just as we did when using IApplicationDestinations
. - Call
BeginList()
, which creates a new jump list that we can modify. - Make modifications to the jump list.
- Call
CommitList()
to save the jump list.
Note that jump lists are never modified in-place; you always create an entirely new jump list and then tell Explorer to use the new list by calling CommitList()
. This isn't a big deal for us now, since we're only using built-in jump list features, but it's something to keep in mind when adding your own custom items to jump lists. If your app keeps any state that affects the contents of the jump list, that state must be stored in a persistent location so it can be read when building a new jump list.
The SimpleAlyViewer dialog has two buttons that change the category that's shown in the jump list. Both button handlers call the helper function ShowCategory()
and pass in the category ID: KDC_FREQUENT
or KDC_RECENT
.
bool CMainDlg::ShowCategory ( KNOWNDESTCATEGORY category )
{
HRESULT hr;
CComPtr<ICustomDestinationList> pDestList;
hr = pDestList.CoCreateInstance ( CLSID_DestinationList,
NULL, CLSCTX_INPROC_SERVER );
if ( FAILED(hr) )
return false;
hr = pDestList->SetAppID ( g_wszAppID );
if ( FAILED(hr) )
return false;
UINT cMaxSlots;
CComPtr<IObjectArray> pRemovedItems;
hr = pDestList->BeginList ( &cMaxSlots, IID_PPV_ARGS(&pRemovedItems) );
if ( FAILED(hr) )
return false;
hr = pDestList->AppendKnownCategory ( category );
if ( FAILED(hr) )
return false;
return SUCCEEDED( pDestList->CommitList() );
}
As before, we create the necessary COM object, query for ICustomDestinationList
, and call SetAppID()
. We call BeginList()
to create a new jump list. BeginList()
has output parameters that indicate the maximum size of the jump list, and an array of custom items that the user has removed from the list. We don't need to worry about these parameters, since we're not adding any custom items to the list. We then call AppendKnownCategory()
to add the appropriate category to the list, and save it with CommitList()
.
Using Jump Lists with a Non-default Viewer
Our final example will be a bit different, a graphics viewer:
The difference this time is that the app doesn't have its own file type; instead, it registers as a handler for existing file types. (The commands for registering and unregistering are in the Jump List menu.) Here are this app's AppID and ProgID:
LPCWSTR g_wszAppID = L"MDunn.CodeProjectSamples.JumpListGraphicsViewer";
LPCWSTR g_wszProgID = L"MDunn.CodeProjectSamples.JumpListGraphicsViewerProgID";
Our viewer will handle BMP, JPG, PNG, GIF, and TIFF files by registering under the OpenWithProgIDs
key for each extension. For example, in the HKCR\.jpg\OpenWithProgIDs
key, we create a string value whose name is our ProgID.
Under our own ProgID key, we add two values, FriendlyTypeName
and AppUserModelID
. FriendlyTypeName
is used as the file type description when you bring up the properties dialog for a file in the jump list. The string in FriendlyTypeName
overrides the description that you see in Explorer, as shown below. The first screen shot is the property page as invoked from Explorer, while the second is the property page as invoked from a jump list item. Notice that the Type of file string is different. (The icon is also different; that is controlled by the DefaultIcon
key described below.)
AppUserModelID
works as described in the previous sample app. There are also some other registry entries that round out our file type association:
- A
DefaultIcon
key specifies the icon to use in our app's jump list. - A
CurVer
key that holds the current version of our ProgID. As before, we aren't using versioning, so we leave off the version field in the ProgID. - A
shell\open\command
key that holds the command line to use when the user selects a file from our jump list.
With these registry entries in place, our viewer's jump list works as you'd expect, and it also appears in the Open With
submenu of all the file types that we register under.
As an additional little detail, the viewer demonstrates how to tell that it is being run from an Open With
menu or a jump list item. The command line written to the command
key is JumpListGfxViewer.exe /v "%1"
(/v
for "viewer mode"). When the app sees the /v
switch and a file path, it adds a "viewer mode" string to the window.
Jump List Issues
Note: The information in this section is based on the beta release of Windows 7 (build 7000). The behavior may be different in later builds.
- Pinning an app sometimes causes a second Taskbar button to be added. During development of the sample apps, I've seen this happen with one of the apps but not the others. The samples in the Windows 7 beta SDK show this problem as well.
- If you add a known category to a jump list multiple times, the jump list will show some white space at the bottom.
Revision History
May 19, 2009: Article first published