Thursday, March 31, 2011

Qt Windows 7 extend frame into client area with transparency

I wanted to have something similar to these below:


Making the window transparent did not help me much. I was getting this:

I could not get rid of the margins.

After some research I figured out, and the result can be seen below:




To get merge the margins with the window frame it is enough to call DwmExtendFrameIntoClientArea with the margins value set to -1.

void ExtendFrameIntoClientArea(QWidget* widget) {
    MARGINS margins = {-1};

    DwmExtendFrameIntoClientArea(widget->winId(), &margins);
}


The transparency part is easy, it was published long time ago:

long EnableBlurBehindWidget(QWidget* widget, bool enable)
{
    HWND hwnd = widget->winId();
    HRESULT hr = S_OK;

    widget->setAttribute(Qt::WA_TranslucentBackground, enable);
    widget->setAttribute(Qt::WA_NoSystemBackground, enable);

    // Create and populate the Blur Behind structure
    DWM_BLURBEHIND bb = {0};

    bb.dwFlags = DWM_BB_ENABLE;
    bb.fEnable = enable;
    bb.hRgnBlur = NULL;

    DwmEnableBlurBehindWindow(hwnd, &bb);
    return hr;
}

I have managed to compile this using MINGW. The DwmEnableBlurBehindWindow and DwmExtendFrameIntoClientArea are loaded directly from dwmapi.dll.

#include <windows.h>

#define DWM_BB_ENABLE                 0x00000001  // fEnable has been specified

typedef struct _DWM_BLURBEHIND
{
    DWORD dwFlags;
    BOOL fEnable;
    HRGN hRgnBlur;
    BOOL fTransitionOnMaximized;
} DWM_BLURBEHIND, *PDWM_BLURBEHIND;

typedef struct _MARGINS
{
    int cxLeftWidth;      // width of left border that retains its size
    int cxRightWidth;     // width of right border that retains its size
    int cyTopHeight;      // height of top border that retains its size
    int cyBottomHeight;   // height of bottom border that retains its size
} MARGINS, *PMARGINS;

extern "C"
{
    typedef HRESULT (WINAPI *t_DwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind);
    typedef HRESULT (WINAPI *t_DwmExtendFrameIntoClientArea)(HWND hwnd, const MARGINS *pMarInset);
}

void DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS *pMarInset) {
    HMODULE shell;

    shell = LoadLibrary(L"dwmapi.dll");
    if (shell) {
        t_DwmExtendFrameIntoClientArea set_window_frame_into_client_area = reinterpret_cast<t_DwmExtendFrameIntoClientArea>(GetProcAddress (shell, "DwmExtendFrameIntoClientArea"));
        set_window_frame_into_client_area(hwnd, pMarInset);

        FreeLibrary (shell);
    }

}

void DwmEnableBlurBehindWindow(HWND hwnd, const DWM_BLURBEHIND* pBlurBehind) {
    HMODULE shell;

    shell = LoadLibrary(L"dwmapi.dll");
    if (shell) {
        t_DwmEnableBlurBehindWindow set_window_blur = reinterpret_cast<t_DwmEnableBlurBehindWindow>(GetProcAddress (shell, "DwmEnableBlurBehindWindow"));
        set_window_blur(hwnd, pBlurBehind);

        FreeLibrary (shell);
    }
}

The project can be downloaded from here.

Windows 7 Taskbar Extensions in Qt: Tab Thumbnails

In this post I'll explain how I implemented this Windows 7 feature using Qt with MINGW32.

For this purpose I have created a simple browser with tabs.


The main blockers implementing this using Qt with MINGW32 are the missing defintions of various important Windows 7 SDK functions, interfaces, enums, constants and structures (e.g. ITaskbarList3, ITaskbarList4, CLSID_TaskbarList, STPFLAG, DWMWINDOWATTRIBUTE, and DWM functions (DwmInvalidateIconicBitmaps, DwmSetIconicThumbnail, DwmSetIconicLivePreviewBitmap, DwmSetWindowAttribute)).

In order to fix this I had to define them mingw friendly in my project. The DWM functions are loaded during runtime from dwmapi.dll.

In the win7utils.h and win7utils.cpp you can see the final result.

Initializing

Firstly the program has to register the TaskbarButtonCreated message. This will create a taskbar button for this application. Without this it is impossible to use the new Windows 7 features.


After having the taskbar button created the application has to initialize an ITaskbarList3 or ITaskbarList4 interface in order to be able to access the new features.

