Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

Windows 7 Goodies in C++: Adding Custom Tasks to Jump Lists

5.00/5 (21 votes)
29 Jun 2009CPOL6 min read 78K   1.2K  
Create custom tasks in your app's jump list on Windows 7

Contents

Introduction

As we saw in my previous article on jump lists, Windows 7 lets an application customize its jump list using the IApplicationDestinations and ICustomDestinationList interfaces. In this article, we'll go beyond the built-in categories, and see how you can customize a jump list by adding custom tasks, and how to handle those tasks in your program.

As with the other articles in this series, the sample code is built with Visual Studio 2008, WTL 8.0, and the beta Windows 7 SDK.

Tasks in Jump Lists

In the previous article, we saw that jump lists can have two types of entries: destinations and tasks. This article will cover tasks, which are commands that your app can carry out, such as "Create a new document." A task is represented internally as an IShellLink, so a task should be a command that can be communicated to your app via the command line.

Recall that the user can pin your app to the Taskbar, and bring up the jump list from the pinned shortcut. That means that your app doesn't have to be running before the user invokes a task. Therefore, tasks must be actions that can be carried out at any time. For example, IE8 has a task to open a new InPrivate browser window, which can be done regardless of whether IE is already running. Microsoft recommends that the list of tasks should be static, and not change based on the current state of the app.

Tasks are shown in their own category, similar to the Recent and Frequent categories that we saw last time. The Tasks category is always the last category (nearest the bottom) in the list, but its items are always shown as long as there is room in the list. This may potentially leave less room for other categories, so when deciding what tasks to put in the list, consider that having a lot of tasks will leave less room for recently-used or frequently-used files, if you include those categories in your jump list.

There is no documented maximum number of tasks, but the maximum is constrained by screen space and usability concerns. The beta build of Windows 7 didn't appear to enforce a maximum (I was able to create a jump list that went off the edge of my 1920x1200 screen), while the RC build has a maximum of 16. You shouldn't rely on this number to stay the same in the future, since Microsoft may change the internal workings of jump lists as the result of design or usability decisions. If you add too many tasks, the jump list will show as many tasks as it can, and the remainder will not be visible.

The sample project for this article is a simple clock whose jump list has tasks for running the app with various color schemes:

Image 1

The app also associates a command line switch with each task:

  • /r: Red text
  • /y: Yellow text
  • /g: Green text
  • /z: Random colors

If the app is run without switches, it will use your preferences for window color and text color:

Image 2

But running it with one of the four switches will use a different color scheme:

Image 3

Setting Up a Jump List

Using tasks requires creating a custom jump list, so the procedure is:

  1. Create an DestinationList COM object and get an ICustomDestinationList interface.
  2. Call SetAppID() to tell the jump list your application's AppID.
  3. Call BeginList() to create a new, empty jump list.
  4. Create an EnumerableObjectCollection object.
  5. Create your tasks and add them to the object collection.
  6. Call AddUserTasks() to add the tasks to the jump list.
  7. Call CommitList() to save the jump list.

The demo app sets up its jump list in WinMain(), so that work is done every time the program is run. (This means that the customized jump list won't appear until you've run the program once.) Creating a jump list is not a time-consuming operation, so this won't delay startup by an appreciable amount.

Creating a new jump list

In the sample project, all the jump list-related code is in SetUpJumpList(). We start by creating a new jump list:

// NOTE: Error-handling omitted
bool SetUpJumpList()
{
CComPtr<ICustomDestinationList> pDestList;
 
  // Create a jump list COM object.
  pDestList.CoCreateInstance ( CLSID_DestinationList );
 
  // Tell the jump list our AppID.
  pDestList->SetAppID ( g_wszAppID );
 
  // Create a new, empty jump list. We aren't adding custom destinations,
  // so cMaxSlots and pRemovedItems aren't used.
UINT cMaxSlots;
CComPtr<IObjectArray> pRemovedItems;
 
  pDestList->BeginList ( &cMaxSlots, IID_PPV_ARGS(&pRemovedItems) );

Next, we create an object collection to hold our tasks. We do this by creating a COM object with CLSID CLSID_EnumerableObjectCollection, and requesting an IObjectCollection interface:

  // Create an object collection to hold the custom tasks.
CComPtr<IObjectCollection> pObjColl;
 
  pObjColl.CoCreateInstance ( CLSID_EnumerableObjectCollection );

We then call another function, AddJumpListTasks(), to populate the collection. We'll see the code for AddJumpListTasks() later.

// Add our custom tasks to the collection.
AddJumpListTasks ( pObjColl );

Now that the tasks are in the collection, we query the collection for an IObjectArray interface, pass that interface to AddUserTasks(), and then save the jump list.

  // Get an IObjectArray interface for AddUserTasks.
CComQIPtr<IObjectArray> pTasksArray = pObjColl;
 
  // Add the tasks to the jump list.
  pDestList->AddUserTasks ( pTasksArray );
 
  // Save the jump list.
  pDestList->CommitList();
}

