通过FindWindowEx获取所有控件

6

我正在构建一个应用程序,它将获取运行在Windows窗体上的所有控件。首先,我可以注入dll到正在运行的Windows窗体应用程序中,并获取其句柄。然后,我会获取该应用程序中的所有子窗口。接下来,我想通过FindWindowEx获取子窗口中的所有控件,但是我做不到。

以下是代码:

static ArrayList GetAllChildrenWindowHandles(IntPtr hParent, int maxCount)
    {
        ArrayList result = new ArrayList();
        int ct = 0;
        IntPtr prevChild = IntPtr.Zero;
        IntPtr currChild = IntPtr.Zero;
        while (true && ct < maxCount)
        {
            currChild = FindWindowEx(hParent, prevChild, null, null);
            if (currChild == IntPtr.Zero)
            {
                int errorCode = Marshal.GetLastWin32Error();
                break;
            }
            result.Add(currChild);
            prevChild = currChild;
            ++ct;
        }
        return result;
    }

我获取了子窗口的句柄并将其用作父窗口。但是,我无法通过FindWindowEx获得子窗口中的所有控件。 抱歉,我的英语不太好。

1
如果你要查找某个特定窗口的所有子窗口,你需要使用 EnumChildWindows - Jerry Coffin
逻辑结构是一棵树。最容易使用递归函数遍历。然而,这也意味着ArrayList不是存储结果的正确数据结构。 - Hans Passant
逻辑结构是一棵树。最容易使用递归函数遍历。然而,这也意味着ArrayList不是存储结果的合适数据结构。我也这么认为,但我不知道该怎么做?你知道吗?谢谢 :) - user2208401
2个回答

9
您可以使用以下代码。将其放入某个辅助类中,例如像这样使用它...
var hwndChild = EnumAllWindows(hwndTarget, childClassName).FirstOrDefault();  

如果您希望的话,可以“失去”class检查 - 但通常您正在检查特定目标。

您可能还想检查我之前发布的这篇文章 - 该方法用于在远程窗口上设置聚焦(这种情况非常常见,您迟早会遇到这个问题)。
Pinvoke设置聚焦于特定控件

public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);

[DllImport("user32.Dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);

private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
    GCHandle gch = GCHandle.FromIntPtr(pointer);
    List<IntPtr> list = gch.Target as List<IntPtr>;
    if (list == null)
        throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
    list.Add(handle);
    return true;
}

public static List<IntPtr> GetChildWindows(IntPtr parent)
{
    List<IntPtr> result = new List<IntPtr>();
    GCHandle listHandle = GCHandle.Alloc(result);
    try
    {
        Win32Callback childProc = new Win32Callback(EnumWindow);
        EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
    }
    finally
    {
        if (listHandle.IsAllocated)
            listHandle.Free();
    }
    return result;
}

public static string GetWinClass(IntPtr hwnd)
{
    if (hwnd == IntPtr.Zero)
        return null;
    StringBuilder classname = new StringBuilder(100);
    IntPtr result = GetClassName(hwnd, classname, classname.Capacity);
    if (result != IntPtr.Zero)
        return classname.ToString();
    return null;
}

public static IEnumerable<IntPtr> EnumAllWindows(IntPtr hwnd, string childClassName)
{
    List<IntPtr> children = GetChildWindows(hwnd);
    if (children == null)
        yield break;
    foreach (IntPtr child in children)
    {
        if (GetWinClass(child) == childClassName)
            yield return child;
        foreach (var childchild in EnumAllWindows(child, childClassName))
            yield return childchild;
    }
}

这不是工作,我可以将应用程序WinForm中的所有控件放入子窗口。我只能获取子窗口,但无法获取子窗口中的所有控件。 - user2208401
尝试使用Spy++(或this)进行测试以确保其正常工作-请参见此处的完全相同代码here(并确保您加载所有内容,给它一些时间)。并尝试我的“SetFocus”链接建议(附加到“线程”)。如果您的控件是非标准(非Windows),则无法正常工作。当然,要删除.FirstOrDefault() :) - NSGaga-mostly-inactive
我遇到了错误:“找不到类型或命名空间名称为'Win32Callback'(您是否缺少使用指令或程序集引用?)”。 - Ashish

1

尝试使用Spy++并查看您要枚举的控件是否为窗口。 如果它们不是窗口,则无法使用此API进行枚举。


2
Spy++ 显示的控件怎么可能不是窗口? - Werner Henze
@WernerHenze:使用Spy++的目的就在于此。问题中的代码可能会失败,因为控件不是窗口,但在这种情况下,Spy++也会以同样的方式失败。也就是说,我们使用Spy++来确定正确的行为。 - MSalters
@MSalters:所以你是在说一个父窗口,在它的客户区域绘制了类似控件的东西,并处理鼠标点击,使其像控件一样运作,但实际上它并不是一个真正的窗口,只是从用户角度看起来像一个窗口!? - Werner Henze
@WernerHenze:是的,就像Internet Explorer一样。 - MSalters
3
我已经使用Spy++,确定需要访问的文本不是一个独立的控件(或“窗口”),但我可以获取其父窗口。是否有API可以用来查询此文本? - Chris Roberts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接