{"id":4933,"date":"2013-03-18T07:00:00","date_gmt":"2013-03-18T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/03\/18\/manipulating-the-positions-of-desktop-icons\/"},"modified":"2020-12-24T11:23:44","modified_gmt":"2020-12-24T19:23:44","slug":"20130318-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130318-00\/?p=4933","title":{"rendered":"Manipulating the positions of desktop icons"},"content":{"rendered":"<p>Today&#8217;s little program demonstrates how you can manipulate the positions of desktop icons.<\/p>\n<p>The entire program is just scaffolding to get us far enough that we can call <a href=\"http:\/\/msdn.microsoft.com\/library\/bb775600.aspx\"> <code>IFolder\u00adView::<wbr \/>Get\u00adItem\u00adPosition<\/code><\/a> and <a href=\"http:\/\/msdn.microsoft.com\/library\/bb775614.aspx\"> <code>IFolder\u00adView::<wbr \/>Select\u00adAnd\u00adPosition\u00adItems<\/code><\/a>.<\/p>\n<p>First, we adapt the code <a title=\"Querying information from an Explorer window\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040720-00\/?p=38393\"> we saw some time ago<\/a> that extracts the <code>IFolder\u00adView<\/code> from a window.<\/p>\n<p><b>Reminder<\/b>: These &#8220;Little Programs&#8221; do no error checking because they are intended as demonstrations, not production-ready applications.<\/p>\n<pre>void FindDesktopFolderView(REFIID riid, void **ppv)\r\n{\r\n CComPtr&lt;IShellWindows&gt; spShellWindows;\r\n spShellWindows.CoCreateInstance(CLSID_ShellWindows);\r\n\r\n CComVariant vtLoc(CSIDL_DESKTOP);\r\n CComVariant vtEmpty;\r\n long lhwnd;\r\n CComPtr&lt;IDispatch&gt; spdisp;\r\n spShellWindows-&gt;FindWindowSW(\r\n     &amp;vtLoc, &amp;vtEmpty,\r\n     SWC_DESKTOP, &amp;lhwnd, SWFO_NEEDDISPATCH, &amp;spdisp);\r\n\r\n CComPtr&lt;IShellBrowser&gt; spBrowser;\r\n CComQIPtr&lt;IServiceProvider&gt;(spdisp)-&gt;\r\n     QueryService(SID_STopLevelBrowser,\r\n                  IID_PPV_ARGS(&amp;spBrowser));\r\n\r\n CComPtr&lt;IShellView&gt; spView;\r\n spBrowser-&gt;QueryActiveShellView(&amp;spView);\r\n\r\n spView-&gt;QueryInterface(riid, ppv);\r\n}\r\n<\/pre>\n<p>The <code>Find\u00adDesktop\u00adFolder\u00adView<\/code> function takes the code from that earlier article and uses it to extract the shell view for the desktop. Everything here should look familiar (just in a different costume), aside from the call to <code>Find\u00adWindow\u00adSW<\/code>, because we are looking for a specific window by location rather than just enumerating through all of them.<\/p>\n<p>The first parameter to <code>Find\u00adWindow\u00adSW<\/code>. is the folder we are looking for. In our case, we are looking for the desktop.<\/p>\n<p>The second parameter is reserved and must be <code>VT_EMPTY<\/code>.<\/p>\n<p>The third parameter describes <a href=\"http:\/\/msdn.microsoft.com\/library\/cc836581.aspx\"> the types of windows we are looking for<\/a>. We use the special <code>SWC_<wbr \/>DESKTOP<\/code> flag (available starting in Windows Vista) to say, &#8220;Hey, I know the desktop isn&#8217;t the sort of thing people think of when they go looking for Explorer windows, but I know what I&#8217;m talking about, so let me have it.&#8221;<\/p>\n<p>The fourth parameter receives the window handle, which is of no interest to us, but the parameter is mandatory, so we have to give it something.<\/p>\n<p>The fifth parameter specifies the <a href=\"http:\/\/msdn.microsoft.com\/librarycc836580.aspx\"> search options<\/a>. We use <code>SWFO_<wbr \/>NEED\u00adDISPATCH<\/code> to say, &#8220;Please return the <code>IDispatch<\/code> in the sixth parameter.&#8221; And the sixth parameter is where we want the <code>IDispatch<\/code> to be returned.<\/p>\n<p>Okay, we already have enough to be able to enumerate all the desktop icons and print their names and locations.<\/p>\n<pre>#define UNICODE\r\n#define <a title=\"TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040212-00\/?p=40643\">_UNICODE<\/a>\r\n#include &lt;windows.h&gt;\r\n#include &lt;shlobj.h&gt;\r\n#include &lt;exdisp.h&gt;\r\n#include &lt;shlwapi.h&gt;\r\n#include &lt;atlbase.h&gt;\r\n#include &lt;atlalloc.h&gt;\r\n#include &lt;stdio.h&gt;\r\n\r\n\/\/ <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040520-00\/?p=39243\">CCoInitialize<\/a> incorporated by reference\r\n\r\nint __cdecl wmain(int argc, wchar_t **argv)\r\n{\r\n CCoInitialize init;\r\n CComPtr&lt;IFolderView&gt; spView;\r\n FindDesktopFolderView(IID_PPV_ARGS(&amp;spView));\r\n CComPtr&lt;IShellFolder&gt; spFolder;\r\n spView-&gt;GetFolder(IID_PPV_ARGS(&amp;spFolder));\r\n\r\n CComPtr&lt;IEnumIDList&gt; spEnum;\r\n spView-&gt;Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&amp;spEnum));\r\n for (CComHeapPtr&lt;ITEMID_CHILD&gt; spidl;\r\n      spEnum-&gt;Next(1, &amp;spidl, nullptr) == S_OK;\r\n      spidl.Free()) {\r\n  <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040823-00\/?p=38073\">STRRET<\/a> str;\r\n  spFolder-&gt;GetDisplayNameOf(spidl, SHGDN_NORMAL, &amp;str);\r\n  CComHeapPtr&lt;wchar_t&gt; spszName;\r\n  <a href=\"http:\/\/msdn.microsoft.com\/library\/bb773427.aspx\">StrRetToStr<\/a>(&amp;str, spidl, &amp;spszName);\r\n\r\n  POINT pt;\r\n  spView-&gt;GetItemPosition(spidl, &amp;pt);\r\n  \r\n  wprintf(L\"At %4d,%4d is %ls\\n\", pt.x, pt.y, spszName);\r\n }\r\n return 0;\r\n}\r\n<\/pre>\n<p>After getting the <code>IFolder\u00adView<\/code>, we also ask for the corresponding <code>IShell\u00adFolder<\/code>. This isn&#8217;t actually necessary for enumerating the icons, but it lets us print their names.<\/p>\n<p>We ask the view for its <code>Items<\/code> enumeration, then proceed to enumerate each of the items. For each item, we ask the <code>IShell\u00adFolder<\/code> for its name, and we ask the <code>IFolder\u00adView<\/code> for its position. Then we print the results.<\/p>\n<p>Okay, that was neat, but you can do more than just query the positions. You can also modify them.<\/p>\n<pre>int __cdecl wmain(int argc, wchar_t **argv)\r\n{\r\n CCoInitialize init;\r\n CComPtr&lt;IFolderView&gt; spView;\r\n FindDesktopFolderView(IID_PPV_ARGS(&amp;spView));\r\n\r\n CComPtr&lt;IEnumIDList&gt; spEnum;\r\n spView-&gt;Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&amp;spEnum));\r\n for (CComHeapPtr&lt;ITEMID_CHILD&gt; spidl;\r\n      spEnum-&gt;Next(1, &amp;spidl, nullptr) == S_OK;\r\n      spidl.Free()) {\r\n  POINT pt;\r\n  spView-&gt;GetItemPosition(spidl, &amp;pt);\r\n  pt.x += (rand() % 5) - 2;\r\n  pt.y += (rand() % 5) - 2;\r\n\r\n PCITEMID_CHILD apidl[1] = { spidl };\r\n spView-&gt;SelectAndPositionItems(\r\n     1, apidl, &amp;pt, SVSI_POSITIONITEM);\r\n }\r\n return 0;\r\n}\r\n<\/pre>\n<p>This time, instead of printing the item&#8217;s name and position, we jiggle the icon position by a few pixels randomly, then set the jiggled coordinates as the new position.<\/p>\n<p>Turn off <i>Auto arrange icons<\/i> and <i>Align icons to grid<\/i> on the desktop, and then run this program. Hey, look, your icons shifted randomly by a few pixels.<\/p>\n<p>For extra hijinx, drop a call to <code>spView-&gt;<wbr \/>Set\u00adCurrent\u00adFolder\u00adFlags(<wbr \/>FWF_<wbr \/>AUTO\u00adARRANGE | FWF_<wbr \/>SNAP\u00adTO\u00adGRID, 0)<\/code> before you enter the loop (to programmatically turn off auto-arrange and snap-to-grid), then put this program in a loop, and slip it onto a friend&#8217;s (or enemy&#8217;s) computer.<\/p>\n<p>More seriously, we can we put the two pieces together to make a program that saves and restores desktop icon positions.<\/p>\n<p><b>Second reminder<\/b>: These &#8220;Little Programs&#8221; do no error checking because they are intended as demonstrations, not production-ready applications.<\/p>\n<pre>void SavePositions(IFolderView *pView, PCWSTR pszFile)\r\n{\r\n CComPtr&lt;IStream&gt; spStream;\r\n SHCreateStreamOnFileEx(pszFile, STGM_CREATE | STGM_WRITE,\r\n     FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, &amp;spStream);\r\n CComPtr&lt;IEnumIDList&gt; spEnum;\r\n pView-&gt;Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&amp;spEnum));\r\n for (CComHeapPtr&lt;ITEMID_CHILD&gt; spidl;\r\n      spEnum-&gt;Next(1, &amp;spidl, nullptr) == S_OK;\r\n      spidl.Free()) {\r\n  IStream_WritePidl(spStream, spidl);\r\n  POINT pt;\r\n  pView-&gt;GetItemPosition(spidl, &amp;pt);\r\n  IStream_Write(spStream, &amp;pt, sizeof(pt));\r\n }\r\n}\r\n<\/pre>\n<p>The <code>Save\u00adPositions<\/code> function enumerates all the icons in a view and writes their identities and positions to a file.<\/p>\n<pre>void RestorePositions(IFolderView *pView, PCWSTR pszFile)\r\n{\r\n CComPtr&lt;IStream&gt; spStream;\r\n SHCreateStreamOnFileEx(pszFile, STGM_READ,\r\n     FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &amp;spStream);\r\n POINT pt;\r\n for (CComHeapPtr&lt;ITEMID_CHILD&gt; spidl;\r\n      SUCCEEDED(IStream_ReadPidl(spStream, &amp;spidl)) &amp;&amp;\r\n      SUCCEEDED(IStream_Read(spStream, &amp;pt, sizeof(pt)));\r\n      spidl.Free()) {\r\n  PCITEMID_CHILD apidl[1] = { spidl };\r\n  pView-&gt;SelectAndPositionItems(1, apidl, &amp;pt, SVSI_POSITIONITEM);\r\n }\r\n}\r\n<\/pre>\n<p>The <code>Restore\u00adPositions<\/code> function does the reverse. It reads the identities and positions from the file and calls <code>IFolder\u00adView::<wbr \/>Select\u00adAnd\u00adPosition\u00adItems<\/code> to move the item to its previously-saved position.<\/p>\n<pre>int __cdecl wmain(int argc, wchar_t **argv)\r\n{\r\n if (argc != 3) {\r\n  wprintf(L\"Usage: %ls save filename\\n\"\r\n          L\"       %ls restore filename\\n\", argv[0], argv[0]);\r\n  return 0;\r\n }\r\n CCoInitialize init;\r\n\r\n CComPtr&lt;IFolderView&gt; spView;\r\n FindDesktopFolderView(IID_PPV_ARGS(&amp;spView));\r\n\r\n if (wcscmp(argv[1], L\"save\") == 0) {\r\n  SavePositions(spView, argv[2]);\r\n } else if (wcscmp(argv[1], L\"restore\") == 0) {\r\n  RestorePositions(spView, argv[2]);\r\n }\r\n return 0;\r\n}\r\n<\/pre>\n<p>And all that&#8217;s left is to write the main program that calls either the <code>Save\u00adPositions<\/code> or <code>Restore\u00adPositions<\/code> function based on the command line parameters.<\/p>\n<p><b>Exercise<\/b>: Discuss what happens if you rename an item on the desktop, and then try to restore its position. What could be done to address this?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just putting together the pieces you already have.<\/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-4933","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just putting together the pieces you already have.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4933","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=4933"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4933\/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=4933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=4933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=4933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}