日博365娱乐场

C#实现操作Windows窗口句柄:遍历、查找窗体和控件【窗口句柄最全总结之一】

📁 日博365娱乐场 ⌚ 2026-01-25 13:42:57 👤 admin 👁️ 3110 ❤️ 797
C#实现操作Windows窗口句柄:遍历、查找窗体和控件【窗口句柄最全总结之一】

C#对Windows窗口或窗口句柄的操作,都是通过 P/Invoke Win32 API 实现的,通过DllImport引入Windows API操作窗口(句柄),可以实现枚举已打开的窗口、向窗口或子窗口(窗口内的控件)发送文本、关闭、键盘按键等各种命令,实现窗口的基本操作。

新建Windows帮助类public class WndHelper{},提供窗口相关的操作,并添加引用using System.Runtime.InteropServices;。

新建WindowHandle项目,用于测试窗口句柄帮助类的使用。

枚举和查找windows窗口信息

EnumWindows枚举所有(顶层)窗口和获取窗口信息的API

EnumWindows API 用来枚举所有的窗口,其第一个参数需要定义一个方法作为参数传入,用于处理枚举时的每一次结果(即C#中的委托方法,委托类型为WndEnumProc(IntPtr hWnd, int lparam))。

实现一个FindAllWindows方法,获取所有的顶层窗口信息,可以指定查询条件(Predicate泛型委托),

WndEnumProc枚举窗口时的处理方法中,需要判断顶层窗口、获取必需的窗口信息

GetParent 获取窗口的父窗口,用于判断找到的窗口是否是顶层窗口。

IsWindowVisible 判断窗口是否可见

GetWindowText 获取窗口标题

GetClassName 获取窗口类名

GetWindowRect 获取窗口位置和尺寸,需要定义一个结构体 LPRECT

注:从Windows8开始,

EnumWindows仅仅遍历桌面应用的顶层窗口。也就是说,Win8之后的使用可以不需要判断

GetParent是否为顶层窗口。

对应的win32 API如下:

///

/// 枚举窗口时的委托参数

///

///

///

///

private delegate bool WndEnumProc(IntPtr hWnd, int lParam);

///

/// 枚举所有窗口

///

///

///

///

[DllImport("user32")]

private static extern bool EnumWindows(WndEnumProc lpEnumFunc, int lParam);

///

/// 获取窗口的父窗口句柄

///

///

///

[DllImport("user32")]

private static extern IntPtr GetParent(IntPtr hWnd);

[DllImport("user32")]

private static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32")]

private static extern int GetWindowText(IntPtr hWnd, StringBuilder lptrString, int nMaxCount);

[DllImport("user32")]

private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("user32")]

private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);

[DllImport("user32")]

private static extern bool GetWindowRect(IntPtr hWnd, ref LPRECT rect);

[StructLayout(LayoutKind.Sequential)]

private readonly struct LPRECT

{

public readonly int Left;

public readonly int Top;

public readonly int Right;

public readonly int Bottom;

}

窗体信息结构体

WindowInfo结构体用于存放必需的窗体信息,也可以直接指定为只读结构体(public readonly struct WindowInfo{},需要C#7.2版本支持)

获取的窗体信息包括窗口句柄、窗口标题、位置、大小尺寸、是否是最小化、可见性等。

///

/// 获取 Win32 窗口的一些基本信息。

///

public struct WindowInfo

{

public WindowInfo(IntPtr hWnd, string className, string title, bool isVisible, Rectangle bounds) : this()

{

Hwnd = hWnd;

ClassName = className;

Title = title;

IsVisible = isVisible;

Bounds = bounds;

}

///

/// 获取窗口句柄。

///

public IntPtr Hwnd { get; }

///

/// 获取窗口类名。

///

public string ClassName { get; }

///

/// 获取窗口标题。

///

public string Title { get; }

///

/// 获取当前窗口是否可见。

///

public bool IsVisible { get; }

///

/// 获取窗口当前的位置和尺寸。

///

public Rectangle Bounds { get; }

///

/// 获取窗口当前是否是最小化的。

///

public bool IsMinimized => Bounds.Left == -32000 && Bounds.Top == -32000;

}

获取窗口FindAllWindows的实现

通过Predicate设置获取的窗口满足的条件,默认仅查找可见且有标题栏的窗口。

///

/// 查找当前用户空间下所有符合条件的(顶层)窗口。如果不指定条件,将仅查找可见且有标题栏的窗口。

///

/// 过滤窗口的条件。如果设置为 null,将仅查找可见和标题栏不为空的窗口。

/// 找到的所有窗口信息

public static IReadOnlyList FindAllWindows(Predicate match = null)

{

windowList = new List();

//遍历窗口并查找窗口相关WindowInfo信息

EnumWindows(OnWindowEnum, 0);

return windowList.FindAll(match ?? DefaultPredicate);

}

///

/// 遍历窗体处理的函数

///

///

///

///

private static bool OnWindowEnum(IntPtr hWnd, int lparam)

{

// 仅查找顶层窗口。

if (GetParent(hWnd) == IntPtr.Zero)

{

// 获取窗口类名。

var lpString = new StringBuilder(512);

GetClassName(hWnd, lpString, lpString.Capacity);

var className = lpString.ToString();

// 获取窗口标题。

var lptrString = new StringBuilder(512);

GetWindowText(hWnd, lptrString, lptrString.Capacity);

var title = lptrString.ToString().Trim();

// 获取窗口可见性。

var isVisible = IsWindowVisible(hWnd);

// 获取窗口位置和尺寸。

LPRECT rect = default;

GetWindowRect(hWnd, ref rect);

var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);

// 添加到已找到的窗口列表。

windowList.Add(new WindowInfo(hWnd, className, title, isVisible, bounds));

}

return true;

}

///

/// 默认的查找窗口的过滤条件。可见 + 非最小化 + 包含窗口标题。

///

private static readonly Predicate DefaultPredicate = x => x.IsVisible && !x.IsMinimized && x.Title.Length > 0;

///

/// 窗体列表

///

private static List windowList;

获取所有的可见窗体:

var windows = WndHelper.FindAllWindows();

for (int i = 0; i < windows.Count; i++)

{

var window = windows[i];

Console.WriteLine($@"{i.ToString().PadLeft(3, ' ')}. {window.Title}

{window.Bounds.X}, {window.Bounds.Y}, {window.Bounds.Width}, {window.Bounds.Height}");

}

Console.ReadLine();

查好包含指定Title的窗体信息:

var windows = WndHelper.FindAllWindows(x => x.Title.Contains("Test"));

不设置过滤,查好所有窗体信息:

var windows = WndHelper.FindAllWindows(x => true);

EnumChildWindows遍历子窗口

EnumChildWindows用于遍历指定父窗口(可选)的子窗口。

BOOL EnumChildWindows(

[in, optional] HWND hWndParent,

[in] WNDENUMPROC lpEnumFunc,

[in] LPARAM lParam

);

///

/// 遍历子窗体(控件)

///

/// 父窗口句柄

/// 遍历的回调函数

/// 传给遍历时回调函数的额外数据

///

[DllImport("user32.dll")]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool EnumChildWindows(IntPtr hwndParent, WndEnumProc lpEnumFunc, int lParam);

///

/// 枚举窗口时的委托参数

///

///

///

///

private delegate bool WndEnumProc(IntPtr hWnd, int lParam);

FindWindow/FindWindowEx查找窗体

FindWindow、FindWindowEx查找顶层窗体和子窗体

FindWindow方法可以直接查找某顶层窗体句柄。

FindWindowEx方法用于查找子窗体句柄。

///

/// 查找窗体

///

/// 窗体的类名称,比如Form、Window。若不知道,指定为null即可

/// 窗体的标题/文字

///

[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]

private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

///

/// 查找子窗体(控件)

///

/// 父窗体句柄,不知道窗体时可指定IntPtr.Zero

/// 子窗体(控件),通常不知道子窗体(句柄),指定0即可

/// 子窗体(控件)的类名,通常指定null,它是window class name,并不等同于C#中的列名Button、Image、PictureBox等,两者并不相同,可通过GetClassName获取正确的类型名

/// 子窗体的名字或控件的Title、Text,通常为显示的文字

///

[DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true)]

private static extern IntPtr FindWindowEx(IntPtr hwndParent, uint hwndChildAfter, string lpszClass, string lpszWindow);

HWND FindWindowEx(HWND hwndParent,HWND hwndChildAfter,LPCTSTR lpszClass,LPCTSTR lpszWindow);

FindWindowEx的参数:

hwndParent:要查找子窗口的父窗口句柄。如果hwndParent为NULL,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。Windows NT5.0 and later:如果hwndParent是HWND_MESSAGE,函数仅查找所有消息窗口。

hwndChildAfter :子窗口句柄。查找从在Z序中的下一个子窗口开始。子窗口必须为hwndParent窗口的直接子窗口而非后代窗口。如果HwndChildAfter为NULL,查找从hwndParent的第一个子窗口开始。如果hwndParent 和 hwndChildAfter同时为NULL,则函数查找所有的顶层窗口及消息窗口。

lpszClass:指向一个指定了类名的空结束字符串,或一个标识类名字符串的成员的指针。它表示window class name,并不等同于C#中的类名,通常指定null即可,可通过GetClassName获取正确的类型名。

lpszWindow:指向一个指定了窗口名(窗口标题)的空结束字符串。如果该参数为 NULL,则为所有窗口全匹配。返回值:如果函数成功,返回值为具有指定类名和窗口名的窗口句柄。如果函数失败,返回值为NULL。

查找窗体或控件的使用

查找子窗体(控件)时,FindWindowEx第三个参数windows类名指定null即可。不要使用C#中的Button等,将会查找不到。

var wndHandle = WndHelper.FindWindow(null, "Form测试窗体的标题栏");

if (wndHandle != IntPtr.Zero)

{

//找到Button

IntPtr btnHandle = WndHelper.FindWindowEx(wndHandle, IntPtr.Zero, null, "点击测试");

IntPtr btnHandle2 = WndHelper.FindWindowEx(wndHandle, IntPtr.Zero, null, "Click");

//IntPtr btnHandle3 = WndHelper.FindWindowEx(msgHandle, IntPtr.Zero, "Control", "点击测试");

if (btnHandle != IntPtr.Zero)

{

WndHelper.SendClick(btnHandle); // 发送点击事件

}

}

MessageBox显示的窗体也为顶层窗体

var wndHandle = WndHelper.FindWindow(null, "测试"); // 查找MessageBox窗体

绑定&快捷按键的控件查找

对于默认的MessageBox显示的窗体,如果是不同类型的按钮,会通知指定快捷键,比如Y表示“是”;N表示“否”。

快捷键的绑定是通过&(包括自己手动实现的绑定快捷键),因此查找时也需要指定,比如"否(&N)"、"是(&Y)"

如下,查找一个标题为"测试"MessageBox弹窗的窗口句柄,并查找其下面的否(N)按钮,实现点击。

var wndHandle = WndHelper.FindWindow(null, "测试"); // 查找MessageBox窗体

if (wndHandle != IntPtr.Zero)

{

IntPtr noBtnHandle = WndHelper.FindWindowEx(wndHandle, IntPtr.Zero, null, "否(&N)"); // 使用&对应快捷按键,查找MessageBox中的"否(N)"按钮

if (noBtnHandle != IntPtr.Zero)

{

WndHelper.SendClick(noBtnHandle);

}

}

查找&快捷键的按钮控件:

FindWindow与FindWindowW、FindWindowA

在winuser.h头的定义中,FindWindow作为FindWindowW或FindWindowA的别名,它根据UNICODE定义的预处理常量自动选择该函数的ANSI或Unicode版本。

注意,混合使用编码将可能导致编译或运行时错误。通常推荐直接FindWindow,而不要直接使用FindWindowW或FindWindowA。

FindWindowEx同样,为FindWindowExA和FindWindowExW的自动别名。

附:关于上面使用遍历窗口API查找窗体时的静态字段windowList和childWindowList

静态字段windowList和childWindowList用于循环窗口句柄时处理每个句柄,但是,由于是共用的静态字段,如果遇到多线程的情况下,肯定会出现问题或混乱。

因此,最好修改下代码,处理多线程使用时,这两个字段的竞争。

参考

Windows 系统上用 .NET/C# 查找所有窗口,并获得窗口的标题、位置、尺寸、最小化、可见性等各种状态

C# 获得窗体句柄并发送消息(利用windows API可在不同进程中获取)

使用 SetParent 跨进程设置父子窗口时的一些问题(小心卡死)

EnumWindows function (winuser.h)

EnumChildWindows function (winuser.h)

FindWindowW function (winuser.h)

相关数据

ball365球网 比分大师-足球篮球比分数据

比分大师-足球篮球比分数据

⌚ 09-27 👁️ 7349
日博365娱乐场 外阴前庭位置

外阴前庭位置

⌚ 07-20 👁️ 6892
日博365娱乐场 带墨字的男孩名字大全

带墨字的男孩名字大全

⌚ 07-09 👁️ 6343