I have a need of changing checkbox state in TreeView (exactly 'SysTreeView32') item owned by external application - for automation purposes. I already have TreeView handle and TreeViewItem handle. I have also found some examples how I can set checkbox state, but for some reason, it is not working (SendMessage returns 0 or crashes entire application). But to the code. What i tried already is this:
TVITEM struct:
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
internal struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
pinvoke for SendMessage:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref TVITEM lParam);
And my method:
internal static void SetTreeNodeState(int treeViewHandler, int treeViewItemHandler, bool state)
{
TVITEM tvItem = new TVITEM();
tvItem.mask = TVIF_STATE | TVIF_HANDLE;
tvItem.hItem = (IntPtr)treeViewItemHandler;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
tvItem.state = (state ? 2 : 1) << 12;
var result = SendMessage((IntPtr)treeViewHandler, TVM_SETITEMW, IntPtr.Zero, ref tvItem);
}
This is the closest approach (i think, at last i did not crash target application once). Of course i have tried to sniff messages for target tree view using Spy++. What concerns me is that Spy++ shows that LParam for SendMessage is actually "TVITEMEXW" but i can beryl find anything about that struct.
Generally i also tried same think with TVM_GETITEMW, but however i did not crash application, SendMessage always returns zero.
What i'm doing wrong?
Okey, thanks to David Heffernan, i figured this out. I Have created overload for SendMessage, that accepts lParam as object by ref:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, Int32 nSize, out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
private static IntPtr SendMessage<T>(Process process, IntPtr hWnd, int msg, int wParam, ref T lParam)
{
uint size = (uint)Marshal.SizeOf(lParam);
byte[] buffer = new byte[size];
IntPtr processHandle = process.Handle;
IntPtr pPointer = VirtualAllocEx(processHandle, IntPtr.Zero, size, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ReadWrite);
IntPtr inputPtr = Marshal.AllocHGlobal((int)size);
IntPtr outputPtr = Marshal.AllocHGlobal((int)size);
Marshal.StructureToPtr(lParam, inputPtr, false);
WriteProcessMemory(processHandle, pPointer, inputPtr, size, out UIntPtr nNbBytesWritten);
IntPtr resultPtr = SendMessage(hWnd, msg, wParam, pPointer);
ReadProcessMemory(processHandle, pPointer, buffer, buffer.Length, out IntPtr nNbBytesRead);
Marshal.Copy(buffer, 0, outputPtr, (int)size);
T result = Marshal.PtrToStructure<T>(outputPtr);
lParam = result;
Marshal.FreeHGlobal(inputPtr);
Marshal.FreeHGlobal(outputPtr);
VirtualFreeEx(processHandle, pPointer, 0, AllocationType.Release);
return resultPtr;
}
Usage example
Set checkbox state for given tree view item:
internal static void SetTreeNodeState(IntPtr treeViewHandle, IntPtr treeViewItemHandle, bool state)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = (uint)(state ? 2 : 1) << 12
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_SETITEMW, 0, ref tvItem);
}
Get checkbox state for given tree view item:
internal static bool GetTreeNodeState( IntPtr treeViewHandle, IntPtr treeViewItemHandle)
{
TVITEM tvItem = new TVITEM
{
mask = TVIF_STATE | TVIF_HANDLE,
hItem = treeViewItemHandle,
stateMask = TVIS_STATEIMAGEMASK,
state = 0
};
Process process = Process.GetProcessesByName("ProcessName")[0];
IntPtr ptr = SendMessage(process, treeViewHandle, TVM_GETITEMW, 0, ref tvItem);
if (ptr != IntPtr.Zero)
{
uint iState = tvItem.state >> 12;
return iState == 2 ? true : false;
}
return false;
}
TVITEM:
[StructLayout(LayoutKind.Sequential)]
internal struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}