{"id":6813,"date":"2012-08-20T07:00:00","date_gmt":"2012-08-20T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/08\/20\/how-do-i-customize-how-my-application-windows-are-grouped-in-the-taskbar\/"},"modified":"2012-08-20T07:00:00","modified_gmt":"2012-08-20T07:00:00","slug":"how-do-i-customize-how-my-application-windows-are-grouped-in-the-taskbar","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120820-00\/?p=6813","title":{"rendered":"How do I customize how my application windows are grouped in the Taskbar?"},"content":{"rendered":"<p>\nBenjamin Smedberg wants to know\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2010\/07\/20\/10040074.aspx#10040417\">\nhow to customize the icon used in the Taskbar<\/a>\nfor applications that are grouped,\nwhen the application is a runtime for multiple applications.\n(This is the other scenario I hinted at\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2012\/08\/17\/10340743.aspx\">\nlast time<\/a>.)\n<\/p>\n<p>\nActually, customizing the icon is only part of what you want to happen\nwhen your application is a runtime.\nIn that case, you really want each inner application to be exposed\nto the user as an entirely separate application.\nIn other words,\nif your application is hosting Product&nbsp;A and Product&nbsp;B,\nyou want the windows for Product&nbsp;A and\nProduct&nbsp;B to group separately,\nhave separate icons,\nmaintain separate jump lists,\nall that stuff.\nBecause from the user&#8217;s point of view, they are separate programs.\nIt just happens that under the covers, they&#8217;re all being driven\nby a single EXE.\n<\/p>\n<p>\nIn Windows, the concept of an application is captured in\nsomething called an <i>Application User Model ID<\/i>,\nor <i>AppID<\/i> for short.\nFor backward compatibility, if your application does not provide\nan explicit AppID,\nthe shell will autogenerate one based on your EXE name.\nTherefore,\nthe starting point for AppIDs is that an AppID maps to an EXE.\nBut once you start customizing your AppID, you can play with\nthis default correspondence.\n<\/p>\n<p>\nAll the information in this article came from the article\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378459(v=VS.85).aspx\">\nApplication User Model IDs (AppUserModelIDs)<\/a> in MSDN.\n<\/p>\n<p>\nOkay, so suppose your application is really a runtime for\nother applications.\nWhat you need to do is assign a different AppID to each of\nthe applications you are hosting.\nThe mechanism for this is up to you.\nYour applications might explicitly provide a unique ID,\nor you may be able to infer one.\nFor example, if you are Internet Explorer and your &#8220;applications&#8221;\nare\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/gg131029(v=VS.85).aspx\">\npinned Web sites<\/a>,\nyou can use the URL of the site being pinned as the unique ID.\n<\/p>\n<p>\nYou then get to take your unique IDs and create AppIDs for them.\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378459(v=VS.85).aspx#how\">\nThe format of an AppID<\/a> is\n<\/p>\n<pre>\nCompanyName.ProductName.SubProduct.VersionInformation\n<\/pre>\n<p>\nwhere the Sub&shy;Product is optional,\nand the Version&shy;Information is present only if you want\ndifferent versions of your app to be treated as distinct.\n(If you want an upgraded version to be a replacement for the old\nversion, then omit the Version&shy;Information so that the old and\nnew versions use the same AppID.)\n<\/p>\n<p>\nNote that you have to be careful how you auto-generate your AppIDs,\nsince the resulting AppID needs to be legal.\nFor example, you cannot just take a URL and use it as the Sub&shy;Product\nof an AppID.\nURLs contain embedded periods, which violates the overall format,\nand they can be longer than 128 characters and can contain spaces,\nboth of which are also called out in the documentation as prohibited.\nInternet Explorer addresses this problem by using a hash of the URL as\nits Sub&shy;Product rather than the full URL.\n<\/p>\n<p>\nYou then assign this AppID to every window associated with\nthe &#8220;application&#8221;.\nYou can do this for an entire process by\ncalling\n<code>SetCurrentProcessExplicitAppUserModelID<\/code>,\nor you can do it on a\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/06\/01\/10170113.aspx\">\nwindow-by-window basis<\/a>\nby setting the\n<code>PKEY_AppUserModel_ID<\/code> property.\n<\/p>\n<p>\nOkay, let&#8217;s write a program that shows how a runtime for other applications\ncan use AppIDs to control its treatment in the taskbar.\nOf course,\nour sample won&#8217;t actually be a runtime for anything;\nthe &#8220;applications&#8221; that it hosts will simply be icons.\n<\/p>\n<p>\nStart with the\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nscratch program<\/a>\nand make these changes:\n<\/p>\n<pre>\n<font COLOR=\"blue\">\n#include &lt;shellapi.h&gt;\n#include &lt;shlobj.h&gt;\n#include &lt;strsafe.h&gt;\n#define HOSTAPPID L\"Contoso.Host\"\nvoid SetProcessAppId(LPCWSTR pszTarget)\n{\n  if (pszTarget[0]) {\n    WCHAR szAppId[256];\n    DWORD dwHash = 0;\n    HashData((BYTE*)pszTarget, wcslen(pszTarget) * sizeof(WCHAR),\n             (BYTE*)&amp;dwHash, sizeof(dwHash));\n    StringCchPrintfW(szAppId, ARRAYSIZE(szAppId),\n                     L\"%s.hosted-%08x\", HOSTAPPID, dwHash);\n    SetCurrentProcessExplicitAppUserModelID(szAppId);\n  } else {\n    StringCchPrintfW(szAppId, ARRAYSIZE(szAppId),\n                     L\"%s.main\", HOSTAPPID);\n  }\n}<\/font>\nint WINAPI <font COLOR=\"blue\">wWinMain<\/font>(HINSTANCE hinst, HINSTANCE hinstPrev,\n                   <font COLOR=\"blue\">LPWSTR<\/font> lpCmdLine, int nShowCmd)\n{\n  <font COLOR=\"blue\">SetProcessAppId(lpCmdLine);<\/font>\n    ...\n    ShowWindow(hwnd, SW_NORMAL);\n    <font COLOR=\"blue\">SetWindowText(hwnd, lpCmdLine);\n    if (lpCmdLine[0]) {\n      WCHAR szIcon[256];\n      StringCchCopyW(szIcon, ARRAYSIZE(szIcon), ptszCmdLine);\n      int iIcon = PathParseIconLocation(szIcon);\n      <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/05\/26\/422076.aspx\">if (iIcon == -1) iIcon = 0;<\/a>\n      HICON hico = ExtractIcon(hinst, szIcon, iIcon);\n      SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hico);\n    }<\/font>\n    ...\n}\n<\/pre>\n<p>\nOur simple host program just hosts an icon.\nThe path to the icon is passed on the command line in the form\n&#8220;path,id&#8221;,\nand for good measure, we put the icon path in the caption so you can\nsee how it groups.\n<\/p>\n<p>\nThe real work happens in the\n<code>SetProcessAppId<\/code> function.\nIf there is no command line, then we are running in standalone mode\nand set our Sub&shy;Product to <code>main<\/code>.\nIf we have a command line, then we hash it and use the hash to build\nour Sub&shy;Product.\nI&#8217;m just using a four-byte hash with a simple has function;\ndepending on how paranoid you are, you could use some other hash\nfunction, but make sure you can get the resulting AppID to fit\ninto 128 characters.\n(This means that hex-encoded SHA512 is too big.)\n<\/p>\n<p>\nOnce we figure out what our AppID is, we set it for the entire\nprocess by calling\n<code>SetCurrentProcessExplicitAppUserModelID<\/code>.\n<\/p>\n<p>\nOkay, let&#8217;s take this program out for a spin.\nYou can run it with the command lines\n<\/p>\n<pre>\nscratch %windir%\\explorer.exe,0\nscratch %windir%\\explorer.exe,0\nscratch %windir%\\explorer.exe,1\nscratch %windir%\\explorer.exe,1\n<\/pre>\n<p>\nto see four copies of the program,\ntwo with one icon and two with another.\nObserve that when they group in the taskbar,\nthe icon for the group is preserved,\nand that the two sets of programs group separately.\n<\/p>\n<p>\nNote also that if you create shortcuts to your host program\nwith a command line,\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378459(v=VS.85).aspx#where\">\nyou need to set the AppID in your shortcut, too<\/a>.\n(Otherwise the shell won&#8217;t know what the AppID of the\nresulting program will be, since you are setting it at runtime.)\n<\/p>\n<p>\nNote also that we did not need to\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd378459(v=VS.85).aspx#host\">\nregister the application as a host process<\/a>\nbecause we explicitly set an AppID in our application\nand in our shortcuts.\n(Or at least, we said that we would.\nI didn&#8217;t actually do it.)\n<\/p>\n<p>\n<b>Bonus reading<\/b>:\n<a HREF=\"http:\/\/windowsteamblog.com\/windows\/b\/developers\/archive\/2009\/06\/18\/developing-for-the-windows-7-taskbar-application-id.aspx\">\nDeveloping for the Windows 7 Taskbar &mdash; Application ID<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Benjamin Smedberg wants to know how to customize the icon used in the Taskbar for applications that are grouped, when the application is a runtime for multiple applications. (This is the other scenario I hinted at last time.) Actually, customizing the icon is only part of what you want to happen when your application is [&hellip;]<\/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-6813","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Benjamin Smedberg wants to know how to customize the icon used in the Taskbar for applications that are grouped, when the application is a runtime for multiple applications. (This is the other scenario I hinted at last time.) Actually, customizing the icon is only part of what you want to happen when your application is [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6813","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=6813"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6813\/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=6813"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=6813"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=6813"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}