Introduction
Recently, my company gave me a dock station for my laptop. It is a nice toy to play with, but soon I faced a little problem. From time to time (actually rather frequently) after reboot, my laptop forgets screen resolution. It does not bother me too much because restoring screen resolution is fast and easy. But the problem is that with resolution, it forgets positions of all icons on my desktop. It is easy to place them again, but not so fast as I have quite a lot of icons.
Of course, there are programs like IconRestorer which can help me. But after all, we are programmers and this is an interesting task to solve. So let the journey begin!
Registry
I Googled where Windows stores information about positions of icons on desktop. And there were nice and easy answers that this information is stored in the registry key "CurrentUser\Software\Microsoft\Windows\Shell\Bags\1\Desktop". I knew how to work with registry and it was an easy task. But very soon I realized that it is not a correct answer. Looks like Windows reads this information on LogIn and stores in on LogOut. And I'd like to restore positions of icons without rebooting my computer. So I needed another solution.
PInvoke
I Googled again and found that actually desktop is a ListView
control inside some process. And anyone can send messages to this control using standard SendMessage
API of Windows. And get some results. But it means that I should use PInvoke technology to work with legacy Windows API. Personally, I felt uneasy about PInvoke. First of all, you should know in which DLL each required function lives. Then there are different marshaling issues. I was scared about it. But fortunately, I found a great site: PInvoke.net. It is very easy to use and contains .NET wrappers for many native Windows functions and structures including some useful overloads. I am definitely going to use this site later on and hope it will help you too.
Process of Desktop
To be able to send messages to a control, one should get its handle - unique id of the control in Windows. In order to do it, one should first identify process containing this control. And here, I faced the first problem. You see, Windows can host the control of desktop in different processes. If you have one static image on desktop, you have one process. If you have several images and Windows changes background image from time to time, then you have another process. It took me some time to find a generic algorithm which should get handle of desktop control here.
Setting Positions of Icons
As it was said, icons are just items displayed into ListView
control. It is very easy to get total number of these items:
var numberOfIcons =
(int)Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero);
It is also easy to set position of an icon to some point (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));
where iconIndex
is index of icon between 0
and numberOfIcons - 1
.
So the next thing to do was to get icon positions. And here was another problem waiting for me.
Getting Positions of Icons
It may look like it is very easy to get position of an icon. You just call something like this:
Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMPOSITION, iconIndex, pointerToResult);
and variable pointerToResult
contains pointer to a simple structure describing position of icon with index iconIndex
. But it does not work. The problem is that control we send message to is in the another process. So the pointerToResult
variable points to the memory in another process. And we can't access it due to memory protection. So the algorithm becomes more complicated. I took it from here. Shortly, you must first allocate shared memory, then send command to write some information into this shared memory and then read required information from there.
Now, I had the abilities both to get and set positions of icons. I wrote a simple program which saved and restored these positions. But appeared that it was not the end.
Getting Texts of Icons
Very soon, after I started using my program, I realized that it may swap icons. My Microsoft Word icon was restored on the place of Recycle Bin, etc. It means that after reboot index of Microsoft Word icon was changed and I can't rely on indexes only. I started thinking what I can use as a constant value to distinguish icons. The obvious answer was: 'their texts'. But this simple task became a nightmare. There are a lot of examples in the Internet how you can get texts of icons on your desktop using the same SendMessage
mechanism. And no one worked on my laptop. Finally, I came across this piece of code solving this task using UIAutomationClient
assembly. It allowed me to get texts of my icons and connect them with indexes of icons. I stored texts with positions and restored positions using index of icon having the same text on current load of computer.
One Last Piece
Now I was able to enjoy my program. It saved and restored positions of icons successfully. But soon, I noticed another problem. After the load of my laptop, I restored positions of icons. They were placed fine. But when I started several applications, all icons suddenly messed up again. I started to investigate this problem and found that I need to refresh desktop to preserve icon positions. Luckily it was easy to do:
[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);
But very soon, it was clear that this code has one side effect. It also frequently arranges icons. This is not exactly what 'Refresh' command does for desktop. I started searching how to make real refresh. And I have not found anything working on my laptop. After some time, I suddenly realized that there is a shortcut 'F5' in Windows that refreshes the desktop and I only need to send this key to the desktop. The code is rather simple:
PostMessage(_desktopHandle, WM_KEYDOWN, VK_F5, 0);
where PostMessage
is similar to SendMessage
. Now everything seemed to work fine.
Points of Interest
It is still interesting for me if there is a way to get texts of icons using SendMessage
:
Win32.SendMessage(_desktopHandle, Win32.LVM_GETITEMTEXT, iconIndex, ...);
There must be some way...
P.S.
I understand that there is not much code in the article. But after all, I haven't created a lot of code. Most part of it was taken from different sources. I just wanted to show pitfalls on the way of solving takes of saving and restoring icon positions. If you need the code, just download my full project (see the link on the top of the article). Or follow links in the text to my sources of information.