Exercise: Experiment with the New Windows 7 Taskbar Features

In this exercise, you will experiment with the new Windows 7 taskbar features. You will extend a showcase application that demonstrates the use of the new taskbar functionality to provide a taskbar progress bar, overlay icon, custom tabbed thumbnails, jump list, and more. Most of the application’s user interface is already implemented; you will use Win32 functions and COM interfaces from the Windows SDK to fill in the missing parts to order to interact with the Windows 7 taskbar.

To begin this exercise, open the TaskbarHOL_Starter solution (under the HOL root folder) in Visual Studio.

Figure 1

TaskbarHOL solution structure in Visual Studio

Spend a minute or two exploring the C++ source and header files that comprise the demo application. For your convenience, ensure that the Task List tool window is visible (in the View menu, choose Task List) and in the tool window’s combo box select Comments. You will now see TODO items for each of the tasks in this exercise.

Note:
In the interests of brevity and simplicity, the demo application does not demonstrate best practices of Win32 UI development (including error handling best practices), nor does it exhibit the best design guidelines for working with the Windows 7 Taskbar. For more information, consult the Windows 7 User Experience Guidelines (Taskbar) at https://msdn.microsoft.com/en-us/library/aa511446.aspx and the Taskbar Extensions article at https://msdn.microsoft.com/en-us/library/dd378460(VS.85).aspx

Task 1—Using Taskbar Overlay Icons and Progress Bars

In this task, you will toggle an overlay icon on the application’s taskbar button when the text entered into an edit box is checked for spelling errors; you will also modify the state and value of the application’s taskbar button progress bar when items are added to a list box.

  1. Navigate to the MainDialog.cpp code file and locate the OnTaskbarButtonCreated method. This method is invoked when the main dialog window procedure receives a window message indicating that the taskbar button for the window has been created. Because there is no #define directive in the Windows header files,, use the RegisterWindowMessage function as shown in the OnInitDialog method in the MainDialog.cpp file to retrieve the window message identifier.
  2. In the method’s code:
    1. Co-create the CLSID_TaskbarList COM object and place the result in the _pTaskbar member variable (of type ITaskbarList3*). The ITaskbarList3* interface is the primary gateway for the Windows 7 Taskbar features, including taskbar overlay icons and progress bars which are the subject of this task.
    2. Call the HrInit method on the resulting interface pointer to initialize the object.
  3. Call the TabWindow::EnableCustomPreviews method and pass to it the window handle of the main dialog (stored in the _hwnd member variable). This will be used in Task 2 to support customization of the application’s thumbnail and preview.
  4. The complete code should be similar to the following:::CoCreateInstance(CLSID_TaskbarList,NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pTaskbar));
    _pTaskbar->HrInit();
    TabWindow::EnableCustomPreviews(_hwnd);
  5. Navigate to the OnSpellCheck method.
  6. In the method’s code, if the SpellCheck::Check call returns false:
    1. Use the SetOverlayIcon method of the ITaskbarList3 interface (stored in the _pTaskbar member variable) to set the taskbar overlay icon to IDI_ERROR. (Use the LoadIcon function to load the icon; pass the window’s handle, _hwnd, as the first parameter.) This will cause an overlay icon to appear in the bottom-right corner of the application’s taskbar button, providing an immediate status indicator without the need to switch to the application’s window. This feature is used by Windows Live Messenger to display online availability status, by Microsoft Office Outlook 14 to display the new mail notification, and by many other applications.
    2. In the same conditional branch, use the SetProgressState method of the ITaskbarList3 interface to set the taskbar button’s progress state to the error state.This will cause a progress bar to appear in the application’s taskbar button, providing an immediate progress indicator without the need to switch to the application’s main window. This feature is used by Windows Explorer when performing file operations, by Internet Explorer when downloading files, and by other Windows applications.
  7. If the SpellCheck::Check call returns true, repeat steps 6.a and 6.b to set the overlay icon to IDI_WINLOGO and the progress state to the normal.
  8. The complete code should be similar to the following:if (!SpellCheck::Check(text))
    {
    HICON hicon = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_ERROR));
    hr = _pTaskbar->SetOverlayIcon(_hwnd, hicon, L"Spelling error");
    hr = _pTaskbar->SetProgressState(_hwnd, TBPF_ERROR);
    }
    else
    {
    HICON hicon = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_WINLOGO));
    hr = _pTaskbar->SetOverlayIcon(_hwnd, hicon, L"Spelling OK");
    hr = _pTaskbar->SetProgressState(_hwnd, TBPF_NORMAL);
    }
  9. Compile and run the application.
  10. Click Check Spelling. An overlay icon with the standard Windows error indicator should appear on top of the application’s taskbar button. For example:

    Figure 3

    A taskbar overlay icon, conveying an error status

  11. Modify the text in the edit box by removing the last word (‘text’). Click Check Spelling again. The overlay icon should change. For example:

    Figure 4

    A taskbar overlay icon without the error indicator

  12. Navigate to the OnAddToList method.
  13. In the method’s code, use the Show method of the _pTabCtrl member variable to display the List tab (use the LIST_TAB constant as the parameter).
  14. Retrieve the handle to the list box window using the GetTab method of the _pTabCtrl member variable and then the GetControl method (pass 0 as the control number).
  15. Use the SendMessage function to add an item to the list box with arbitrary text (use the LB_ADDSTRING message).
  16. Obtain the current number of items in the list box by using the SendMessage function with the LB_GETCOUNT message.
  17. Use the SetProgressValue method of the _pTaskbar member variable to update the progress value; use the number of items in the list box as the current value, and 10 as the maximum value.
  18. Compile and run the application.
  19. Click Add to List a few times to add items to the list box. Observe the taskbar button and ensure that the progress bar advances as items are being added. For example:

    Figure 5

    A taskbar progress bar when the window doesn’t have focus, with a ‘Normal’ progress state

  20. Click Check Spelling and notice that the progress bar color changes to red, indicating that the progress state is an error state. For example:

    Figure 6

    A taskbar progress bar when the window doesn’t have focus, with an ‘Error’ progress state and an additional overlay icon

