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.

No comments: