Display a custom thumbnail for your application (and while you’re at it, a custom live preview)


By default, when the taskbar or any other application wants to display a thumbnail for a window, the result is a copy of the window contents shrunk down to the requested size. Today we're going to override that behavior and display a custom thumbnail.

Take the program from last week and make these changes:

#include <dwmapi.h>

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
  g_hicoAlert = LoadIcon(nullptr, IDI_EXCLAMATION);
  g_wmTaskbarButtonCreated = RegisterWindowMessage(
                              TEXT("TaskbarButtonCreated"));
  BOOL fTrue = TRUE;
  DwmSetWindowAttribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION,
                        &fTrue, sizeof(fTrue));
  DwmSetWindowAttribute(hwnd, DWMWA_HAS_ICONIC_BITMAP,
                        &fTrue, sizeof(fTrue));
  return TRUE;
}

We start by enabling custom thumbnails by setting the DWMWA_HAS_ICONIC_BITMAP attribute to TRUE. This overrides the default thumbnail generator and allows us to provide a custom one.

Next is a helper function that I broke out from this program because it's useful on its own. It simply creates a 32bpp bitmap of the desired size and optionally returns a pointer to the resulting bits.


HBITMAP Create32bppBitmap(HDC hdc, int cx, int cy,
                          RGBQUAD **pprgbBits = nullptr)
{
 BITMAPINFO bmi = { 0 };
 bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
 bmi.bmiHeader.biWidth = cx;
 bmi.bmiHeader.biHeight = cy;
 bmi.bmiHeader.biPlanes = 1;
 bmi.bmiHeader.biBitCount = 32;
 bmi.bmiHeader.biCompression = BI_RGB;
 void *pvBits;
 HBITMAP hbm = CreateDIBSection(hdc, &bmi,
           DIB_RGB_COLORS, &pvBits, NULL, 0);
 if (pprgbBits) *pprgbBits = static_cast<RGBQUAD*>(pvBits);
 return hbm;
}

Next, we take our Paint­Content function and make it render into a DC instead:

void
RenderContent(HDC hdc, LPCRECT prc)
{
 LOGFONTW lf = { 0 };
 lf.lfHeight = prc->bottom - prc->top;
 wcscpy_s(lf.lfFaceName, L"Verdana");
 HFONT hf = CreateFontIndirectW(&lf);
 HFONT hfPrev = SelectFont(hdc, hf);
 wchar_t wszCount[80];
 swprintf_s(wszCount, L"%d", g_iCounter);
 FillRect(hdc, prc, GetStockBrush(WHITE_BRUSH));
 DrawTextW(hdc, wszCount, -1, const_cast<LPRECT>(prc),
          DT_CENTER | DT_VCENTER | DT_SINGLELINE);
 SelectFont(hdc, hfPrev);
 DeleteObject(hf);
}

In our case, we will want to render into a bitmap:

HBITMAP
GenerateContentBitmap(HWND hwnd, int cx, int cy)
{
 HDC hdc = GetDC(hwnd);
 HDC hdcMem = CreateCompatibleDC(hdc);
 HBITMAP hbm = Create32bppBitmap(hdcMem, cx,cy);
 HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm);
 RECT rc = { 0, 0, cx, cy };
 RenderContent(hdcMem, &rc);
 SelectBitmap(hdcMem, hbmPrev);
 DeleteDC(hdcMem);
 ReleaseDC(hwnd, hdc);
 return hbm;
}

We can use this function when DWM asks us to generate a custom thumbnail or a custom live preview bitmap.

void
UpdateThumbnailBitmap(HWND hwnd, int cx, int cy)
{
 HBITMAP hbm = GenerateContentBitmap(hwnd, cx, cy);
 DwmSetIconicThumbnail(hwnd, hbm, 0);
 DeleteObject(hbm);
}

void
UpdateLivePreviewBitmap(HWND hwnd)
{
 RECT rc;
 GetClientRect(hwnd, &rc);
 HBITMAP hbm = GenerateContentBitmap(hwnd, rc.right - rc.left,
                                     rc.bottom - rc.top);
 DwmSetIconicLivePreviewBitmap(hwnd, hbm, nullptr, 0);
 DeleteObject(hbm);
}

 // WndProc
 case WM_DWMSENDICONICTHUMBNAIL:
  UpdateThumbnailBitmap(hwnd, HIWORD(lParam), LOWORD(lParam));
  break;
 case WM_DWMSENDICONICLIVEPREVIEWBITMAP:
  UpdateLivePreviewBitmap(hwnd);
  break;

One of the quirks of the WM_DWM­SEND­ICONIC­THUMB­NAIL message is that it passes the x- and y-coordinates backwards. Most window messages put the x-coordinate in the low word and the y-coordinate in the high word, but WM_DWM­SEND­ICONIC­THUMB­NAIL does it the other way around.

Since we're generating a custom thumbnail and live preview bitmap, we need to let the window manager know that the custom rendering is out of date and needs to be re-rendered: Invalidate the custom bitmaps when the counter changes.

