Click here to Skip to main content
Click here to Skip to main content

Windows 7 Goodies in C++: Jump Lists

, 19 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An intro to using jump lists with your Windows 7 applications

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?

Sample jump list from Paint

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.

Naive aly file viewer app

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:

  1. The app uses the common file open dialog (GetOpenFileName() or IFileOpenDialog). When the user selects a file with these dialogs, the dialog calls SHAddToRecentDocs().
  2. The user double-clicks a file in Explorer, Explorer runs the app associated with the file's extension, and calls SHAddToRecentDocs() for you.
  3. 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:

Empty jump list

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:

Populated 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 = /* full path to the file */;
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;  // our AppID - see above
 
    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:

  1. Create an ICustomDestinationList interface.
  2. Call SetAppID() just as we did when using IApplicationDestinations.
  3. Call BeginList(), which creates a new jump list that we can modify.
  4. Make modifications to the jump list.
  5. 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:

Graphics viewer main frame

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.

Open With menu

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Dunn
Software Developer (Senior) VMware
United States United States
Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.
 
Mike was a VC MVP from 2005 to 2009.

Comments and Discussions

 
Generaltaskbar context menu process name Pinmemberstunger16-Sep-09 15:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 19 May 2009
Article Copyright 2009 by Michael Dunn
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid