Chapter 14: Adding Support for Windows 7 Jump Lists & Taskbar Tabs
The Hilo Browser and Annotator support Windows 7 Jump Lists and taskbar tabs. Jump Lists provide the user with easy access to recent files and provide a mechanism to launch key tasks. Taskbar tabs provide a preview image and access to additional actions within the Windows taskbar. In this Chapter we will see how the Hilo Browser and Annotator applications implement support for Windows 7 Jump Lists and taskbar tabs.
Implementing Jump List Items
The Jump List for an application is displayed when you right-click on the taskbar tab for the application. On a touch screen you can show the Jump List in three ways: touch the tab with one finger and then touch the required screen with another finger, use the pan gesture dragging the tab away from the taskbar, or touch the tab until a circle appears and then let go.
The Jump List contains two types of items, destinations and tasks. For example, Figure 1 shows the Jump List for Internet Explorer, there are two categories: Frequent and Tasks. The Frequent list items are web pages that are frequently displayed and since these refer to content they are described as destinations. Other applications will have files, folders, or some other content-based items as destinations and typically these are items that can be represented by an IShellItem object (or sometimes an IShellLink object). The screenshot shows five items in the Frequent list and you can set a limit to how many items are shown through the Customize Start Menu dialog accessed through the Start menu properties.
The Tasks list items are things that you can do with Internet Explorer: start private browsing and open a new tab. Since tasks need to have information about a process that will perform the task and parameters indicating the task action, tasks are IShellLink objects.
Figure 1 Jump List for Internet Explorer
The Windows 7 Shell API allows you to alter the Jump List in several ways. The simplest action is to add a destination to the Recent file list because in many cases Windows 7 will do this without any code, although you can specifically add a destination to this list. The Shell API provides access through the ICustomDestinationList interface to allow you to customize the Jump List to add custom categories and tasks.
Recent File Lists
Recent file lists have been a feature of Windows since Windows XP and are provided through the SHAddToRecentDocs Windows 7 Application Programming Interface (API) function. This function adds a document to the My Recent Documents (Windows XP) or Recent Items (Windows Vista and later) menu on the Start menu. Figure 2 shows an example of the Recent Items menu where there is Word document, a Visual Studio solution, and two image files. The entries in the Recent Items menu are paths to data files and the shell uses file association registry information to determine which application opens the file.
Figure 2 Showing the Windows 7 Recent Items menu
Windows 7 extends the concept of the Recent Items list to individual applications so that the SHAddToRecentDocs function not only adds a file to the system’s Recent Items menu but also adds the file to the application’s Recent Jump List. Figure 3 shows the Jump List for Annotator, the lower three items are tasks added to every Jump List and allow you to start a new instance of the application, pin the application item to the taskbar, and to close down an instance. The top items are the Recent file list and are recent files opened with Annotator. Items are added to the Recent list if your application opens a file with one of the Shell file APIs (the GetOpenFileName function or the IFileOpenDialog interface) or if your application explicitly calls the SHAddToRecentDocs function.
Figure 3 Showing the Jump List for Annotator
Figure 3 shows that two image files have been opened recently with Annotator and Figure 2 shows that these files are also displayed on the system-wide Windows 7 Recent Items menu. Since the SHAddToRecentDocs function adds files to the Recent Items list this raises the question of how Windows 7 knows that these files were opened by Annotator and not some other image editor. The answer lies in application user model IDs (or AppID for short).
AppIDs are strings that identify an application and allow the Windows 7 shell to stack the tabs on the taskbar for multiple instances of the same application. Windows 7 can allocate an AppID for an application, but you can specify the string yourself which gives you greater flexibility. The API function to do this is SetCurrentProcessExplicitAppUserModelID and holds the current record for being the Windows 7 API function with the longest name. This function takes a single parameter which is the AppID as a Unicode string. AppIDs can be any unique string but the recommendation is to use the same format as ProgID strings, although clearly you should use a different value to your application’s ProgID. Listing 1 shows the declaration of constant strings at the top of the AnnotatorApplication.cpp file which defines the ProgID and AppID strings.
Listing 1 Declaration of the ProgID and AppID for Annotator
const std::wstring ProgID = L"Microsoft.Hilo.AnnotatorProgID";
const std::wstring FriendlyName = L"Microsoft Hilo Annotator";
const std::wstring AppUserModelID = L"Microsoft.Hilo.Annotator";
const std::wstring FileTypeExtensions[] =
{
L".bmp",
L".dib",
L".jpg",
L".jpeg",
L".jpe",
L".jfif",
L".gif",
L".tif",
L".tiff",
L".png"
};
The SetCurrentProcessExplicitAppUserModelID function must be called before the first window is created and in Annotator this function is called in the AnnotatorApplication::Initialize method before the main window is created. This function associates the specified AppID with the application and its windows but it makes no connection between the application and the files that it can edit. That is the role of the ProgID registry setting. Listing 2 shows part of the registration that is required for Annotator to add files to the Recent file Jump List. The first few lines shows that Annotator has to be registered as a handler for the file types that it edits and Listing 2 only gives the registration for the .bmp file type (all the file types mentioned in the FileTypeExtensions array in Listing 1 will have similar values). The important point is that the file type has a string entry in its OpenWithProgIDs key indicating that the file can be opened with Annotator. Note that Annotator will not be the only application registered as a handler for the file type.
Listing 2 Registration for Jump Lists
[HKEY_CLASSES_ROOT\.bmp\OpenWithProgids]
"Microsoft.Hilo.AnnotatorProgID"="":
[HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID]
"FriendlyTypeName"="Microsoft Hilo Annotator"
"AppUserModelID"="Microsoft.Hilo.Annotator"
[HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID\CurVer]
@="Microsoft.Hilo.AnnotatorProgID"
[HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID\Shell\Open\Command]
@="C:\\Hilo\\annotator.exe %1"
The ProgID registration contains a value called AppUserModelID that has the AppID for the application. This registration value links the AppID to the ProgID. For the shell to be able to launch an instance of the application there must be information about the location and command line format for the application. This is the purpose of the Shell\Open\Command subkey. The default value of this key contains the path and an indication that the data file should be the first command line parameter. Chapter 12 described the RegistrationHelper utility that is called automatically when the Hilo Annotator application is first used. This utility does the registration to enable Annotator to use Jump Lists.
There are three ways to load a file in Annotator:
- Through the command line. When you click on the Annotator button in the Hilo Browser it starts the Annotator with the full path to the currently selected photo.
- Through selecting another file in the Annotator image editor. When you start Annotator without a filename, the application shows all the photos in the Pictures library and you can scroll through the list of pictures for the photo you want to edit.
- Through the Open menu item on the main menu of Annotator. When you click the Open menu item it shows the standard File Open dialog through which you can browse for one or more files to edit.
The last option in this list will use the standard File Open dialog through the IFileOpenDialog interface and when you open a file with this API the shell will automatically make a call to the SHAddToRecentDocs function. The other two methods to open a file do not use a Windows 7 API that adds the file to the Jump List and so Annotator must do this explicitly with a call the SHAddToRecentDocs. This call is made in the ImageEditorHandler::SaveFileAtIndex method, which is called when you click the Save or Save A Copy As menu items in Annotator and also when you close the application.
Jump List Tasks
The Recent file list is just one way that you can add items to a Jump List. The Windows 7 shell allows you to customize Jump Lists further, and Browser illustrates this by adding a custom task. The key to customizing the Jump List is the ICustomDestinationList interface. Changes to a Jump List are done in a transactional way: you start by calling the ICustomDestinationList::BeginList method, make the changes, and then call the ICustomDestinationList::CommitList method to make the changes permanent. If the application has an explicit AppID then you must call the ICustomDestinationList::SetAppID method before calling the BeginList method.
Browser does this work in the JumpList class which is called at the end of the BrowserApplication::Initialize method when the application is first created. Listing 3 shows the code to add a custom task with the JumpList class. Browser assumes that Annotator is in the same folder and so the first action is to get the path for this folder and use it to create a full path to Annotator. Next an instance of the JumpList class is created and initialized with the AppID of the application. Finally, the JumpList::AddUserTask method is called to create a task that will launch the Annotator application with no command line parameters.
Listing 3 Creating a task in the Browser Jump List
wchar_t currentFileName[FILENAME_MAX];
if (!GetModuleFileName(nullptr, currentFileName, FILENAME_MAX))
{
return S_OK;
}
// Annotator should be found in the same directory as this binary
std::wstring currentDirectory = currentFileName;
std::wstring externalFileName = currentDirectory.substr(0, currentDirectory.find_last_of(L"\\") + 1);
externalFileName += L"annotator.exe";
JumpList jumpList(AppUserModeId);
hr = jumpList.AddUserTask(externalFileName.c_str(), L"Launch Annotator", nullptr);
Listing 4 shows the main code in the JumpList::AddUserTask method. First, this code creates the destination list object and identifies the Jump List to alter through a call to the ICustomDestinationList::SetAppID method. Next the code indicates that it will start changing the Jump List by calling the ICustomDestinationList::BeginList method. This method returns two items, the first is the number of items that will be shown in the Recent or Frequent destination lists (set through the Start menu properties dialog) and the other object is a collection of items that you have remove from the Jump List (by right-clicking on an item and clicking Remove from this list item on the context menu). Browser does not process the removed item list and so although it stores the array as a member of the JumpList class it does nothing with this interface pointer. The code then calls the JumpList::CreateUserTask method to add the task item before calling the ICustomDestinationList::CommitList method to complete the changes.
Listing 4 Creating a new category for Browser
HRESULT hr = CoCreateInstance(
CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_destinationList));
hr = m_destinationList->SetAppID(m_appId.c_str());
UINT cMinSlots;
hr = m_destinationList->BeginList(&cMinSlots, IID_PPV_ARGS(&m_objectArray));
hr = CreateUserTask(applicationPath, title, commandLine);
hr = m_destinationList->CommitList();
The JumpList::CreateUserTask method is shown in Listing 5. This code creates an object collection object (accessed through an IObjectCollection interface) that will contain the items to add to the Jump List. The code initializes this object by adding a shell link object to the task and then adding the collection to the user task list with a call to the ICustomDestinationList::AddUserTasks method. The user task list is a standard category called Tasks but you can create a custom category by calling the ICustomDestinationList::AppendCategory method and adding the category items to the return object array.
Listing 5 Creating a new category on Browser's Jump List
ComPtr<IObjectCollection> shellObjectCollection;
HRESULT hr = CoCreateInstance(
CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&shellObjectCollection));
// Create shell link first
ComPtr<IShellLink> shellLink;
hr = CreateShellLink(applicationPath, title, commandLine, &shellLink);
hr = shellObjectCollection->AddObject(shellLink);
// Add the specified user task to the Task category of a Jump List
ComPtr<IObjectArray> userTask;
hr = shellObjectCollection->QueryInterface(&userTask);
hr = m_destinationList->AddUserTasks(userTask);
Tasks are typically shell link objects because this allows you to provide the path to an application rather than a document. In Browser the shell link object is created by a call to the JumpList::CreateShellLink method which is shown in Listing 6. This code creates and initializes a shell link object (shellLink) which is returned to the calling method, JumpList::CreateUserTask. The shell link object has several properties which include the path of the application that will be executed and the command line arguments passed to the application. Most of the properties are set through Set methods on the IShellLink interface, but there is no such method for the title that will be displayed by the Jump List. So to set this value the JumpList::CreateShellLink method obtains the extended properties of the shell link object by querying for the IPropertyStore interface. The method then sets the PKEY_Title property with the task name. Properties are PROPVARIANT structures which are used in a similar fashion as VARIANT structures: they have to be initialized before use and then cleared when they are no longer needed.
Listing 6 Creating a shell link object
ComPtr<IShellLink> shellLink;
HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
// Set the path and file name of the shell link object
hr = shellLink->SetPath(applicationPath);
// Set the command-line arguments for the shell link object
hr = shellLink->SetArguments(commandLine);
// Set the name of the shell link object
ComPtr<IPropertyStore> propertyStore;
hr = shellLink->QueryInterface(&propertyStore);
PROPVARIANT propertyValue;
hr = InitPropVariantFromString(title, &propertyValue);
hr = propertyStore->SetValue(PKEY_Title, propertyValue);
hr = propertyStore->Commit();
hr = shellLink->QueryInterface(shellLinkAddress);
PropVariantClear(&propertyValue);
Implementing the Annotator Taskbar Tab
Windows 7 provide thumbnails for running applications through their taskbar tabs. When you hover the mouse over a tab, or with a touch screen you touch the tab and hold your finger on the tab for until the thumbnail appears. By default this thumbnail will be a preview of the entire window as shown in Figure 4 for Browser.
Figure 4 Showing the taskbar thumbnail for Browser
The thumbnail is a portion of the main window of the application and by default the entire window is shown. but your code can provide a specific part of the window. In addition, you can add controls to the thumbnail window so that you can have basic access to the application even when the application is minimized.
Annotator provides code to change the section of the window that is shown in the thumbnail and to add controls to allow you to scroll the images in the image editor. To see this, start Annotator on its own, either through the Start menu (type Annotator.exe in the Start menu search box and click the link returned) or through the Launch Annotator task on the Browser Jump List. In both cases Annotator will be started with more than one image in the image editor pane. At this point hover the mouse cursor over the taskbar tab for Annotator and you will see that the thumbnail for Annotator only has the image currently being edited, as shown in Figure 5. The thumbnail does not show the ribbon, and in addition, there are two buttons at the bottom of the thumbnail called Backward Button and Forward Button. When you click on either of these buttons the image in Annotator will change as if you pressed the keyboard left or right arrow keys.
Figure 5 Showing the Taskbar thumbnail for Annotator
When you change the size of the image in the Annotator by using the zoom buttons, the image shown in the thumbnail is zoomed appropriately.
The code that does most of the work is in a class called Taskbar. This class wraps access to the task bar list object that implements the ITaskbarList3 interface. The thumbnail shows the contents of a window and allows you to add child controls to the thumbnail. These controls will send command messages to a window and therefore many of the ITaskbarList3 interface methods need a HWND parameter. The Taskbar class constructor takes a HWND parameter which is stored as a member variable and this handle is passed to the object methods that need a window handle. The other important member variable is the interface pointer to the task bar list object and this is initialized by the Initialize method, which is called by all the public methods. The Taskbar::Initialize method is shown in Listing 7, this simply creates the task bar list object and initializes it.
Listing 7 Creating the task bar list object
HRESULT Taskbar::Initialize()
{
HRESULT hr = S_OK;
if (!m_taskbarList)
{
hr = CoCreateInstance(
CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_taskbarList));
if (SUCCEEDED(hr))
{
hr = m_taskbarList->HrInit();
}
}
return hr;
}
Thumbnail Image
Providing the thumbnail image is relatively straightforward: all you need to do is give the taskbar list object a rectangle that contains coordinates for the window’s client area. Once you have done this, the taskbar will obtain the appropriate part of the window when necessary and draw it in the thumbnail. You do not have to do any additional drawing.
In Annotator a request to redraw the application window is routed through to the ImageEditorHandler::OnRender method which calls the ImageEditorHandler::UpdateTaskbarThumbnail method. The majority of this method calculates the clipping rectangle for the current photo (in Listing 8 this rectangle is the rect variable). The code that sets the current clipping rectangle of the window to show in the thumbnail is shown in Listing 8. The Taskbar::SetThumbnailClip method simply calls the ITaskbarList3::SetThumbnailClip method passing the windows handle passed to the Taskbar class constructor (hWndParent the handle of the main window) and the clipping rectangle.
Listing 8 Providing the thumbnail clip rectangle
static Taskbar taskbar(hWndParent);
// Zoom the image to the thumbnail
hr = taskbar.SetThumbnailClip(&rect);
Thumbnail Buttons
There are several steps needed to add a button to a thumbnail. First you need to indicate to the taskbar the images that will be used for the buttons, then you need to add the buttons and provide information like tooltip captions and an ID, and finally you have to provide code to handle the command messages generated when a button is clicked. In Annotator the Taskbar class does the work of adding buttons and their images to the taskbar and this code is called in the AnnotatorApplication::Initialize method, the code is shown in Listing 9. The Taskbar object is initialized with the handle of the main window of the Annotator application. This is used to indicate to the taskbar that the buttons will be shown for thumbnails for this particular window. The ThumbnailToolbarButton structure shown in this code is used to hold the identifier for the button and an indication as to whether the button is enabled (at this point, by default, both buttons are enabled). When you click on the button a WM_COMMAND message is sent to the handler window and the identifier of the button will be provided as part of the wParam of this message.
Listing 9 Adding buttons to the thumbnail image
HWND hwnd;
mainWindow->GetWindowHandle(&hwnd);
Taskbar taskbar(hwnd);
ThumbnailToobarButton backButton = {APPCOMMAND_BROWSER_BACKWARD, true};
ThumbnailToobarButton nextButton = {APPCOMMAND_BROWSER_FORWARD, true};
hr = taskbar.CreateThumbnailToolbarButtons(backButton, nextButton);
The Taskbar::CreateThumbnailToolbarButtons method does the main work of adding the buttons to the thumbnail. The thumbnail button images are provided through a standard Windows 7 image list control and the first action of the CreateThumbnailToolbarButtons method is to initialize the task bar list object with an image list and the code to do this is provided by the Taskbar::SetThumbnailToolbarImage method shown in Listing 10. This code is straightforward; the resources of the Annotator contain two images arrow_toolbar_16.bmp and arrow_toolbar_24.bmp which are used when the system uses, respectively, 16x16 and 24x24 pixels icons. Each image contains two button images and the computer’s icon size setting determines which of these is used to initialize the imageList control through a call to the ImageList_LoadImage function. Once the image list has been initialized it is associated with the taskbar thumbnail for Annotator with a call to the ITaskbarList3::ThumbBarSetImageList method.
Listing 10 Setting the thumbnail image
// Get the recommended width of a small icon in pixels
int const smallIconWidth = GetSystemMetrics(SM_CXSMICON);
// Load the bitmap based on the system's small icon width
HIMAGELIST imageList;
if (smallIconWidth <= 16)
{
imageList = ImageList_LoadImage(
HINST_THISCOMPONENT, MAKEINTRESOURCE(IDB_BITMAP_TOOLBAR_16),
16, 0, RGB(255, 0, 255), IMAGE_BITMAP, LR_CREATEDIBSECTION);
}
else
{
imageList = ImageList_LoadImage(
INST_THISCOMPONENT, MAKEINTRESOURCE(IDB_BITMAP_TOOLBAR_24),
24, 0, RGB(255, 0, 255), IMAGE_BITMAP, LR_CREATEDIBSECTION);
}
// Add the tool bar buttons to the taskbar
if (imageList)
{
hr = m_taskbarList->ThumbBarSetImageList(m_hWnd, imageList);
}
ImageList_Destroy(imageList);
Once the image list for the thumbnail controls has been loaded, the Taskbar::CreateThumbnailToolbarButtons method, Listing 11, can create the buttons. This method calls the ITaskbarList3::ThumbBarAddButtons method with an array that has information about the index of the button image in the image list, the tooltip string, and the ID of the control that will be passed through the WM_COMMAND message when the button is clicked.
Listing 11 Creating thumbnail buttons
// Set the icon/images for thumbnail toolbar buttons
hr = SetThumbnailToolbarImage();
THUMBBUTTON buttons[2] = {};
// First button
buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS;
buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;;
buttons[0].iId = backButton.buttonId;
buttons[0].iBitmap = 0;
StringCchCopyW(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Backward Button");
// Second button
buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS;
buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;
buttons[1].iId = nextButton.buttonId;
buttons[1].iBitmap = 1;
StringCchCopyW(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Forward Button");
// Set the buttons to be the thumbnail toolbar
hr = m_taskbarList->ThumbBarAddButtons(m_hWnd, ARRAYSIZE(buttons), buttons);
Once the buttons have been created, you can change these values at a later stage by calling the ITaskbarList3:: ThumbBarUpdateButtons method. Annotator does this in the ImageEditorHandler::UpdateTaskbarThumbnail method (which is called when the main window is updated). In this method there is code to test the position of the current photo in the list of photos in the image editor pane. If the current photo is the first photo in the list then the Backward Button is disabled and if the photo is at the end of the list the Forward Button is disabled. This action of enabling and disabling the thumbnail buttons is carried out by calling Taskbar::EnableThumbnailToolbarButtons and the relevant code is shown in Listing 12. If a button should be enabled, this method uses the THBF_ENABLED flag and if the button is to be disabled the button uses the THBF_DISABLED flag.
Listing 12 Enabling thumbnail buttons
HRESULT Taskbar::EnableThumbnailToolbarButtons(ThumbnailToobarButton backButton, ThumbnailToobarButton nextButton)
{
HRESULT hr = Initialize();
if (SUCCEEDED(hr))
{
THUMBBUTTON buttons[2] = {};
// First button
buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS;
if (backButton.enabled)
{
buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;
}
else
{
buttons[0].dwFlags = THBF_DISABLED;
}
buttons[0].iId = backButton.buttonId;
buttons[0].iBitmap = 0;
StringCchCopyW(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Backward Button");
// Second button
buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS;
if (nextButton.enabled)
{
buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;
}
else
{
buttons[1].dwFlags = THBF_DISABLED;
}
buttons[1].iId = nextButton.buttonId;
buttons[1].iBitmap = 1;
StringCchCopyW(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Forward Button");
// Update the buttons of the thumbnail toolbar
hr = m_taskbarList->ThumbBarUpdateButtons(m_hWnd, ARRAYSIZE(buttons), buttons);
}
return hr;
}
When you click on the Forward Button or Backward Button the WM_COMMAND message is sent to the window associated with the thumbnail (in Annotator this is the main window). This message is routed to the image editor class and handled in the ImageEditorHandler::OnCommand method. This method calls the ImageEditorHandler::NextImage or ImageEditorHandler::PreviousImage method depending on which button is pressed. The code in Listing 12 provides the THBF_DISMISSONCLICK flag for enabled buttons to indicate that when you click on the thumbnail button, the thumbnail will disappear. If you omit this flag then the thumbnail will remain on screen and you will see the scrolling action performed within the thumbnail and the Annotator window.
Conclusions
In this chapter you have learned how to implement support for Windows 7 Jump Lists and a taskbar thumbnail for the application. In the next chapter you will see how to use the Windows 7 Web Services API to upload a photo to an online photo sharing application.