In our event filter:

bool MyClass::eventFilter(void *message_, long *result)
{
    static unsigned int taskBarCreatedId = WM_NULL;

    MSG* message = static_cast(message_);

    if (taskBarCreatedId == WM_NULL) {
        taskBarCreatedId = RegisterWindowMessage(L"TaskbarButtonCreated");
        return false;
    }

    if (message->message == taskBarCreatedId &&
        message->hwnd == parent->winId()) 
        //very important to check to which window this message is address
        //since it is possible to get a dozen of them
        //the parent can be a QMainWindow or any other QWidget which acts as a 
        //tab container
    {
        //init the ITaskbarList3  interface
        //announce that the interface is ready
        return true;
    }
    //...
}



Adding tabs

The most important ITaskbarList3 functions to work with in this case are: RegisterTab, SetTabActive, SetTabOrder and UnregisterTab.

You should not register the window or widget which contains these tabs as a tab.

First thing to do is to capture all messages sent to the application. This can be achieved by substituting the current application's event filter with ours.
This will route all messages to our own function.

// MyClass.h
class MyClass {
//..
  static bool eventFilter(void *message_, long *result);
  static QCoreApplication::EventFilter m_oldEventFilter;
//..
};

//MyClass.cpp
QCoreApplication::EventFilter TabsManager::m_oldEventFilter = NULL;

MyClass::MyClass {
//..
   m_oldEventFilter = qApp->setEventFilter(&MyClass::eventFilter);
}

Before registering a widget as a tab it is necessary to set two attributes to the window handle. This can be achieved by setting the DWMWA_FORCE_ICONIC_REPRESENTATION and DWMWA_HAS_ICONIC_BITMAP to true:

void EnableWidgetIconicPreview(QWidget* widget) {
    BOOL enable = TRUE;

    DwmSetWindowAttribute(
        widget->winId(),
        DWMWA_FORCE_ICONIC_REPRESENTATION,
        &enable,
        sizeof(enable));

    DwmSetWindowAttribute(
        widget->winId(),
        DWMWA_HAS_ICONIC_BITMAP,
        &enable,
        sizeof(enable));
}

This code will make the application receive WM_DWMSENDICONICTHUMBNAIL and WM_DWMSENDICONICLIVEPREVIEWBITMAP messages when a thumbnail is requested for the registered widget.

Another important thing which is related to Qt, you SHOULD NOT REGISTER AS A TAB A WIDGET WHICH IS BEING USED IN THE MAINWINDOW OR DIALOG.
This will not work.
The solution is to create a new blank widget, register it, and map the real widget to this blank widget. The thumbnails in the taskbar will be provided by the real widgets but the messages will be addressed to the blank ones.

So each time when a WM_DWMSENDICONICTHUMBNAIL or WM_DWMSENDICONICLIVEPREVIEWBITMAP message is received, it is addressed to the blank registered widget, not the real one.

The steps:
1) Create a new QWidget*
2) Map this created QWidget to the real QWidget is being used. This is apt to you.
3) Set iconic preview enabled to the created QWidget
4) Register the created QWidget as a tab
5) Set tab order
6) Set it as the active tab

//ITaskbarList3*  m_taskbarHandler;
//QMap<WId, QWidget*> m_widgetMap;
//QWidget* m_parent;

void MyClass::addTab(QWidget* widget) {

    QWidget* tab_widget = new QWidget();

    //enable iconic preview
    EnableWidgetIconicPreview(tab_widget->winId(), true);

    //map it
    m_widgetMap[tab_widget->winId()] = widget;


    //register it
    m_taskbarHandler->RegisterTab(tab_widget->winId(), m_parent->winId());
    m_taskbarHandler->SetTabOrder(tab_widget->winId(), NULL);
    m_taskbarHandler->SetTabActive(NULL, tab_widget->winId(), 0);
}

Processing the received messages

In our own event filter we'll receive many messages, the most important ones are:

The message code provided by the RegisterWindowMessage function. When the "TaskbarButtonCreated" message was registered - When this happens our tab manager will initialize the ITaskbarList3 interface.

WM_DWMSENDICONICTHUMBNAIL - from MSDN: "instructs a window to provide a static bitmap to use as a thumbnail representation of that window."

WM_DWMSENDICONICLIVEPREVIEWBITMAP - from MSDN: "Instructs a window to provide a static bitmap to use as a live preview (also known as a Peek preview) of that window."

