Desktop Icon manipulation - how to get a handle to SysListView32 when themes with picture rotation are enabled

user594650 picture user594650 · Jan 29, 2011 · Viewed 7.6k times · Source

I'm trying to move icons around the desktop, everything works well until a theme that has picture rotation is picked. With a basic Windows 7 theme, the SysListView32 is child of SHELLDLL_DefView which is in turn child of Progman.

But when a picture rotation desktop theme is picked, SysListView32 becomes child of SHELLDLL_DefView which in turn becomes child of WorkerW. There are more than 1. How should I go about finding the right HWND pointing to the right WorkerW. Enumerate all desktop windows and check each one with a classname WorkerW?

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, GetWindow_Cmd uCmd);
[DllImport("user32.DLL")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

enum GetWindow_Cmd : uint
{
    GW_HWNDFIRST = 0,
    GW_HWNDLAST = 1,
    GW_HWNDNEXT = 2,
    GW_HWNDPREV = 3,
    GW_OWNER = 4,
    GW_CHILD = 5,
    GW_ENABLEDPOPUP = 6
}

In my main() for example, I make the following calls:

IntPtr HWND = FindWindow("Progman",null);
HWND = GetWindow(HWND, GetWindow_Cmd.GW_CHILD);
HWND = GetWindow(HWND, GetWindow_Cmd.GW_CHILD);

Answer

user594650 picture user594650 · Jan 29, 2011

Big thanks to Hans for trying this on his machine, and for Sertac for clueing me that SysListView32 changes parents from "Progman" to the "WorkerW" classname. My solution was to first try to find the SysListView32 inside Progman's children:

       hwndIcon = NativeMethods.FindWindow("Progman", null);
       hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SHELLDLL_DefView", null);
       hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SysListView32", "FolderView");

if hwndIcon returns IntPtr.Zero, I try enumerating all windows under the desktop, then find those whose classname is "WorkerW" (I do this in the delegate GetSysListViewContainer(...)) Amongst the latter, I find "The One And Only One," ie. the one that has a child. That is the one that contains SHELLDLL_DefView, which itself contains SysListView32, which itself contains the handle of each Icon on the desktop:

       hwndIcon = NativeMethods.FindWindow("Progman", null);
       hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SHELLDLL_DefView", null);
       hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SysListView32", "FolderView");

       if (hwndIcon == IntPtr.Zero)
        {
            IntPtr hDesktop = NativeMethods.GetDesktopWindow();
            IntPtr hwnd = IntPtr.Zero;
            EnumWindowsProc ewp = new EnumWindowsProc(GetSysListViewContainer);
            EnumWindows(ewp, 0);
            hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SHELLDLL_DefView", null);
            hwndIcon = NativeMethods.FindWindowEx(hwndIcon, IntPtr.Zero, "SysListView32", "FolderView");
        }

With the following I get a desktop icon count:

       int vItemCount = NativeMethods.SendMessage(hwndIcon, LVM_GETITEMCOUNT, 0, 0);
       string vText;
       int vProcessId = 0;

And with this I loop through all icons:

        NativeMethods.GetWindowThreadProcessId(hwndIcon, ref vProcessId);
        IntPtr vProcess = NativeMethods.OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, vProcessId);
        IntPtr foo = IntPtr.Zero;
        IntPtr vPointer = NativeMethods.VirtualAllocEx(vProcess, IntPtr.Zero, sizeof(uint), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

            for (int j = 0; j < vItemCount; j++)
            {
                byte[] vBuffer = new byte[256];
                LVITEM[] vItem = new LVITEM[1];
                vItem[0].mask = LVIF_TEXT;
                vItem[0].iItem = j;
                vItem[0].iSubItem = 0;
                vItem[0].cchTextMax = vBuffer.Length;
                vItem[0].pszText = (IntPtr)((int)vPointer + Marshal.SizeOf(typeof(LVITEM)));
                uint vNumberOfBytesRead = 0;
                WriteProcessMemory(vProcess, vPointer, Marshal.UnsafeAddrOfPinnedArrayElement(vItem, 0), Marshal.SizeOf(typeof(LVITEM)), ref vNumberOfBytesRead);
                SendMessage(hwndIcon, LVM_GETITEMW, j, vPointer.ToInt32());
                ReadProcessMemory(vProcess, (IntPtr)((int)vPointer + Marshal.SizeOf(typeof(LVITEM))), Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0), vBuffer.Length, out vNumberOfBytesRead);

                // Get the name of the Icon
                vText = Encoding.Unicode.GetString(vBuffer, 0, (int)vNumberOfBytesRead);

                // Get  Icon location
                SendMessage(hwndIcon, LVM_GETITEMPOSITION, j, vPointer.ToInt32());
                Point[] vPoint = new Point[1];
                foo = Marshal.UnsafeAddrOfPinnedArrayElement(vPoint, 0);
                ReadProcessMemory(vProcess, vPointer, Marshal.UnsafeAddrOfPinnedArrayElement(vPoint, 0), Marshal.SizeOf(typeof(Point)), out vNumberOfBytesRead);

               //and ultimaely move icon.
               SendMessage(hwndIcon, LVM_SETITEMPOSITION, j, lParam[0]);

So to recap, I needed to figure out why I couldn't get a handle for the listview container where all desktop icons are stored in Windows. The original code I had worked well when there was no background rotation, but failed to get the handle of ListSysView32 when there was.

Is there any better way to do this from .Net? kj