void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
  switch (id) {
  case IDC_INCREMENT:
    ++g_iCounter;
    InvalidateRect(hwnd, nullptr, TRUE);
    DwmInvalidateIconicBitmaps(hwnd);
    break;
  }
}

And finally, just to be interesting, we'll also stop rendering content into our main window.

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
    // do nothing
}

Run this program and observe that the window comes up blank. Ah, but if you hover over the taskbar button, the custom thumbnail will appear, and that custom thumbnail has the number 0 in it. Click on the button in the thumbnail, and the number in the custom thumbnail increments.

As a bonus, move the mouse over the thumbnail to trigger Aero Peek. The live preview bitmap contains the magic number! Move the mouse away, and the magic number vanishes.

Now, this was an artificial example, so the effect is kind of weird. However, you can imagine using this in less artificial cases where the result is useful. You application might be a game, and instead of using the default thumbnail which shows a miniature copy of the game window, you can have your thumbnail be a tiny scoreboard or focus on a section of the board. For example, if you are a card game, the thumbnail might show just the cards in your hand.

I can't think of a useful case for showing a live preview bitmap different from the actual window. The intended use for a custom live preview bitmap is for applications like Web browsers which want to minimize a tab's memory usage when it is not active. When a tab becomes inactive, the browser can destroy all graphics resources except for a bitmap containing the last-known-valid contents of the window, and use that bitmap for the thumbnail and live preview.

Comments (12)
  1. JW says:

    One valid example that music players use (or at least mine) is that they show the album art of the currently playing music, often including some extra buttons to control the playback with.

    [That's a valid example of a custom thumbnail (I gave another example in the article). But the one I can't think of a valid case for is the custom Aero Peek. -Raymond]
  2. Lockwood says:

    "One of the quirks of the WM_DWM­SEND­ICONIC­THUMB­NAIL message is that it passes the x- and y-coordinates backwards. Most window messages put the x-coordinate in the low word and the y-coordinate in the high word, but WM_DWM­SEND­ICONIC­THUMB­NAIL does it the other way around"

    Why does this one behave differently? Is there an amusing backstory to this?

    [No. The people who added that message wasn't aware of the existing conventions. This happens every so often. -Raymond]
  3. laonianren says:

    A company of Microsoft's size should have an API conventions guru.  I imagine him in a basement office, wearing a brown warehouse coat and sharply inhaling before explaining why some proposed new Windows message is intolerable.

    That said, Microsoft probably doesn't need any more bureaucracy.  Ignore me.

  4. Matt says:

    @Raymond: "I can't think of a useful case for showing a live preview bitmap different from the actual window"

    How about the case where a program (e.g. Lync) shows the face of the person you're talking to, rather than a zoomed out illegible conversation in a window? Much more useful :)

    [You're thinking of the thumbnail. I'm talking about Aero Peek. Aero Peek is the same size as the original window. -Raymond]
  5. not important says:

    "The people who added that message wasn't aware of the existing conventions.- Raymond."

    O brother… So the person who implemented these window messages did not know basic Win32 stuff? How did you guys hired that person?

    [This is hardly "basic Win32 stuff". It's "obscure historical Win32 stuff." -Raymond]
  6. @laonianren says:

    I think you can considered yourself ignored buddy :)

  7. "Little Programs" Monday is awesome. But can you give examples of programs which you know already use these features so we can see them in action? Or that is against some policy/rule on the blog?

    [I didn't bother looking for examples. -Raymond]
  8. Brian G. says:

    @xpclient: Trillian (the IM client) uses the custom thumbnail to show the user icon of the person whose conversation is in the tab instead of a (usually useless) shrunk-down picture of the tab. That's not a custom Aero Peek though, just the taskbar thumbnail.

  9. alexcohn says:

    "I can't think of a valid case for is the custom Aero Peek"

    For an image editor, this could show the image sans the clutter of toolbox widgets

    [Okay, I could actually believe that. Well-done. -Raymond]
  10. jon says:

    "This is hardly "basic Win32 stuff". It's "obscure historical Win32 stuff." -Raymond"

    So fundamental messages like WM_MOUSEMOVE and WM_LBUTTONDOWN are obscure and historical?

    What other obscure and historical things are Microsoft developers unaware of these days?

    [News flash: Everybody nowadays uses message crackers so they don't need to memorize how the parameters are packed. -Raymond]
  11. jon says:

    Which message crackers should we use with WM_DWM­SEND­ICONIC­THUMB­NAIL then?

    [It doesn't have one, but you knew that. Look, I already said it was a mistake. What do you want, a time machine to go back and fix the mistake? -Raymond]
  12. jon says:

    That's the first time you said it was a mistake, and that's actually all I wanted, instead of the excuses and blind justifications you normally provide for Microsoft ***-ups :)

    [I was trying to be polite to the people who violated the convention by calling it a "quirk". There's nothing technically wrong with passing the parameters in an unconventional order, so "mistake" is perhaps too strong a term. It is however a violation of convention. (Who's the one with the social skills of a thermonuclear device?) -Raymond]

Comments are closed.