WM_ACTIVATE - when a thumbnail was clicked.

WM_CLOSE - when a thumbnail is about to be closed.

Providing the static bitmap

When a WM_DWMSENDICONICTHUMBNAIL message is received first thing it is checked if this is addressed to one of our registered widgets. This check can be done by comparing widget->winId() to message->hwnd. If they match we have to provide a static bitmap.
The created bitmap is set by calling the DwmSetIconicThumbnail function

Providing the live preview

Basically is almost the same as in the first case, except that the DwmSetIconicLivePreviewBitmap function is called.

An example:
case WM_DWMSENDICONICTHUMBNAIL:
     //check if this is message is addressed to one of our widgets
     if (!m_widgetMap.contains(message.hwnd)) return false;
     
     //get the real widget
     widget = m_widgetMap[message.hwnd];

     QPixmap thumbnail = QPixmap::grabWidget(widget).scaled(size, Qt::KeepAspectRatio);

     //QPixmap::Alpha in case the image has transparent regions
     HBITMAP hbitmap = thumbnail.toWinHBITMAP(QPixmap::Alpha);

     DwmSetIconicThumbnail(id, hbitmap, 0);
     DeleteObject(hbitmap);
     return true;

case WM_DWMSENDICONICLIVEPREVIEWBITMAP:
     //check if this is message is addressed to one of our widgets
     if (!m_widgetMap.contains(message.hwnd)) return false;
     
     //we want to grap the main window and show as a live preview
     widget = parent;

     QPixmap thumbnail = QPixmap::grabWidget(widget).scaled(size, Qt::KeepAspectRatio);

     HBITMAP hbitmap = thumbnail.toWinHBITMAP(QPixmap::NoAlpha);

     DwmSetIconicLivePreviewBitmap(id, hbitmap, 0);
     DeleteObject(hbitmap);
     return true;

case WM_ACTIVATE :
     if (LOWORD(message->wParam) == WA_ACTIVE) {
        //check if this is message is addressed to one of our widgets
        if (!m_widgetMap.contains(message->hwnd)) return false;
   
         //get the real widget
         widget = m_widgetMap[message->hwnd];
         
         //announce that widget was activated
         //..
     }
     //route message further
     return false;

case WM_CLOSE :
      //The same as for WM_ACTIVE except ..
      //announce that widget is about to be removed
      //..
      
      return false;


Updating the tab

When the content of the tab has changed, the thumbnail bitmap will not change by itself. That is why is needed to call DwmInvalidateIconicBitmaps. This will update the taskbar thumbnail for the specified tab.


The source code can be downloaded from here.

Strix Code Blog: Windows 7 or Vista UAC Shield Icon in Qt

Strix Code Blog: Windows 7 or Vista UAC Shield Icon in Qt: "(For a way to embed an application manifest with QtCreator see here) In a Qt application you cannot use the BCM_SETSHIELD message to show t..."

Thursday, March 24, 2011

Windows 7 Taskbar Extensions in Qt: Window Thumbnail Image

In this tutorial I'll explain how to set a iconic bitmap on a window to use as a thumbnail representation using Qt with MINGW.

Missing definitions and declarations

To be able to set a custom thumbnail, the following functions are required:
- DwmSetIconicThumbnail
- DwmSetWindowAttribute
- DwmSetIconicLivePreviewBitmap

These functions can be found in the Dwmapi.h header file. Unfortunately this file is not present in MINGW.

The solution is to call them directly from Dwmapi.dll.

Firstly it is needed to typedef a function pointer type to these functions.

extern "C"
{
    typedef HRESULT (WINAPI *t_DwmSetIconicThumbnail)(HWND hwnd, HBITMAP hbmp, DWORD dwSITFlags);
    typedef HRESULT (WINAPI *t_DwmSetWindowAttribute)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
    typedef HRESULT (WINAPI *t_DwmSetIconicLivePreviewBitmap)(HWND hwnd, HBITMAP hbmp, POINT *pptClient, DWORD dwSITFlags);
}

Then, make wrappers which will call these functions directly from the dll.

void DwmSetIconicThumbnail(HWND hwnd, HBITMAP hbmp, DWORD dwSITFlags) {
    HMODULE shell;

    shell = LoadLibrary(L"dwmapi.dll");
    if (shell) {
        t_DwmSetIconicThumbnail set_iconic_thumbnail = reinterpret_cast<t_DwmSetIconicThumbnail>(GetProcAddress (shell, "DwmSetIconicThumbnail"));
        set_iconic_thumbnail(hwnd, hbmp, dwSITFlags);

        FreeLibrary (shell);
    }
}

void DwmSetWindowAttribute(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute) {
    HMODULE shell;

    shell = LoadLibrary(L"dwmapi.dll");
    if (shell) {
        t_DwmSetWindowAttribute set_window_attribute = reinterpret_cast<t_DwmSetWindowAttribute>(GetProcAddress (shell, "DwmSetWindowAttribute"));
        set_window_attribute(hwnd, dwAttribute, pvAttribute, cbAttribute);

        FreeLibrary (shell);
    }
}

void DwmSetIconicLivePreviewBitmap(HWND hwnd, HBITMAP hbmp, POINT *pptClient, DWORD dwSITFlags) {
    HMODULE shell;

    shell = LoadLibrary(L"dwmapi.dll");
    if (shell) {
        t_DwmSetIconicLivePreviewBitmap set_live_preview = reinterpret_cast<t_DwmSetIconicLivePreviewBitmap>(GetProcAddress (shell, "DwmSetIconicLivePreviewBitmap"));
        set_live_preview(hwnd, hbmp, pptClient, dwSITFlags);

        FreeLibrary (shell);
    }
}

Project settings

It is needed to add extra linkage libraries options to the linker.

LIBS += libgdi32


Setting the thumbnail

You need to do the following:
1) Call the DwmSetWindowAttribute function to set window attributes for non-client rendering to DWMWA_FORCE_ICONIC_REPRESENTATION and DWMWA_HAS_ICONIC_BITMAP
2) Catch WM_DWMSENDICONICTHUMBNAIL and WM_DWMSENDICONICLIVEPREVIEWBITMAP Windows events
3) Call DwmSetIconicThumbnail, on WM_DWMSENDICONICTHUMBNAIL event, to set thumbnail bitmap
4) Call DwmSetIconicLivePreviewBitmap, on DWMSENDICONICLIVEPREVIEWBITMAP event, to set the window live preview bitmap

Firslty, set window attributes:

void MainWindow::EnableIconicPreview(bool enable) {
    BOOL fForceIconic = enable ? TRUE : FALSE;
    BOOL fHasIconicBitmap = enable ? TRUE : FALSE;

    DwmSetWindowAttribute(
        this->winId(),
        DWMWA_FORCE_ICONIC_REPRESENTATION,
        &fForceIconic,
        sizeof(fForceIconic));


    DwmSetWindowAttribute(
        this->winId(),
        DWMWA_HAS_ICONIC_BITMAP,
        &fHasIconicBitmap,
        sizeof(fHasIconicBitmap));
}

Secondly, re-implement the QMainWindow winEvent protected function:
class MainWindow : public QMainWindow
{
//..

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

//..
};

bool MainWindow::winEvent(MSG * message, long * result)
{
    switch (message->message)
    {
    case WM_DWMSENDICONICTHUMBNAIL:
        //call DwmSetIconicThumbnail
        break;

    case WM_DWMSENDICONICLIVEPREVIEWBITMAP:
        //call  DwmSetIconicLivePreviewBitmap
        break;
    default:
        return false;
    }

    return false;
}

On WM_DWMSENDICONICTHUMBNAIL event

Convert a QPixmap image to HBITMAP, and call DwmSetIconicThumbnail for this window:

QPixmap image(":/qtlogo.png");
HBITMAP hbitmap = m_image.toWinHBITMAP();
DwmSetIconicThumbnail(this->winId(), hbitmap, 0);
DeleteObject(hbitmap);



On WM_DWMSENDICONICLIVEPREVIEWBITMAP event

In this case it is required to provide the taskbar with a custom image of the selected window:

HBITMAP hbitmap = QPixmap::grabWidget(this).scaled(this->size(), Qt::KeepAspectRatio).toWinHBITMAP();
DwmSetIconicLivePreviewBitmap(this->winId(), hbitmap, 0, 0);

DeleteObject(hbitmap);

The result:

The code can be downloaded from here.

Useful link.

Windows 7 Taskbar Extensions in Qt: Thumbnail Toolbar

In this tutorial I'll explain how to create a Windows 7 thumbnail toolbar using Qt with MINGW GCC. At the end of the post you'll find the link to the project's code.

Missing definitions and declarations

MINGW GCC doesn't have all needed definitions and declarations to create a windows 7 thumbnail toolbar. That is why they need to be defined. I've explained how I did it in my previous post. You can browse the definitions file here.

