保存和恢复桌面图标位置






4.93/5 (20投票s)
本文介绍如何在 Windows 桌面保存和恢复图标的位置。
引言
最近,我的公司给我配了一台笔记本扩展坞。这东西玩起来挺有趣的,但很快我就遇到了一个小麻烦。我的笔记本时不时(实际上相当频繁)重启后,会忘记屏幕分辨率。这倒不是什么大问题,因为恢复屏幕分辨率又快又容易。但问题是,随着分辨率一起,它还会忘记我桌面上所有图标的位置。重新摆放它们很简单,但由于图标太多,所以并不算快。
当然,市面上也有像 IconRestorer 这样的程序可以提供帮助。但归根结底,我们是程序员,这是一个有趣的挑战。所以,让我们开始这段旅程吧!
注册表
我在网上搜索 Windows 在哪里存储桌面图标位置信息。搜索结果很简单,都指向注册表项"CurrentUser\Software\Microsoft\Windows\Shell\Bags\1\Desktop"。我知道如何操作注册表,这本来是个简单的任务。但很快我就意识到,这并不是正确的答案。看起来 Windows 在登录时读取这些信息,并在注销时存储它们。而我希望在不重启电脑的情况下恢复图标位置。因此,我需要另一个解决方案。
PInvoke
我又进行了搜索,发现实际上桌面是一个某个进程中的 ListView
控件。任何人都可以通过标准的 Windows SendMessage
API 向这个控件发送消息,并获得一些结果。但这意味我要使用 PInvoke 技术来操作旧的 Windows API。我个人对 PInvoke 感到不安。首先,你需要知道每个所需函数存在于哪个 DLL 中。然后,还会出现各种各样的封送(marshalling)问题。我对此感到害怕。但幸运的是,我找到了一个很棒的网站:PInvoke.net。它非常容易使用,并且为许多原生 Windows 函数和结构提供了 .NET 包装器,包括一些有用的重载。我肯定以后会继续使用这个网站,并希望它也能帮到你。
桌面的进程
要能够向控件发送消息,就需要获得它的句柄——控件在 Windows 中的唯一标识符。要做到这一点,首先需要确定包含该控件的进程。这时,我遇到了第一个问题。你看,Windows 可以将桌面控件托管在不同的进程中。如果你有一个静态桌面图像,你有一个进程。如果你有多个图像,并且 Windows 会不时更换背景图像,那么你就有了另一个进程。我花了一些时间才找到一个通用的算法来获取桌面控件的句柄,在此处。
设置图标位置
如前所述,图标只是显示在 ListView
控件中的项。获取这些项的总数很容易
var numberOfIcons =
(int)Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero);
将图标的位置设置到某个点 (x,y)
也同样容易
public static IntPtr MakeLParam(int wLow, int wHigh)
{
return (IntPtr)(((short)wHigh << 16) | (wLow & 0xffff));
}
...
Win32.SendMessage(_desktopHandle, Win32.LVM_SETITEMPOSITION, iconIndex, MakeLParam(x, y));
其中 iconIndex
是图标的索引,介于 0
到 numberOfIcons - 1
之间。
所以接下来要做的是获取图标的位置。而另一个问题就在前面等着我。
获取图标位置
这看起来似乎很容易获取图标的位置。你只需调用类似这样的代码
Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMPOSITION, iconIndex, pointerToResult);
而变量 pointerToResult
将包含一个指向简单结构的指针,该结构描述了索引为 iconIndex
的图标的位置。但这样做并不奏效。问题在于,我们发送消息的控件位于另一个进程中。因此,pointerToResult
变量指向的是另一个进程的内存。由于内存保护,我们无法访问它。所以算法变得更加复杂。我从此处获取了该算法。简而言之,你必须先分配共享内存,然后发送命令将某些信息写入该共享内存,最后再从中读取所需信息。
现在,我同时具备了获取和设置图标位置的能力。我写了一个简单的程序来保存和恢复这些位置。但事实证明,事情并未就此结束。
获取图标文本
很快,在我开始使用我的程序后,我意识到它可能会交换图标。我的 Microsoft Word 图标被恢复到了回收站的位置,等等。这意味着重启后 Microsoft Word 图标的索引发生了变化,我不能只依赖索引。我开始思考可以用什么作为常量来区分图标。显而易见答案是:“它们的文本”。但这个简单的任务却成了一场噩梦。网上有很多关于如何使用相同的 SendMessage
机制获取桌面图标文本的例子。但在我的笔记本上,没有一个奏效。最终,我偶然发现了这段代码,它使用 UIAutomationClient
程序集解决了这个问题。它让我能够获取我的图标文本,并将它们与图标索引关联起来。我存储了文本和位置,并通过具有相同文本的图标在当前计算机加载时的索引来恢复位置。
最后一块拼图
现在,我已经能够享受我的程序了。它成功地保存和恢复了图标的位置。但很快,我注意到了另一个问题。在我笔记本加载后,我恢复了图标的位置。它们摆放得很好。但当我启动几个应用程序后,所有图标突然又混乱了。我开始调查这个问题,发现我需要刷新桌面才能保持图标位置。幸运的是,这很容易做到
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
...
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
但很快就发现,这段代码有一个副作用。它还会频繁地排列图标。这与桌面上的“**刷新**”命令的作用并不完全一样。我开始搜索如何进行真正的刷新。但在我的笔记本上,我没有找到任何有效的解决方案。过了一会儿,我突然意识到 Windows 中有一个快捷键“F5”可以刷新桌面,我只需要将这个按键发送给桌面。代码相当简单
PostMessage(_desktopHandle, WM_KEYDOWN, VK_F5, 0);
其中 PostMessage
与 SendMessage
类似。现在一切似乎都正常工作了。
关注点
我仍然对是否能使用 SendMessage
来获取图标文本感到好奇。
Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMTEXT, iconIndex, ...);
肯定有办法……
附注
我知道文章中的代码量不多。毕竟,我并没有编写多少代码。大部分代码都来自不同的来源。我只是想展示在解决保存和恢复桌面图标位置这个任务过程中遇到的陷阱。如果你需要代码,只需下载我的完整项目(见文章顶部的链接)。或者通过文本中的链接找到我的信息来源。