Creating tasks

The AddJumpListTasks() function in the sample project creates four tasks, as shown in the screen shot at the beginning of the article. Each task is created in the same way, by creating a ShellLink object and querying for an IShellLink interface, and then calling the appropriate IShellLink methods. Each task has five properties, four of which are set via IShellLink methods:

  • SetPath(): The full path to the executable (required).
  • SetArguments(): Command line arguments to pass to the executable (optional, but you will usually set arguments).
  • SetIconLocation(): An icon to show next to the task (optional).
  • SetDescription(): A description of the task that appears as a tooltip (optional).

The last property, the text to show in the jump list item, is set by querying the ShellLink object for an IPropertyStore interface, and setting the PKEY_Title property.

Let's look at an example of how to create a task. This should be pretty familiar if you've ever dealt with shortcuts, which also use IShellLink. For simplicity, all values are hard-coded here in this snippet, but the sample project's AddJumpListTasks() queries for some common data (like the executable path) and calls AddJumpListTask() once for each task.

// NOTE: Error-handling omitted
bool AddJumpListTask ( IObjectCollection* pObjColl )
{
    // Create a shell link COM object.
CComPtr<IShellLink> pLink;
 
  pLink.CoCreateInstance ( CLSID_ShellLink );
 
  // Set the executable path
TCHAR szExePath[MAX_PATH];
 
  GetModuleFileName ( NULL, szExePath, _countof(szExePath) );
  pLink->SetPath ( szExePath );
 
  // Set the arguments
  pLink->SetArguments ( _T("/r") );
 
  // Set the icon location. IDI_RED is the resource ID of the icon
  pLink->SetIconLocation ( szExePath, -IDI_RED );
 
  // Set the link description (tooltip on the jump list item)
  pLink->SetDescription ( _T("Run the clock with red text") ); 

Next we get the IPropertyStore interface and set the PKEY_Title property:

  // Set the link title (the text of the jump list item).
  // This is kept in the object's property store, so QI for that interface.
CComQIPtr<IPropertyStore> pPropStore = pLink;
PROPVARIANT pv;
 
  InitPropVariantFromString ( L"Red Text", &pv );
 
  // Set the title property.
  pPropStore->SetValue ( PKEY_Title, pv );
  PropVariantClear ( &pv );
 
  // Save the changes we made to the property store
  pPropStore->Commit();

Now that we've set up the link, we add it to the object collection that was created earlier in SetUpJumpList() and passed to this function:

  // Add this shell link to the object collection.
  pObjColl->AddObject ( pLink );
}

Here's how the jump list looks with just the Red Text task:

Image 4

Creating separators

One more thing you can put in the task list is a separator. You can see a separator between the Green Text and Random Colors items here:

Image 5

In the Windows 7 beta build, separators are very small, and don't take up a full-height slot in the jump list. In RC builds, however, the line is wider and there is more vertical space around the separator:

Image 6

A separator is basically a task whose ShellLink has a property that marks it as being a separator. All other aspects are the same as regular tasks, in that you create a ShellLink object and add it to an object collection. To create a separator, you only have to do one thing: Set the PKEY_AppUserModel_IsDestListSeparator property of the ShellLink to TRUE:

// NOTE: Error-handling omitted
bool AddJumpListSeparator ( IObjectCollection* pObjColl )
{
  // Create a shell link COM object.
CComPtr<IShellLink> pLink;
 
  pLink.CoCreateInstance ( CLSID_ShellLink );
 
  // Get an IPropertyStore interface.
CComQIPtr<IPropertyStore> pPropStore = pLink;
 
  InitPropVariantFromBoolean ( TRUE, &pv );
 
  // Set the property that makes the task a separator.
  pPropStore->SetValue ( PKEY_AppUserModel_IsDestListSeparator, pv );
  PropVariantClear ( &pv );
 
  // Save the property changes.
  pPropStore->Commit();
 
  // Add this shell link to the object collection.
  pObjColl->AddObject ( pLink );
}

That's it! Note that Windows will check for separators at the beginning and end of the task list and remove them. It will also collapse multiple consecutive separators into one.

There is one more thing you can do with jump lists: create a custom category that shows destinations that you define. We'll take a look at how to do that next time.

Revision History

June 28, 2009: Article first published

License

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