{"id":5153,"date":"2013-02-25T07:00:00","date_gmt":"2013-02-25T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/02\/25\/display-a-custom-thumbnail-for-your-application-and-while-youre-at-it-a-custom-live-preview\/"},"modified":"2023-12-01T10:40:38","modified_gmt":"2023-12-01T18:40:38","slug":"20130225-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130225-00\/?p=5153","title":{"rendered":"Display a custom thumbnail for your application (and while you&#8217;re at it, a custom live preview)"},"content":{"rendered":"<p>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&#8217;re going to override that behavior and display a custom thumbnail.<\/p>\n<p>Take <a title=\"Display control buttons on your taskbar preview window\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130218-00\/?p=5223\"> the program from last week<\/a> and make these changes:<\/p>\n<pre><span style=\"border: solid 1px currentcolor;\">#include &lt;dwmapi.h&gt;<\/span>\r\n\r\nBOOL\r\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\r\n{\r\n  g_hicoAlert = LoadIcon(nullptr, IDI_EXCLAMATION);\r\n  g_wmTaskbarButtonCreated = RegisterWindowMessage(\r\n                              TEXT(\"TaskbarButtonCreated\"));\r\n  <span style=\"border: solid 1px currentcolor; border-bottom: none;\">BOOL fTrue = TRUE;                                            <\/span>\r\n  <span style=\"border: 1px currentcolor; border-style: none solid;\">DwmSetWindowAttribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION,<\/span>\r\n  <span style=\"border: 1px currentcolor; border-style: none solid;\">                      &amp;fTrue, sizeof(fTrue));                 <\/span>\r\n  <span style=\"border: 1px currentcolor; border-style: none solid;\">DwmSetWindowAttribute(hwnd, DWMWA_HAS_ICONIC_BITMAP,          <\/span>\r\n  <span style=\"border: solid 1px currentcolor; border-top: none;\">                      &amp;fTrue, sizeof(fTrue));                 <\/span>\r\n  return TRUE;\r\n}\r\n<\/pre>\n<p>We start by enabling custom thumbnails by setting the <code>DWMWA_<wbr \/>HAS_<wbr \/>ICONIC_<wbr \/>BITMAP<\/code> attribute to <code>TRUE<\/code>. This overrides the default thumbnail generator and allows us to provide a custom one.<\/p>\n<p>Next is a helper function that I broke out from <a title=\"BeginBufferedPaint: It's not just for buffered painting any more\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110520-00\/?p=10613\"> this program<\/a> because it&#8217;s useful on its own. It simply creates a 32bpp bitmap of the desired size and optionally returns a pointer to the resulting bits.<\/p>\n<pre><span style=\"border: solid 1px currentcolor; border-bottom: none;\">HBITMAP Create32bppBitmap(HDC hdc, int cx, int cy,         <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">                          RGBQUAD **pprgbBits = nullptr)   <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> BITMAPINFO bmi = { 0 };                                   <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biWidth = cx;                               <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biHeight = cy;                              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biPlanes = 1;                               <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biBitCount = 32;                            <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> bmi.bmiHeader.biCompression = BI_RGB;                     <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> void *pvBits;                                             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HBITMAP hbm = CreateDIBSection(hdc, &amp;bmi,                 <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">           DIB_RGB_COLORS, &amp;pvBits, NULL, 0);              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> if (pprgbBits) *pprgbBits = static_cast&lt;RGBQUAD*&gt;(pvBits);<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> return hbm;                                               <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                          <\/span>\r\n<\/pre>\n<p>Next, we take our <code>Paint\u00adContent<\/code> function and make it render into a DC instead:<\/p>\n<pre>void\r\n<span style=\"border: solid 1px currentcolor;\">RenderContent(HDC hdc, LPCRECT prc)<\/span>\r\n{\r\n LOGFONTW lf = { 0 };\r\n lf.lfHeight = <span style=\"border: solid 1px currentcolor;\">prc-&gt;<\/span>bottom - <span style=\"border: solid 1px currentcolor;\">prc-&gt;<\/span>top;\r\n wcscpy_s(lf.lfFaceName, L\"Verdana\");\r\n HFONT hf = CreateFontIndirectW(&amp;lf);\r\n HFONT hfPrev = SelectFont(<span style=\"border: solid 1px currentcolor;\">hdc<\/span>, hf);\r\n wchar_t wszCount[80];\r\n swprintf_s(wszCount, L\"%d\", g_iCounter);\r\n <span style=\"border: solid 1px currentcolor;\">FillRect(hdc, prc, GetStockBrush(WHITE_BRUSH));<\/span>\r\n DrawTextW(<span style=\"border: solid 1px currentcolor;\">hdc<\/span>, wszCount, -1, const_cast&lt;LPRECT&gt;(prc),\r\n          DT_CENTER | DT_VCENTER | DT_SINGLELINE);\r\n SelectFont(<span style=\"border: solid 1px currentcolor;\">hdc<\/span>, hfPrev);\r\n DeleteObject(hf);\r\n}\r\n<\/pre>\n<p>In our case, we will want to render into a bitmap:<\/p>\n<pre><span style=\"border: solid 1px currentcolor; border-bottom: none;\">HBITMAP                                         <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">GenerateContentBitmap(HWND hwnd, int cx, int cy)<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                               <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HDC hdc = GetDC(hwnd);                         <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HDC hdcMem = CreateCompatibleDC(hdc);          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HBITMAP hbm = Create32bppBitmap(hdcMem, cx,cy);<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm);   <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> RECT rc = { 0, 0, cx, cy };                    <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> RenderContent(hdcMem, &amp;rc);                    <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> SelectBitmap(hdcMem, hbmPrev);                 <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> DeleteDC(hdcMem);                              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> ReleaseDC(hwnd, hdc);                          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> return hbm;                                    <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">}                                               <\/span>\r\n<\/pre>\n<p>We can use this function when DWM asks us to generate a custom thumbnail or a custom live preview bitmap.<\/p>\n<pre>void\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">UpdateThumbnailBitmap(HWND hwnd, int cx, int cy)              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HBITMAP hbm = GenerateContentBitmap(hwnd, cx, cy);           <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> DwmSetIconicThumbnail(hwnd, hbm, 0);                         <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> DeleteObject(hbm);                                           <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">}                                                             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">\u00a0                                                             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">void                                                          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">UpdateLivePreviewBitmap(HWND hwnd)                            <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                             <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> RECT rc;                                                     <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> GetClientRect(hwnd, &amp;rc);                                    <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> HBITMAP hbm = GenerateContentBitmap(hwnd, rc.right - rc.left,<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">                                     rc.bottom - rc.top);     <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> DwmSetIconicLivePreviewBitmap(hwnd, hbm, nullptr, 0);        <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\"> DeleteObject(hbm);                                           <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                             <\/span>\r\n\r\n \/\/ WndProc\r\n <span style=\"border: solid 1px currentcolor; border-bottom: none;\">case WM_DWMSENDICONICTHUMBNAIL:                              <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\"> UpdateThumbnailBitmap(hwnd, HIWORD(lParam), LOWORD(lParam));<\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\"> break;                                                      <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\">case WM_DWMSENDICONICLIVEPREVIEWBITMAP:                      <\/span>\r\n <span style=\"border: 1px currentcolor; border-style: none solid;\"> UpdateLivePreviewBitmap(hwnd);                              <\/span>\r\n <span style=\"border: solid 1px currentcolor; border-top: none;\"> break;                                                      <\/span>\r\n<\/pre>\n<p>One of the quirks of the <code>WM_<wbr \/>DWM\u00adSEND\u00adICONIC\u00adTHUMB\u00adNAIL<\/code> 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 <code>WM_<wbr \/>DWM\u00adSEND\u00adICONIC\u00adTHUMB\u00adNAIL<\/code> does it the other way around.<\/p>\n<p>Since we&#8217;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.<\/p>\n<pre>void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)\r\n{\r\n  switch (id) {\r\n  case IDC_INCREMENT:\r\n    ++g_iCounter;\r\n    InvalidateRect(hwnd, nullptr, TRUE);\r\n    <span style=\"border: solid 1px currentcolor;\">DwmInvalidateIconicBitmaps(hwnd);<\/span>\r\n    break;\r\n  }\r\n}\r\n<\/pre>\n<p>And finally, just to be interesting, we&#8217;ll also stop rendering content into our main window.<\/p>\n<pre>void\r\nPaintContent(HWND hwnd, PAINTSTRUCT *pps)\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">\/\/ do nothing<\/span>\r\n}\r\n<\/pre>\n<p>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.<\/p>\n<p>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.<\/p>\n<p>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.<\/p>\n<p>I can&#8217;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&#8217;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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mini-me.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-5153","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Mini-me.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/5153","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=5153"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/5153\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=5153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=5153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=5153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}