I query the desktop icons by LVM_GETITEMRECT
and only get the position of the icon. But I would also need the bounds.
Docu is here: https://learn.microsoft.com/en-us/windows/win32/controls/lvm-getitemrect
It is this part I am not able to implement:
When the message is sent, the left member of this structure is used to specify the portion of the list-view item from which to retrieve the bounding rectangle. It must be set to one of the following values ....
Here the code which is working, but only gets the position x/y. The width/height values are always 0. Remark: Complete code "frees" the memory in finally
.
genericPtr = VirtualAllocEx(
processHandle,
IntPtr.Zero,
(uint)Marshal.SizeOf(typeof(Rectangle)),
// Rectangle ist the maximum
// sizeof(uint), ??? SIZE HERE ???
AllocationType.Commit,
MemoryProtection.ReadWrite);
CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, genericPtr.ToInt32());
Rectangle[] vRectangle = new Rectangle[1];
int rectSize = Marshal.SizeOf(typeof(Rectangle));
ReadProcessMemory(processHandle, genericPtr,
Marshal.UnsafeAddrOfPinnedArrayElement(vRectangle, 0),
rectSize, out vNumberOfBytes);
I understand I have to set the LVIR_Message
value for SendMessage
, but how would I do it?
private enum LVIR_Message : uint
{
LVIR_BOUNDS = 0,
LVIR_ICON = 1,
LVIR_LABEL = 2,
LVIR_SELECTBOUNDS = 3
}
Ref to related question: How can I disable "Auto arrange icons" of the Desktop view in C#?
First off, Rectangle
is the wrong type to use. You need the RECT
struct instead.
And, you need to Pin the rectangle with GCHandle
before you can take its address.
And, you need to write the contents of the Rectangle
into the target process before you then send the message.
And, you can't use IntPtr.ToInt32()
if you compile your app for 64bit, you would need IntPtr.ToInt64()
instead, as LPARAM
changes size depending on the platform you are running on.
Try this instead:
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
...
RECT[] vRectangle = new RECT[1];
vRectangle[0].left = LVIR_Message.LVIR_BOUNDS;
GCHandle gch = GCHandle.Alloc(vRectangle, GCHandleType.Pinned);
try
{
uint rectSize = (uint)Marshal.SizeOf(typeof(RECT));
IntPtr rectPtr = gch.AddrOfPinnedObject();
// or:
// IntPtr rectPtr = Marshal.UnsafeAddrOfPinnedArrayElement(vRectangle, 0);
IntPtr processAllocPtr = VirtualAllocEx(
processHandle,
IntPtr.Zero,
rectSize,
AllocationType.Commit,
MemoryProtection.ReadWrite);
if (processAllocPtr == IntPtr.Zero)
{
throw new Win32Exception();
}
try
{
if (!WriteProcessMemory(
processHandle,
processAllocPtr,
rectPtr,
rectSize,
out vNumberOfBytesWritten))
{
throw new Win32Exception();
}
if (IntPtr.Size == 8)
{
CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr.ToInt64());
}
else
{
CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr.ToInt32());
}
// or, simply use/declare an overload of CWindow.SendMessage()
// that takes an IntPtr instead of an int/int64....
//
// CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr);
if (!ReadProcessMemory(
processHandle,
processAllocPtr,
rectPtr,
rectSize,
out vNumberOfBytes))
{
throw new Win32Exception();
}
}
finally
{
VirtualFreeEx(
processHandle,
processAllocPtr,
0,
MEM_RELEASE
);
}
}
finally
{
gch.Free();
}
// use the updated vRectangle[0] as needed...
Alternatively:
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
...
RECT vRectangle = new RECT();
vRectangle.left = LVIR_Message.LVIR_BOUNDS;
int rectSize = Marshal.SizeOf(typeof(RECT));
IntPtr rectPtr = Marshal.AllocHGlobal(rectSize);
try
{
Marshal.StructureToPtr(vRectangle, rectPtr, false);
IntPtr processAllocPtr = VirtualAllocEx(
processHandle,
IntPtr.Zero,
(uint)rectSize,
AllocationType.Commit,
MemoryProtection.ReadWrite);
if (processAllocPtr == IntPtr.Zero)
{
throw new Win32Exception();
}
try
{
if (!WriteProcessMemory(
processHandle,
processAllocPtr,
rectPtr,
(uint)rectSize,
out vNumberOfBytesWritten))
{
throw new Win32Exception();
}
if (IntPtr.Size == 8)
{
CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr.ToInt64());
}
else
{
CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr.ToInt32());
}
// or, simply use/declare an overload of CWindow.SendMessage()
// that takes an IntPtr instead of an int/int64....
//
// CWindow.SendMessage(listViewPtr, (uint)LVM_Message.LVM_GETITEMRECT, itemId, processAllocPtr);
if (!ReadProcessMemory(
processHandle,
processAllocPtr,
rectPtr,
(uint)rectSize,
out vNumberOfBytes))
{
throw new Win32Exception();
}
Marshal.PtrToStructure(rectPtr, vRectangle);
}
finally
{
VirtualFreeEx(
processHandle,
processAllocPtr,
0,
MEM_RELEASE
);
}
}
finally
{
Marshal.FreeHGlobal(rectPtr);
}
// use the updated vRectangle as needed...
Now, that being said, you are taking the completely wrong approach to begin with. The fact that the Desktop uses a ListView for its icons is an implementation detail that you should not rely on, as it would eventually break:
A reminder about the correct way of accessing and manipulating the position of icons on the desktop
Starting in Windows 10 version 1809, there were changes to the way that the positions of the desktop icons are managed, and one of the consequences is that if you try to manipulate the icon positions by talking directly to the ListView control, those changes aren’t taken into account by the icon management code
The recommended approach is to use IFolderView::GetItemPosition()
instead:
Manipulating the positions of desktop icons
Okay, we already have enough to be able to enumerate all the desktop icons and print their names and locations.
...
After getting the IFolderView, ... We ask the view for its Items enumeration, then proceed to enumerate each of the items. For each item, ... , and we ask the IFolderView for its position. Then we print the results.
Unfortunately, that only gets you an icon's position, not its bounding rectangle. I don't see a Shell API for retrieving that value.