Task 2—Using Live Thumbnail Previews and Tabbed Thumbnails

In this task, you will customize live thumbnails (and previews) of the application’s window and provide the displayed thumbnail lazily (on demand); you will also display multiple taskbar buttons with thumbnails and live previews corresponding to each of the application’s tabs.

  1. Navigate to the MainDialog.cpp code file and locate the OnSendThumbnail method. The purpose of this method, which is invoked as a result of receiving the WM_DWMSENDICONICTHUMBNAIL window message, is to provide a thumbnail preview of the application’s main window. At this time, we will provide a simple screen capture of the window that is not different from what DWM generates on its own. Later on, we will provide a custom preview of the application’s tab, which will require a more sophisticated approach.

    The main dialog’s window procedure determines the thumbnail dimensions by inspecting the lParam message parameter. The high word (HIWORD) of the parameter is the thumbnail width; the low word (LOWORD) of the parameter is the thumbnail height.

    In Task 1 (step 3), we instructed the DWM to request a thumbnail bitmap and a live preview bitmap from the application’s main window. If we didn’t do so, we wouldn’t get a chance to customize the thumbnail bitmap and live preview bitmap.

    Finally, note that the application’s WinMain function calls the ChangeWindowMessageFilter API function to request that the WM_DWMSENDICONICTHUMBNAIL and WM_DWMSENDICONICLIVEPREVIEWBITMAP window messages be allowed to pass through UIPI. This enables the messages to pass through even when the application is running as an administrator (with a High integrity level).

  2. In the method’s code, use the GetWindowThumbnail method to retrieve the window thumbnail bitmap and pass the resulting bitmap to the DwmSetIconicThumbnail method. Use the DWM_SIT_DISPLAYFRAME constant for the last parameter.
  3. Delete the created thumbnail bitmap using the DeleteObject GDI function to prevent a GDI object leak.
  4. The complete code should be similar to the following:HBITMAP hbm = GetWindowThumbnail(width, height);
    ::DwmSetIconicThumbnail(_hwnd, hbm, DWM_SIT_DISPLAYFRAME);
    ::DeleteObject(hbm);
  5. Navigate to the OnSendLivePreview method.
  6. In the method’s code, use the GetWindowPreview method to obtain a live preview bitmap and pass the resulting bitmap to the DwmSetIconicLivePreviewBitmap method (with the DWM_SIT_DISPLAYFRAME constant as before).
  7. Delete the created live preview bitmap as in step 3.
  8. The complete code should be similar to the following:HBITMAP hbm = GetWindowPreview();
    ::DwmSetIconicLivePreviewBitmap(_hwnd, hbm, NULL, DWM_SIT_DISPLAYFRAME);
    ::DeleteObject(hbm);
  9. Compile and run the application.
  10. Let the mouse hover over the application’s taskbar button. A thumbnail with the window’s contents should appear. For example:

    Figure 7

    A standard thumbnail obtained by capturing the window

  11. Now hover over the application’s thumbnail. A live preview of the window’s contents should appear (notice that all other windows become transparent). For example:

    Figure 8

    A standard live preview obtained by capturing the window

  12. Navigate to the OnShowTabbedThumbnails method.

    In this method, we will create thumbnails and live previews for the three tabs that appear in the application’s tab control. Each tab will have an individual thumbnail and live preview in the taskbar, similarly to Internet Explorer 8 tabs.

    Figure 2

    Internet Explorer 8 features tabbed thumbnails of its TDI interface

    The sequence of operations required to display a custom thumbnail and live preview is fairly complicated. In this task we will perform most of the necessary steps.

  13. In the method’s code, ensure that the _usingTabWindows Boolean flag is set to true and return from the method.
  14. Next, call the TabWindow::DisableCustomPreviews method and pass the dialog’s window handle (_hwnd) as the parameter. This is required because we are no longer interested in customizing the main window’s thumbnail and live preview; instead, we will be showing custom tab thumbnails.
  15. Retrieve the number of tabs in the tab control using the TabCount method of the _pTabCtrl member variable.
  16. Repeat steps 16.a through 16.g for each of the tabs in the tab control:
    1. Create a new TabWindow object with the tab control’s window handle (obtained using the Hwnd method) as the target.
    2. The TabWindow class represents a top-level off-screen window that is used to proxy for the actual window for which a custom thumbnail and a live preview are requested. The need for a proxy window is a limitation of the Windows 7 DWM which interacts with top-level windows only. You will see the implementation details of the TabWindow class in subsequent steps during this task.
    3. Use the SetPreviewRequestSink method to set the callback object that is invoked when a thumbnail or live preview bitmap is requested to the _pPreviewSink member variable.
    4. Insert the new TabWindow object into the _tabWindows collection.
    5. Declare a new POINT object and use the GetTabControlPreview method to retrieve a bitmap for the tab control’s window and the offset of the window from the original window frame.
    6. The reason for calculating the offset is that we want the actual taskbar thumbnail to display only the image itself, and not the entire window frame. In this case, the taskbar thumbnail APIs require the offset from the application’s main window frame to be specified. Thumbnail customization can be useful if you want to draw attention to a specific part of the window when it’s displayed as a thumbnail, or when you want to dynamically decide which part of the window to display in the thumbnail. Technically, you're not required to display a part of your window in the thumbnail—you could provide a completely custom image—but this has the potential to confuse users.
    7. Use the SetOffset method of the newly created TabWindow object to specify the offset calculated in the previous step.
    8. Use the AddPreviews method of the _pPreviewSink member variable to add the bitmap created in step 16.c as the preview bitmap of the window returned from TabWindow’s Hwnd method.
    9. Finally, use the SetTabOrder and SetTabActive methods of the _pTaskbar member variable to insert the new tab window thumbnail after the last thumbnail and to indicate that the tab window is now active.
  17. The complete code should be similar to the following:if (_usingTabWindows)
    return;

    _usingTabWindows = true;
    TabWindow::DisableCustomPreviews(Hwnd());

    for (SIZE_T nTab = 0; nTab < _pTabCtrl->TabCount(); ++nTab)
    {
    TabWindow* tabWindow = new TabWindow(
    _pTabCtrl->Hwnd(), this, _pTabCtrl->GetTab(nTab)->Text(), nTab);
    tabWindow->SetPreviewRequestSink(_pPreviewSink);

    _tabWindows.push_back(tabWindow);

    POINT offset;
    HBITMAP tabPreview = GetTabControlPreview(&offset);

    tabWindow->SetOffset(offset);

    _pPreviewSink->AddPreviews(tabWindow->Hwnd(), tabPreview);

    _pTaskbar->SetTabOrder(tabWindow->Hwnd(), NULL);
    _pTaskbar->SetTabActive(tabWindow->Hwnd(), Hwnd(), 0);
    }
  18. Navigate to the OnNotify method.

    In this method, you will update the thumbnail and live preview bitmaps of the tab that is currently losing focus. Because there is no way to retrieve the window bitmap of a control that is not currently drawn, we cache the result from the last time the control was shown—which is exactly when the current selection is leaving this tab page.

  19. In the method’s code, if the notify code is TCN_SELCHANGING, retrieve the tab control preview bitmap as in step 16.c.
  20. Retrieve the currently selected tab number using the CurrentTab method of the _pTabCtrl member variable.
  21. Retrieve the TabWindow object from the _tabWindows collection using the index obtained in the previous step.
  22. Add the preview for the tab window’s window handle as in steps 16.d and 16.e.
  23. Call the InvalidatePreviews method of the TabWindow object to ensure that the preview bitmaps are invalidated and redrawn when next requested; otherwise, a stale bitmap might be shown).
  24. If the notify code was TCN_SELCHANGE, ensure that the _usingTabWindows Boolean member variable is true and call the SetTabActive method of the _pTaskbar member variable to mark the newly selected tab as the current tab in the application’s taskbar button thumbnail list.
  25. The complete code should be similar to the following:if (pNotifyHeader->code == TCN_SELCHANGING && _usingTabWindows)
    {
    POINT offset;
    HBITMAP tabPreview = GetTabControlPreview(&offset);

    SIZE_T curTab = _pTabCtrl->CurrentTab();
    TabWindow* tabWindow = _tabWindows[curTab];
    tabWindow->SetOffset(offset);

    _pPreviewSink->AddPreviews(tabWindow->Hwnd(), tabPreview);
    tabWindow->InvalidatePreviews();
    }
    if (pNotifyHeader->code == TCN_SELCHANGE)
    {
    SIZE_T curTab = _pTabCtrl->CurrentTab();
    _pTabCtrl->Show(curTab);

    if (_usingTabWindows)
    {
    _pTaskbar->SetTabActive(_tabWindows[curTab]->Hwnd(), _hwnd, 0);
    }
    }
  26. Navigate to the RegisterTab method. This method is called by the TabWindow class implementation when the tab window is first created.
  27. In the method’s code, call the RegisterTab method of the _pTaskbar member variable to register the new tab.
  28. The complete code should be similar to the following:_pTaskbar->RegisterTab(pTab->Hwnd(), Hwnd());
  29. Navigate to the ActivateTab method. The TabWindow class implementation calls this method when the user clicks on the tab’s thumbnail.
  30. In the method’s code, use the TabWindow parameter's Index method to retrieve the index of the activated tab.
  31. Call the SetCurrentTab method of the _pTabCtrl member variable and pass the value obtained in the previous step to it.
  32. The SetCurrentTab method dispatches the TCN_SELCHANGING and TCN_SELCHANGE notifications that you handled in steps 19 to 24. Therefore, there’s no need to show the tab’s controls or to set it as the active tab for the taskbar.
  33. Call the SetForegroundWindow API function to set the main dialog as the foreground window (users probably want to interact with the application itself when they click on one of the tab thumbnails, so it should be brought to the foreground).
  34. The complete code should be similar to the following:SIZE_T curTab = pTab->Index();
    _pTaskbar->SetTabActive(pTab->Hwnd(), _hwnd, 0);
    _pTabCtrl->Show(curTab);

    ::SetForegroundWindow(_hwnd);
  35. Navigate to the UnregisterTab method. The TabWindow class implementation calls this method when the tab is about to be removed as a result of the user’s clicking the close button in the upper right corner of the tab’s thumbnail.
  36. In the method’s code, call the UnregisterTab method of the _pTaskbar member variable and pass the tab’s window handle to it (retrieved from the TabWindow’s Hwnd method).
  37. The complete code should be similar to the following:_pTaskbar->UnregisterTab(pTab->Hwnd());
  38. Navigate to the TabWindow.cpp code file and locate the TabWindow class constructor.
  39. We will now implement the functionality of the TabWindow class, which creates a proxy top-level window that handles DWM and other messages on behalf of the underlying tab page.
  40. In the constructor code, initialize the _offset point member variable to 0, 0.
  41. Use the CreateWindowExAPI function to create a window with the c_szWindowClass window class:
    1. Position it -32000 pixels off the screen.
    2. Pass the this pointer as the last parameter.
    3. Assign the result to the _proxy member variable.
  42. The complete code should be similar to the following:_offset.x = _offset.y = 0;
    _proxy = ::CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,c_szWindowClass, szName,
    WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION,-32000, -32000, 10, 10, NULL, NULL, MainDialog::Hinstance(), (LPVOID)this);
  43. Navigate to the TabWindow class destructor (~TabWindow).
  44. In the destructor code, call the UnregisterTab method on the _pMainDlg member variable and pass the this pointer as the parameter.
  45. If the _thumbnail and/or _preview member variables are notNULL, call the DeleteObject GDI function to destroy them.
  46. Finally, call the DestroyWindow API function to destroy the proxy window (_proxy).
  47. The complete code should be similar to the following:_pMainDlg->UnregisterTab(this);

    if (_thumbnail != NULL)
    ::DeleteObject(_thumbnail);
    if (_preview != NULL)
    ::DeleteObject(_preview);

    ::DestroyWindow(_proxy);
  48. Navigate to the EnableDisableCustomPreviews method. This method determines whether the DWM should consult the window procedure to determine the thumbnail and live preview bitmaps for the window.
  49. In the method’s code, initialize a BOOL variable according to the enable parameter.
  50. Call the DwmSetWindowAttribute method with the DWMWA_FORCE_ICONIC_REPRESENTATION flag:
    1. Pass the hwnd parameter as the window handle
    2. Pass the BOOL variable and its size as the third and fourth parameters accordingly.
  51. Repeat the previous step with the DWMWA_HAS_ICONIC_BITMAP flag.
  52. The complete code should be similar to the following:BOOL fForceIconic = enable ? TRUE : FALSE;
    BOOL fHasIconicBitmap = enable ? TRUE : FALSE;

    ::DwmSetWindowAttribute(
    hwnd,
    DWMWA_FORCE_ICONIC_REPRESENTATION,
    &fForceIconic,
    sizeof(fForceIconic));

    ::DwmSetWindowAttribute(
    hwnd,
    DWMWA_HAS_ICONIC_BITMAP,
    &fHasIconicBitmap,
    sizeof(fHasIconicBitmap));
  53. Navigate to the SendThumbnail method. You will call this method from the tab window’s window procedure when a thumbnail is requested by DWM.
  54. In the method’s code, if the _pSink member variable is notNULL:
    1. Use its OnSendThumbnail method to request a thumbnail bitmap dynamically.
    2. Pass the result to the SetThumbnail method.
  55. Next, call the DwmSetIconicThumbnail method and pass the:
    1. _proxy window handle as the window handle
    2. _thumbnail bitmap as the bitmap
    3. DWM_SIT_DISPLAYFRAME constant as the final parameter
  56. The complete code should be similar to the following:if (_pSink != NULL)
    {
    SetThumbnail(_pSink->OnSendThumbnail(_proxy, width, height), false);
    }
    ::DwmSetIconicThumbnail(_proxy, _thumbnail, DWM_SIT_DISPLAYFRAME);
  57. Navigate to the SendPreview method. You will call this method from the tab window’s window procedure when a live preview is requested by DWM.
  58. In the method’s code, if the _pSink member variable is notNULL:
    1. Use its OnSendPreview method to request a live preview bitmap dynamically.
    2. Pass the result to the SetPreview method.
  59. Next, call the DwmSetIconicLivePreviewBitmap with the:
    1. Window handle as in step 53
    2. _preview bitmap as the bitmap
    3. Final parameter as in step 53
  60. The complete code should be similar to the following:if (_pSink != NULL)
    {
    SetThumbnail(_pSink->OnSendThumbnail(_proxy, width, height), false);
    }
    ::DwmSetIconicThumbnail(_proxy, _thumbnail, DWM_SIT_DISPLAYFRAME);
  61. Navigate to the InvalidatePreviews method. Clients of the TabWindow class can called this method to invalidate the existing thumbnail and live preview bitmaps.
  62. In the method’s code, call the DwmInvalidateIconicBitmaps method, and pass the _proxy window handle as the parameter.
  63. The complete code should be similar to the following:::DwmInvalidateIconicBitmaps(_proxy);
  64. Navigate to the WindowProc method. This method contains the heart of the thumbnail handling infrastructure. In the method’s code, handle the messages described in the subsequent steps.
  65. In response to WM_CREATE:
    1. Enable custom previews for the _proxy window.
    2. Call the RegisterTab method of the _pMainDlg member variable.
    3. Pass the this pointer as the parameter.
  66. In response to WM_ACTIVATE, if the low word of the wParam message parameter is WA_ACTIVE, call the ActivateTab method of the _pMainDlg member variable. This is required to activate the tab when the user clicks on the tab’s thumbnail.
  67. In response to WM_SYSCOMMAND, if the wParam message parameter is SC_CLOSE, delegate processing to the DefWindowProc API function. If it’s not SC_CLOSE, send the WM_SYSCOMMAND message to the window handle returned by _pMainDlg’s Hwnd method.
  68. In response to WM_CLOSE, call the DestroyTab method of the _pMainDlg member variable.
  69. In response to WM_DWMSENDICONICTHUMBNAIL, call the SendThumbnail method (extract the thumbnail width from the high word and the thumbnail height from the low word of the lParam message parameter).
  70. In response to WM_DWMSENDICONICLIVEPREVIEWBITMAP, call the SendPreview method.
  71. In the ‘default’ switch clause, delegate processing to the DefWindowProc API function.
  72. The complete code should be similar to the following:LRESULT lResult = 0;

    switch (message)
    {
    case WM_CREATE:
    {
    TabWindow::EnableCustomPreviews(_proxy);
    _pMainDlg->RegisterTab(this);
    break;
    }

    case WM_ACTIVATE:
    if (LOWORD(wParam) == WA_ACTIVE)
    {
    _pMainDlg->ActivateTab(this);
    }
    break;

    case WM_SYSCOMMAND:
    if (wParam != SC_CLOSE)
    {
    lResult = SendMessage( _pMainDlg->Hwnd(), WM_SYSCOMMAND, wParam, lParam);
    }
    else
    {
    lResult = ::DefWindowProc(_proxy, message, wParam, lParam);
    }
    break;

    case WM_CLOSE:
    _pMainDlg->DestroyTab(this);
    break;

    case WM_DWMSENDICONICTHUMBNAIL:
    SendThumbnail(HIWORD(lParam), LOWORD(lParam));
    break;

    case WM_DWMSENDICONICLIVEPREVIEWBITMAP:
    SendPreview();
    break;

    default:
    lResult = ::DefWindowProc(_proxy, message, wParam, lParam);
    break;
    }

    return lResult;
  73. Compile and run the application.
  74. Click Show Tabbed Thumbnails to unleash the tabbed thumbnails functionality. Note that three tab thumbnails should appear (stacked) behind the application’s taskbar button. For example:

    Figure 10

    When multiple thumbnails are available for the same taskbar button, the button appears stacked

  75. Hover over the application’s taskbar button to view the three tab thumbnails and then hover over the thumbnails to view the live previews. Note that all three thumbnails contain the same preview bitmap. This is caused by the fact that we haven’t visited the other tabs yet, so there was no opportunity for them to redraw the preview bitmap. For example:

    Figure 11

    Thumbnail bitmaps and live preview bitmaps are not automatically updated when using tabbed thumbnails and preview customization

  76. Click on the thumbnails and ensure that the corresponding tab page is selected in the application’s main window.
  77. Close one of the tabs by clicking on the close button in the upper right corner of the tab’s thumbnail; ensure that the tab thumbnail is removed and the tab page in the underlying tab control is removed from the main window. For example:

    Figure 12

    Support for closing tabs from the taskbar thumbnail provides a nice finishing touch

Task 3—Using Taskbar Jump Lists

In this task, you will add the functionality to support adding user tasks, custom categories, and destinations to the application’s jump list.

  1. Navigate to the MainDialog.cpp code file and locate the OnCreateJumpList method. This method orchestrates the registration of the application’s file types and the creation of the jump list.
    Note:
    In order to have items appear in the Recent or Frequent categories in the jump list, your application needs a properly defined file-type association in the Windows Registry (although it does not have to be the default handler for that file-type). The sample application can be registered to handle TXT files. For more information about file-type associations, consult https://msdn.microsoft.com/en-us/library/dd758090.aspx
  2. In the method’s code, check the return value of the FileRegistration::AreFileExtensionsRegistered method (pass the c_lpszProgID string to it). If the method returns true, it means that the file associations for the application are already present in the Windows Registry, and there’s no need to register them again.
  3. If the method returns false, call the FileRegistration::RegisterFileExtensions method and pass the c_lpszProgID string and the c_rglpszExtensions array of file extension strings to it.
  4. This method registers the file association in the Windows Registry. Inspect the method’s code to see what entries are added to the registry.
  5. If the RegisterFileExtensions method returns a failure HRESULT, pass it to the ShowAssociationsErrorMessage method and return from the current method.
    Note:
    A possible reason for the RegisterFileExtensions (and later, the UnregisterFileExtensions) method to fail is that the process is not running with administrator privileges. If this is the case, the ShowAssociationsErrorMessage method will display an informative message asking the user to re-run the application with administrative privileges. This does not necessarily represent a development guideline with regard to User Account Control (UAC) and administrative elevation.
  6. Finally, initialize the _pJumpList variable with a new instance of the JumpList helper class, and call the DeleteList and BuildList methods on that instance to construct the jump list.
  7. The complete code should be similar to the following:if (!FileRegistration::AreFileExtensionsRegistered(c_lpszProgID))
    {
    HRESULT hr = FileRegistration::RegisterFileExtensions(
    c_lpszProgID, c_rglpszExtensions, ARRAYSIZE(c_rglpszExtensions));
    if (FAILED(hr))
    {
    ShowAssociationsErrorMessage(hr);
    return;
    }
    }

    _pJumpList = new JumpList();
    _pJumpList->DeleteList();
    _pJumpList->BuildList();
  8. Navigate to the JumpList.cpp code file and locate the BuildList method.
  9. In the method’s code, co-create the CLSID_DestinationList COM object and assign it to the _pcdl member variable (of type ICustomDestinationList).

    The ICustomDestinationList interface is used to manipulate the jump list contents, including custom categories and user tasks, which are the subjects of this task.

  10. Call the BeginList method and assign the removed items collection to the _poaRemoved member variable.

    The removed items collection represents the items removed by the user from the application’s jump list. The user can remove items from the jump list by right-clicking them and selecting ‘Remove from this list’ from the context menu. Applications must honor the user’s selection of removed items—if your application attempts to add an item that was previously removed by the user, the list-building transaction will fail.

    Figure 13

    Users can remove items from the jump list by using the context menu’s ‘Remove from this list’ command

  11. Call the AddCustomCategory and AddUserTasks methods.
  12. If they both succeed, call the CommitList method.
  13. Release the _pcdl and _poaRemoved interface pointers.
  14. The complete code should be similar to the following:HRESULT hr = CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pcdl));
    if (SUCCEEDED(hr))
    {
    UINT cMinSlots;
    hr = _pcdl->BeginList(&cMinSlots, IID_PPV_ARGS(&_poaRemoved));
    if (SUCCEEDED(hr))
    {
    hr = AddCustomCategory();
    if (SUCCEEDED(hr))
    {
    hr = AddUserTasks();
    if (SUCCEEDED(hr))
    {
    hr = _pcdl->CommitList();
    }
    }
    _poaRemoved->Release();
    _poaRemoved = NULL;
    }
    _pcdl->Release();
    _poaRemoved = NULL;
    }
  15. Navigate to the DeleteList method.
  16. In the method’s code, co-create the CLSID_DestinationList object as in step 8.
  17. Call the DeleteList method of the created object.
  18. Release the created interface pointer.
  19. The complete code should be similar to the following:HRESULT hr = CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pcdl));
    if (SUCCEEDED(hr))
    {
    hr = _pcdl->DeleteList(NULL);
    _pcdl->Release();
    }
  20. Navigate to the AddUserTasks method. In this method, you will add user tasks to the application’s jump list.

    The target of the user tasks will be the same application (TaskbarHOL.exe), but the tasks will be defined with a command line parameter. The WinMain function in MainDialog.cpp handles the various command line parameters and displays a message box accordingly.

  21. In the method’s code, co-create a CLSID_EnumerableObjectCollection COM object and assign it to a variable of the IObjectCollection* type.
  22. Retrieve the full file name of the application by using the GetModuleFileName API function.
  23. Create a shell link using the CreateShellLink static method with the application path as the first parameter, and the string “/Task1” as the argument.
  24. Add the shell link created in the previous step to the collection created in step 20.
  25. Create a jump list separator using the CreateSeparator static method.
  26. Repeat step 23.
  27. Create a shell link as in step 22 with the string “/Task3” as the argument.
  28. Repeat step 23.
  29. Query the object collection for the IObjectArray* interface.
  30. Pass the resulting object to the AddUserTasks method of the _pcdl member variable.
  31. Release the object array and the object collection interface pointers.
  32. The complete code should be similar to the following:IObjectCollection *poc;
    HRESULT hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&poc));
    if (SUCCEEDED(hr))
    {
    WCHAR szAppPath[MAX_PATH];
    ::GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath));

    IShellLink *psl;
    hr = JumpList::CreateShellLink(szAppPath, L"/Task1", L"Task 1", &psl);
    if (SUCCEEDED(hr))
    {
    hr = poc->AddObject(psl);
    psl->Release();
    }

    if (SUCCEEDED(hr))
    {
    hr = JumpList::CreateSeparator(&psl);
    if (SUCCEEDED(hr))
    {
    hr = poc->AddObject(psl);
    psl->Release();
    }
    }

    if (SUCCEEDED(hr))
    {
    hr = JumpList::CreateShellLink(szAppPath, L"/Task3", L"Task 3", &psl);
    if (SUCCEEDED(hr))
    {
    hr = poc->AddObject(psl);
    psl->Release();
    }
    }

    if (SUCCEEDED(hr))
    {
    IObjectArray* poa;
    hr = poc->QueryInterface(IID_PPV_ARGS(&poa));
    if (SUCCEEDED(hr))
    {
    hr = _pcdl->AddUserTasks(poa);
    poa->Release();
    }
    }
    poc->Release();
    }
    return hr;

    You’ve just added common user tasks to your application’s jump list. Common user tasks would include launching the application with different command line arguments, navigating to a specific area in the application, or even specifying commands for a running instance of the application. For example, Windows Live Messenger uses jump list tasks for changing the user’s online availability status.

    Figure 3

    The Windows Live Messenger taskbar jump list, featuring user tasks

  33. Navigate to the AddCustomCategory method.

    Custom categories are an advanced feature that can be used by an application to provide more than just the Recent or Frequent system categories. For example, an e-mail application might provide Inbox, Follow-up and other custom categories into which messages are grouped.

  34. In the method’s code, co-create a CLSID_EnumerableObjectCollection as in step 20.
  35. Initialize an array of file names and pass it to the CreateSampleFiles method, which creates the files under the user’s Documents folder.
  36. For each of the files:

    1. Use the CreateShellItem static method to create a shell item from the file name.
    2. Use the IsItemInArray method to ensure that the shell item is not in the array of items removed by the user (_poaRemoved)
    3. Add it to the collection created in step 33.

    The difference between shell items and shell links (represented by IShellItem and IShellLink in the COM Shell APIs), is that a shell item contains only a path to a document that your application is registered to handle. A shell link serves as a shortcut and contains additional information including the application to launch, command-line arguments to pass to that application, a shortcut icon and other properties.

  37. Query the object collection for the IObjectArray* interface.
  38. Add the custom category to the jump list using the AppendCategory method of the _pcdl member variable. Use a category name of your choice.
  39. Release the object array and object collection interface pointers.
  40. The complete code should be similar to the following:IObjectCollection *poc;
    HRESULT hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&poc));
    if (SUCCEEDED(hr))
    {
    LPCWSTR rglpszFiles[] =
    {
    L"TaskbarHOL_File1.txt",
    L"TaskbarHOL_File2.txt",
    L"TaskbarHOL_File3.txt"
    };

    hr = CreateSampleFiles(rglpszFiles, ARRAYSIZE(rglpszFiles));
    if (SUCCEEDED(hr))
    {
    for (UINT i = 0; i < ARRAYSIZE(rglpszFiles); i++)
    {
    IShellItem *psi;
    if (SUCCEEDED(JumpList::CreateShellItem(rglpszFiles[i], &psi)))
    {
    if (!IsItemInArray(psi, _poaRemoved))
    {
    poc->AddObject(psi);
    }
    psi->Release();
    }
    }

    IObjectArray *poa;
    hr = poc->QueryInterface(IID_PPV_ARGS(&poa));
    if (SUCCEEDED(hr))
    {
    hr = _pcdl->AppendCategory(L"Custom Category", poa);
    poa->Release();
    }
    poc->Release();
    }
    }
    return hr;
  41. Compile and run the application.
  42. Click the Create Jump List button. If you have not launched the application with elevated privileges, you will see the following message box:

    Figure 15

    The application displays an error message if it doesn’t have administrative privileges

  43. If this occurs, run the application again as an administrator and repeat the previous step.
  44. Right click the application’s taskbar button and ensure that a jump list is present, with a custom category containing destinations as well as user tasks and a separator. For example:

    Figure 16

    The application’s jump list contains custom destinations, user tasks, and taskbar tasks

  45. Click the user tasks and ensure that the application launches with the specified command line argument as per the lab instructions.
  46. Click the destinations in the custom category and ensure that the application launches with the appropriate command line argument (/HandleDocument).
  47. When you’re done, click Clear Associations to remove any traces of the application’s file type association from the Windows Registry. (This operation requires administrative privileges.)