Project settings

It is needed to add extra linkage libraries options to the linker.

LIBS += libcomctl32 libole32
Creating the thumbnail toolbar

In short you need to do the following:
1) Create an instance to the ITaskbarList3 COM interface
2) Create a list of images and add it to the thumbnail toolbar
3) Create a list of thumbnail toolbar buttons and add it to the toolbar
4) Update the buttons or the images when needed

What you can do:
1) Show/hide, disable/enable, change button details, update the image list
2) Have a thumbnail toolbar per program's window

What you can't do:
1) Add more than 7 buttons to the toolbar
2) Remove the toolbar (it will be destroyed when the window associated to it will be destroyed.)

Initialize the ITaskbarList3 COM interface

Firstly you need to implement your own winEvent function. This can be done by overriding the QMainWindow's winEvent protected function.

class MainWindow : public QMainWindow
{
//...
protected:
     bool winEvent(MSG * message, long * result);
//...
};

In order to initialize the ITaskbarList3 COM interface it is needed to register the
"TaskbarButtonCreated" event.

//..
unsigned int messageId = RegisterWindowMessage(L"TaskbarButtonCreated");
//..

When the event is registered, it is possible to obtain access to ITaskbarList3.

Putting all together:
//...
bool MainWindow::winEvent(MSG * message, long * result)
{
    static UINT taskBarCreatedId = WM_NULL;
    if (taskBarCreatedId == WM_NULL) {
        taskBarCreatedId = RegisterWindowMessage(L"TaskbarButtonCreated");
    }

    if (message->message == taskBarCreatedId) {
        //initialize the ITaskbarList3 interface
    } 

    return false;
}

Initializing the Interface

//..
void MainWindow::W7ToolbarInit() {
    HRESULT hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3,
                                  reinterpret_cast<void**> (&(m_w7toolbar)));

    if (SUCCEEDED(hr)){

        hr = m_w7toolbar->HrInit();

        if (FAILED(hr)) {
            m_w7toolbar->Release();
            m_w7toolbar = NULL;
        }
    }
}
//..

Create the list of images

The images I used in the project were PNG files 20x20 pixels, 32 bit, with transparency.

Create the image list to hold 4 images:

HIMAGELIST himl = ImageList_Create(20, 20, ILC_COLOR32, 4, 0);


Add the images to the list:
QPixmap img;
QBitmap mask;

img = QIcon(":/back.png").pixmap(20);
mask  = img.createMaskFromColor(Qt::transparent);
ImageList_Add(himl, img.toWinHBITMAP(QPixmap::PremultipliedAlpha), mask.toWinHBITMAP());

//repeat the same thing for the rest of the images

Set the thumbnailtoolbar's image list to the :
m_w7toolbar->ThumbBarSetImageList(this->winId(), himl);
"this" is a pointer to a QMainWindow object, but it also can be a QWidget pointer.

Free the allocated memory:
ImageList_Destroy(himl);

Add the buttons

Each button structure holds information about the button. Such as image index, the index from the image list, tool tip text, state, visibility and other flags.

It is important to remember, the thumbnail toolbar can't hold more than 7 buttons.

Let's say we want to have three buttons:
class MainWindow : public QMainWindow
{
//..
     THUMBBUTTON m_thbButtons[3];
//..
};

Initializing and adding the buttons:
#define IDTB_FIRST 3000

void MainWindow::W7ToolbarButtonsInit() {
    QString tooltips[3] = {"Prev", "Play", "Next"};

    for (int index = 0; index < 3; index++) {
        wcscpy(m_thbButtons[index].szTip, tooltips[index].toStdWString().c_str());

        //the ID of the button
        m_thbButtons[index].iId = IDTB_FIRST + index;
        //the image of the button, index from the image list
        m_thbButtons[index].iBitmap = index;
        
        //show tooltip and use the bitmpap index
        m_thbButtons[index].dwMask = (THUMBBUTTONMASK)(THB_BITMAP | THB_FLAGS | THB_TOOLTIP);

        //set the button enabled
        m_thbButtons[index].dwFlags = (THUMBBUTTONFLAGS)(THBF_ENABLED);

    }

    //set the buttons
    m_w7toolbar->ThumbBarAddButtons(this->winId(), 3, m_thbButtons);
}

Once the buttons are added, they should be visible.

Catching the thumbnailtoolbar click events

