Thursday, January 27, 2011

Windows 7 Taskbar Extensions in Qt: Taskbar Overlay Icon and Progress bar


Contents

ITaskbarList3 and GCC
Preparation
Initializing the Interface
Setting the Overlay Icon
Set the Progress Bar State and Value



ITaskbarList3 and GCC

To access the Taskbar button from Qt with MINGW it is needed to declare the ITaskbarList3 COM interface, define CLSID_TaskbarList and IID_ITaskbarList3. And to declare used structs (THUMBBUTTON) and enums (TBPFLAG, THUMBBUTTONFLAGS, THUMBBUTTONMASK). All this can be done by finding their declarations in the Windows 7 SDK header file ShObjIdl.h, and adapting them for MINGW GCC in a separate header file.
In my previous post I have explained how this can be accomplished.
The result can be viewed here.

ITaskbarList3 COM interface adapted for MINGW GCC:

//MIDL_INTERFACE("56FDF342-FD6D-11d0-958A-006097C9A090")
DECLARE_INTERFACE_(ITaskbarList, IUnknown)
{
    STDMETHOD (HrInit) (THIS) PURE;
    STDMETHOD (AddTab) (THIS_ HWND hwnd) PURE;
    STDMETHOD (DeleteTab) (THIS_ HWND hwnd) PURE;
    STDMETHOD (ActivateTab) (THIS_ HWND hwnd) PURE;
    STDMETHOD (SetActiveAlt) (THIS_ HWND hwnd) PURE;
};
typedef ITaskbarList *LPITaskbarList;

//MIDL_INTERFACE("602D4995-B13A-429b-A66E-1935E44F4317")
DECLARE_INTERFACE_(ITaskbarList2, ITaskbarList)
{
    STDMETHOD (MarkFullscreenWindow) (THIS_ HWND hwnd, int fFullscreen) PURE;
};
typedef ITaskbarList2 *LPITaskbarList2;

//MIDL_INTERFACE("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")
DECLARE_INTERFACE_(ITaskbarList3, ITaskbarList2)
{
    STDMETHOD (SetProgressValue) (THIS_ HWND hwnd, ULONGLONG ullCompleted, ULONGLONG ullTotal) PURE;
    STDMETHOD (SetProgressState) (THIS_ HWND hwnd, TBPFLAG tbpFlags) PURE;
    STDMETHOD (RegisterTab) (THIS_ HWND hwndTab,HWND hwndMDI) PURE;
    STDMETHOD (UnregisterTab) (THIS_ HWND hwndTab) PURE;
    STDMETHOD (SetTabOrder) (THIS_ HWND hwndTab, HWND hwndInsertBefore) PURE;
    STDMETHOD (SetTabActive) (THIS_ HWND hwndTab, HWND hwndMDI, DWORD dwReserved) PURE;
    STDMETHOD (ThumbBarAddButtons) (THIS_ HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton) PURE;
    STDMETHOD (ThumbBarUpdateButtons) (THIS_ HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton) PURE;
    STDMETHOD (ThumbBarSetImageList) (THIS_ HWND hwnd, HIMAGELIST himl) PURE;
    STDMETHOD (SetOverlayIcon) (THIS_ HWND hwnd, HICON hIcon, LPCWSTR pszDescription) PURE;
    STDMETHOD (SetThumbnailTooltip) (THIS_ HWND hwnd, LPCWSTR pszTip) PURE;
    STDMETHOD (SetThumbnailClip) (THIS_ HWND hwnd, RECT *prcClip) PURE;
};
typedef ITaskbarList3 *LPITaskbarList3;

//MIDL_INTERFACE("c43dc798-95d1-4bea-9030-bb99e2983a1a")
DECLARE_INTERFACE_(ITaskbarList4, ITaskbarList3)
{
    STDMETHOD (SetTabProperties) (HWND hwndTab, STPFLAG stpFlags) PURE;
};
typedef ITaskbarList4 *LPITaskbarList4;

Also is important to add to your project settings (the .pro file) the needed libraries to link with:

LIBS +=  libole32


Preparation

In my example I'm using an object derived from QMainWindow.

First thing I did, I re-implemented the protected virtual function of the QMainWindow in my derived class:

protected:
bool winEvent(MSG * message, long * result);


This functions is needed in order to catch the message which says that the taskbar button is created and ready to be used.

In order to make Windows to initialize the taskbar button for my application, it is needed to call the following function:

RegisterWindowMessage(L"TaskbarButtonCreated");


This function returns the message id which I'm waiting in winEvent.
When I get this message id, I know I can initialize and use the taskbar button.

//...
//some where in the constructor of the MainWindow
messageId = RegisterWindowMessage(L"TaskbarButtonCreated");

//...
bool MainWindow::winEvent(MSG * message, long * result)
{
if (message->message == messageId) {

//get taskbar handle interface
//do stuff
}

return false;
}
//...


Initializing the Interface

When the message id is received in winEvent, it is possible to initialize the interface

//...
ITaskbarList3* m_taskbarInterface;
//...

HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, reinterpret_cast<void**> (&(m_taskbarInterface)));

if (SUCCEEDED(hr)) {
hr = m_taskbarInterface->HrInit();

if (FAILED(hr)) {
m_taskbarInterface->Release();
m_taskbarInterface = NULL;
}
}



Setting the Overlay Icon

This operation is very simple, once we have the taskbar interface intialized.

Step 1: Create a QIcon:
QIcon icon(":/icon.png")


Step 2: Convert QIcon to HICON:
HICON overlay_icon = icon.isNull() ? NULL : icon.pixmap(48).toWinHICON();


Step 3: Set the HICON as overlay icon. If NULL, the previous overlay icon will be removed
m_taskbarInterface->SetOverlayIcon(winId(), overlay_icon, L"Icon description");


Step 4: Release HICON resources
DestroyIcon(overlay_icon);


One important thing, is that overlay icons are not shown if the Taskbar is set to use small icons.

Set the Progress Bar State and Value

The progress bar has five states, they are defined in the TBPFLAG enum.

Setting the desired state:
//set no progress
m_taskbarInterface->SetProgressState(winId(), TBPF_NOPROGRESS);

//set normal
m_taskbarInterface->SetProgressState(winId(), TBPF_NORMAL);

//set indeterminate
m_taskbarInterface->SetProgressState(winId(), TBPF_INDETERMINATE);

//set paused
m_taskbarInterface->SetProgressState(winId(), TBPF_PAUSED);

//set error
m_taskbarInterface->SetProgressState(winId(), TBPF_ERROR);


Setting the current value:
m_taskbarInterface->SetProgressValue(winId(), current_value, max_value);



It is important to release the allocated resources, when the application is being closed:
m_taskbarInterface->Release();


Below is a screen shot of the application:



The Qt Project can be found here.

Links:
Link to EcWin7, from here I understood how to import COM declarations from Windows SDK headers and about RegisterWindowMessage

No comments: