使用 SendMessage 设置 TreeViewItem 复选框状态

Set TreeViewItem checkbox state with SendMessage

我需要更改外部应用程序拥有的 TreeView(确切地说 'SysTreeView32')项中的复选框状态 - 用于自动化目的。我已经有了 TreeView 句柄和 TreeViewItem 句柄。我还找到了一些如何设置复选框状态的示例,但由于某种原因,它不起作用(SendMessage returns 0 或使整个应用程序崩溃)。但是要代码。我已经尝试过的是:

TVITEM 结构:

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

为 SendMessage 调用:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref TVITEM lParam);

还有我的方法:

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

这是最接近的方法(我想,我终于没有让目标应用程序崩溃一次)。当然,我曾尝试使用 Spy++ 嗅探目标树视图的消息。让我担心的是 Spy++ 显示 SendMessage 的 LParam 实际上是 "TVITEMEXW" 但我可以 beryl 找到关于该结构的任何信息。

通常我也尝试过与 TVM_GETITEMW 相同的想法,但是我没有崩溃应用程序,SendMessage 总是 returns 零。

我做错了什么?

当您发送此特定消息时,您需要提供一个结构的地址。因为 window 属于不同的进程,所以您提供的地址无效。 Windows 个进程具有独立的虚拟内存地址 space。您提供的地址在您的进程中有效,但仅在您的进程中有效。

为了解决这个问题并发送此消息,您需要使用 VirtualAllocEx 在目标进程中分配内存。您还需要使用 WriteProcessMemory 来填充结构。如果您的进程和目标进程具有不同的位数,您需要注意结构布局的任何可能问题。您需要对像 pszText 这样的成员执行相同的技巧,它们本身就是指针。

这里已经有很多问题涉及跨进程消息编组的主题。我相信您将能够找到它们。同样,您现在可以在网上找到很多教程,您现在已经意识到了这个问题。

也许更大的问题是其他进程可能不会像您期望的那样以这种方式从外部被戳到。如果您发现编写自己的跨进程自动化非常具有挑战性,请不要感到惊讶。与其这样做,不如使用 UI 自动化?

好的,感谢 David Heffernan,我解决了这个问题。我已经为 SendMessage 创建了重载,它通过 ref:

接受 lParam 作为对象
[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;
}

用法示例

设置给定树视图项的复选框状态:

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

获取给定树视图项的复选框状态:

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