When a button is pressed, an event with the button's id is generated. These events can be caught in the winEvent function.

//...
bool MainWindow::winEvent(MSG * message, long * result)
{
    static UINT taskBarCreatedId = WM_NULL;
    if (taskBarCreatedId == WM_NULL) {
        taskBarCreatedId = RegisterWindowMessage(L"TaskbarButtonCreated");
    }

    if (message->message == taskBarCreatedId) {
        W7ToolbarInit();
        W7ToolbarSetImages();
        W7ToolbarButtonsInit();

    } else switch (message->message){
    case WM_COMMAND:
        {
            //get the button index
            int buttonId = LOWORD(message->wParam) - IDTB_FIRST;

            if ((buttonId >= 0) && (buttonId < 3)) {

                qDebug() << "Button " << buttonId << " was pressed";
                if (buttonId == 1) { //if "Play|Pause" was pressed

                    if (m_thbButtons[1].iBitmap == 1) {
                        //set the Pause image index
                        m_thbButtons[1].iBitmap = 3;
                        wcscpy(m_thbButtons[1].szTip, L"Pause");
                    } else {
                        //set the Play image index
                        m_thbButtons[1].iBitmap = 1;
                        wcscpy(m_thbButtons[1].szTip, L"Play");
                    }

                    //update the thumbnailtoolbar
                    m_w7toolbar->ThumbBarUpdateButtons(this->winId(), 3, m_thbButtons);
                }

            }


            break;
        }

    default:
        return false;

    }

    return false;
}

The code can be downloaded from here.
The screenshot can be seen below:

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

Wednesday, January 26, 2011

Windows 7 Taskbar Extensions in Qt: Jump List


Contents

Preparation
Working With Jump Lists



As of Windows 7 the taskbar has become more efficient, and has a lot of new features. Such as Jump List, Thumbnails, Thumbnail toolbar, Overlay Icons, Progress bar and other things.

This article focuses on how to manage/customize a Jump List using Qt with GCC.

A Jump List is like a customized Start menu for your application. It can contain Destinations and Tasks. A Destination can be a link to a file, location, URL. A Task can be viewed as an action or command which the application can perform.

It contains two built-in Destinations categories, Recent and Frequent. These lists are managed automatically.
By default a Jump List contains a Recent category. Each time a file is opened with the application, or file-based applications through SHAddToRecentDocs, or the common file dialog, it will be added automatically to Recent and Frequent.

Preparation

1) Register your application to handle a file type

To enable the Recent category it is enough to register a file type with your application, and it is not necessary to be the default file-type handler.

Below is a simple example of registry keys and values needed to register a .rgm file extension to C:\Path\to\bin\app.exe application.

It is possible to copy this into a .reg file and import it.


Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.rgm]
@="rgm_auto_file"

[HKEY_CLASSES_ROOT\rgm_auto_file]
@="RGM file"

[HKEY_CLASSES_ROOT\rgm_auto_file\DefaultIcon]
@="C:\\Path\\to\\bin\\app.exe,1"

[HKEY_CLASSES_ROOT\rgm_auto_file\shell]

[HKEY_CLASSES_ROOT\rgm_auto_file\shell\Open]

[HKEY_CLASSES_ROOT\rgm_auto_file\shell\Open\Command]
@="C:\\Path\\to\\bin\\app.exe '%1'"


2) Find and declare missing declarations of the needed COM interfaces, their CLSID and IID, and “friends” (enums, typedefs).

In order to be able to manage the Jump List from your application it is necessary to have access to different COM interfaces. Not all of them are accessible within MINGW with GCC.
This can be done by finding their definitions in Windows 7 SDK header files.

For instance:
ICustomDestinationList is declared in c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\ShObjIdl.h:


EXTERN_C const CLSID CLSID_DestinationList;
#ifdef __cplusplus
class DECLSPEC_UUID("77f10cf0-3db5-4966-b520-b7c54fd35ed6")
DestinationList;
#endif

MIDL_INTERFACE("6332debf-87b5-4670-90c0-5e57b408a49e")
ICustomDestinationList : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE SetAppID(
/* [string][in] */ __RPC__in_string LPCWSTR pszAppID) = 0;

