Search code examples
c#pinvoke

Set TreeViewItem checkbox state with SendMessage


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?


Solution

  • 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;
    }