virtual HRESULT STDMETHODCALLTYPE BeginList(
/* [out] */ __RPC__out UINT *pcMinSlots,
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;

virtual HRESULT STDMETHODCALLTYPE AppendCategory(
/* [string][in] */ __RPC__in_string LPCWSTR pszCategory,
/* [in] */ __RPC__in_opt IObjectArray *poa) = 0;

virtual HRESULT STDMETHODCALLTYPE AppendKnownCategory(
/* [in] */ KNOWNDESTCATEGORY category) = 0;

virtual HRESULT STDMETHODCALLTYPE AddUserTasks(
/* [in] */ __RPC__in_opt IObjectArray *poa) = 0;

virtual HRESULT STDMETHODCALLTYPE CommitList( void) = 0;

virtual HRESULT STDMETHODCALLTYPE GetRemovedDestinations(
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;

virtual HRESULT STDMETHODCALLTYPE DeleteList(
/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszAppID) = 0;

virtual HRESULT STDMETHODCALLTYPE AbortList( void) = 0;

};

will become:

DEFINE_GUID(CLSID_DestinationList,0x77f10cf0,0x3db5,0x4966,0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6);
DEFINE_GUID(IID_ICustomDestinationList,0x6332debf,0x87b5,0x4670,0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e);

DECLARE_INTERFACE_(ICustomDestinationList, IUnknown)
{
STDMETHOD (SetAppID) (LPCWSTR pszAppID);
STDMETHOD (BeginList) (UINT *pcMinSlots, REFIID riid, void **ppv) PURE;
STDMETHOD (AppendCategory) (LPCWSTR pszCategory, IObjectArray *poa) PURE;
STDMETHOD (AppendKnownCategory) (KNOWNDESTCATEGORY category) PURE;
STDMETHOD (AddUserTasks) (IObjectArray *poa) PURE;
STDMETHOD (CommitList) (void) PURE;
STDMETHOD (GetRemovedDestinations) (REFIID riid, void **ppv) PURE;
STDMETHOD (DeleteList) (LPCWSTR pszAppID) PURE;
STDMETHOD (AbortList) (void) PURE;

};
typedef ICustomDestinationList *LPICustomDestinationList;

Here you cand find more.

To perform different jobs on a Jump List you’ll need to declare more interfaces.

Minimum needed to add items to Recent or/and create custom category:
ICustomDestinationList, IObjectArray

To create Tasks or/and to add customized items to Recent/Frequent/Custom list:
ICustomDestinationList, IObjectArray, IShellItem, IShellLink, IPropertyStore


One other helpful hint, each time you encounter the following macro in Windows 7 SDK examples:
IID_PPV_ARGS(&pItem)

you can substitute it with
IID_Of_The_Item’s_Class, reinterpret_cast<void**>(&(pItem))



3) Add to your Qt project file the needed libraries to link with
LIBS +=  libole32


Working With Jump Lists


How to Add Items to Recent/Frequent list

The simplest way to do this is to call
QString filePath("c:\\file.rgm");
SHAddToRecentDocs(0x00000003, filePath.toStdWString().c_str());

If the file extension of the filePath is not registered to this application, this call will have no effect.


Show/hide Recent or/and Frequent list

- Create the custom jump list object
ICustomDestinationList* destinationList;
CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, reinterpret_cast<void**> (&(destinationList)));

- Initialize the list

UINT max_count;
IObjectArray* objectArray;
destinationList->BeginList(&max_count, IID_IObjectArray, reinterpret_cast<void**> (&(objectArray)));

This function will start a “transaction”.

Returned values through max_count represents the max allowed recent items to display in Jump List, and objectArray holds the items which have been removed by the user.

- Enable the Frequent list
destinationList->AppendKnownCategory(KDC_FREQUENT);

- Enable the Recent list
destinationList->AppendKnownCategory(KDC_RECENT);


- Commit the menu “transaction”
destinationList->CommitList();


The whole example can be found here :
https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example1
For simplicity there are no checks for valid pointers or initialization results.


Creating a Custom Jump List Cateogry

- The first step is to declare the COM interfaces, enums and typedefs for:
ICustomDestinationList, IObjectCollection, IObjectArray, IShellItem, IShellLink, IPropertyStore

All the details can be found here.

- It is needed to find a way to initialize a IShellItem, providing just a valid file path.
I chose to call SHCreateItemFromParsingName. But since it is declared in Shobjidl.h, I can’t include this file with GCC. So I have to load this function from the shell32.dll itself.

Typedef a function pointer type:
extern "C"
{
typedef HRESULT (WINAPI *t_SHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv);
}

then use it:

IShellItem* FilePath2ShellItem(QString path) {
HMODULE shell;
IShellItem *shell_item = NULL;
t_SHCreateItemFromParsingName SHCreateItemFromParsingName = NULL;

shell = LoadLibrary(L"shell32.dll");

if (shell) {
SHCreateItemFromParsingName = reinterpret_cast<t_SHCreateItemFromParsingName>(GetProcAddress (shell, "SHCreateItemFromParsingName"));

if (SHCreateItemFromParsingName != NULL) {
SHCreateItemFromParsingName(path.toStdWString().c_str(), NULL, IID_IShellItem, reinterpret_cast<void**> (&(shell_item)));
}

FreeLibrary (shell);
}

return shell_item;
}


- Init a ICustomDestinationList:
UINT max_count = 0;
IObjectArray* objectArray;
ICustomDestinationList* destinationList;

//create the custom jump list object
CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, reinterpret_cast<void**> (&(destinationList)));

- Start the “transaction” :
destinationList->BeginList(&max_count, IID_IObjectArray, reinterpret_cast<void**> (&(objectArray)));

IObjectCollection* obj_collection;
CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,
CLSCTX_INPROC, IID_IObjectCollection, reinterpret_cast<void**> (&(obj_collection)));


- Init an object array from obj_collection:
IObjectArray* object_array;

obj_collection->QueryInterface(IID_IObjectArray, reinterpret_cast<void**> (&(object_array)));

- Create and add shell items to this object_array. It is important to know, if the file path is not valid, or the file extension is not registered to this application, SHCreateItemFromParsingName will fail to create a valid IShellItem.
obj_collection->AddObject(FilePath2ShellItem("C:\\file1.rgm"));
obj_collection->AddObject(FilePath2ShellItem("C:\\file2.rgm"));


- Add the object array as a new category in the Jump List:
destinationList->AppendCategory(L“Custom entry”, object_array);

- Commit the changes:
destinationList->CommitList();


- Release the resources:
object_array->Release();
obj_collection->Release();
objectArray->Release();
destinationList->Release();
The Result

The code can be found here https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example2


Adding Tasks


To populate the Tasks list it is needed to create a collection of IShellLinks and call
ICustomDestinationList::AddUserTasks.

As previously this must be done after calling ICustomDestinationList::BeginList,
and applying it by calling ICustomDestinationList::CommiList after.

A task is a shortcut.

To create a task item can be used the code below:
IShellLink* shell_link = NULL;
CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<void**> (&(shell_link)));


Set the values:
shell_link->SetPath(L"C:\\path\\to\\app.exe");
shell_link->SetArguments(L"Some args");
shell_link->SetIconLocation(L"C:\\path\\to\\app.exe", app_index);
shell_link->SetDescription(L"Description");


To set the title we need to use a IPropertyStore object.
PROPVARIANT pv;
IPropertyStore* prop_store = NULL;
shell_link->QueryInterface(IID_IPropertyStore, reinterpret_cast<void**> (&(prop_store)));

InitPropVariantFromString(L"Title", &pv);
prop_store->SetValue(PKEY_Title, pv);
prop_store->Commit();


If we want this item to be a separator:
PROPVARIANT pv;
IPropertyStore* prop_store = NULL;
shell_link->QueryInterface(IID_IPropertyStore, reinterpret_cast<void**> (&(prop_store)));

InitPropVariantFromBoolean(TRUE, &pv);
prop_store->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv);
prop_store->Commit();


The rest is almost similar to the example of creating a custom category.

Instead of:

obj_collection->AddObject(FilePath2ShellItem("C:\\file1.rgm"));
obj_collection->AddObject(FilePath2ShellItem("C:\\file2.rgm"));
destinationList->AppendCategory(L“Custom entry”, object_array);

will be

obj_collection->AddObject(CreateShellLink("Task 1", "Task Description", "C:\\path\\to\\app.exe", "app args", "c:\\path\\to\\ico\\resources", 0));

destinationList->AddUserTasks(object_array);


The created tasks from the example3

The sources for the example 3 can be found here https://github.com/xfreebird/blogstuff/tree/master/qt/jumplist_example3



Useful links:

Taskbar Extensions
Hilo: Developing C++ Applications for Windows 7
Chapter 14: Adding Support for Windows 7 Jump Lists & Taskbar Tabs
Windows 7 Taskbar – Part 1, The Basics
Windows 7 SDK Examples
CodeProject article on JumpLists
CodeProject article on JumpLists Tasks
Link to EcWin7, from here I understood how to import COM declarations from Windows SDK headers