C#调用win32 api 操作其它窗口

时间:2023-03-10 07:08:21
C#调用win32 api 操作其它窗口

实现以下功能:

  1. 找到窗体
  2. 找到控件(也叫子窗体
  3. 获取内容
  4. 获取位置
  5. 设置
    • 位置
    • 内容
  6. 鼠标点击

示范

1. 找窗体

以操作系统自带的计算器为例

string clWindow = "CalcFrame"; //整个窗口的类名

string tlWindow = "计算器"; //窗口标题

IntPtr ParenthWnd = FindWindow(clWindow, tlWindow);

这样就得到了窗口的句柄 ParenthWnd ,如果 ParenthWnd==IntPtr.Zero 说明没有找到窗体;

[DllImport("User32.dll", EntryPoint = "FindWindow")]

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

参数 lpClassName,lpWindowName 可以只填写一个,但另一个需要填写: null 而不是 :"" (即 string.Empty)。

如果同时开了几个同样的窗口,比如启动了2个 计算器, 那返回的句柄是哪个的呢 ? 答案是:最顶层的那个。

1、如果计算器2界面,在计算器1界面的前面,那么会返回计算器2的句柄。 2、如果两个计算器界面是并排的,即不存在重叠(包括部分重叠), 先切换计算器1,然后切换计算器

2,然后切换别的程序,会返回计算器2的句柄,反之返回计算器1的句柄。

这个问题说的可能是Windows系统界面里某个名词的概念,如顶层窗口,Z次序,活动窗口。但我不是特别明白,姑且举例说明实际情况了。

2. 找控件,获取内容,获取位置

[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string lpszClass, string lpszWindow);

这个函数使用起来比较麻烦,hwndParent 指的是目标控件的直接上级的句柄,有时候你知道窗口的句柄还不行,还需要知道之间的层级关系,逐层查找才行。这里举例说明一下怎么用 :

FindWindowEx(ParenthWnd, IntPtr.Zero, "TPanel", "打印预览");

我想说的是:参数 hwndChildAfter 可以写 IntPtr.ZerolpszClasslpszWindow 可以为null 。

我一般是用枚举的方法:枚举得到这个窗口的所有句柄列表,然后筛选这个列表,从而得到想要的句柄。 上代码:

[DllImport("user32.dll", ExactSpelling = true)]
public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, int lParam); [DllImport("user32.dll")]
public static extern int GetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")]
public static extern int GetClassNameW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); public struct WindowInfo
{
public IntPtr hWnd;//句柄
public string szWindowName;//窗口名
public string szClassName;//类名
public System.Drawing.Rectangle Rect;//位置大小信息
} List<WindowInfo> EnumChildWindowsCallback(IntPtr handle)
{
//用于保存句柄列表
List<WindowInfo> wndList = new List<WindowInfo>();
win32Api.EnumChildWindows(handle, delegate (IntPtr hWnd, int lParam)
{
WindowInfo wnd = new WindowInfo();
StringBuilder sb = new StringBuilder(256);
//get hwnd
wnd.hWnd = hWnd;
//get window name
GetWindowTextW(hWnd, sb, sb.Capacity);
wnd.szWindowName = sb.ToString();
//get window class
GetClassNameW(hWnd, sb, sb.Capacity);
wnd.szClassName = sb.ToString();
RECT rect = new RECT();
if (GetWindowRect(hWnd, ref rect) == true)
wnd.Rect = rect.ToRectangle(); wndList.Add(wnd);
return true;
}, 0); //这里已经得到句柄列表 “wndList” ,对wndList进行筛选即可。
return wndList;
}

3. 设置内容,位置

设置窗口标题,文本框内容:

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

例子 const int WMSETTEXT = 0x000C; SendMessage(ParenthWnd, WMSETTEXT, IntPtr.Zero, "哥要你改标题");

设置窗口位置,大小:

[DllImport("User32.dll", EntryPoint = "MoveWindow")]
public static extern bool MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);

例子: MoveWindow(hWnd, x, y, nWidth, nHeight, true);

4. 鼠标点击

方法1 :给控件发送消息

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

找到控件句柄后,这样就可以了

const int WM_CLICK = 0x00F5;
SendMessage(hWnd, WM_CLICK, IntPtr.Zero, IntPtr.Zero);

方法2:移动鼠标位置,让鼠标点击

[DllImport("user32.dll")]
public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); [Flags]
public enum MouseEventFlags : uint
{
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
Absolute = 0x8000
}

得到窗口的位置后,根据要点击的目标点相对窗口的位置,设置点击位置,如

mouse_event((int)MouseEventFlags.LeftDown + (int)MouseEventFlags.LeftUp, windowRect.Left + 455, windowRect.Top + 40, 0, 0);

当然前提是,窗口是顶层的,鼠标位置在你想点的位置。可以通过这样的方法实现

[DllImport("user32.dll")]
public static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

实际例子: win32Api.SetForegroundWindow(ParenthWnd); win32Api.SetCursorPos(windowRect.Left + 455, windowRect.Top + 40); 然后再调用 mouse_event


这两种点击方法的对比:

  • 方法2使用面更广,如对于找不到句柄的按钮(原理基本上是窗体启动后,动态画上去的,另外wpf控件用spy++也是 找不到句柄的) 也可以采用这种方式。

  • 另外也不需要知道控件的句柄,知道相对窗口或别的控件的位置即可。

但是方法2的缺点也是很明显的:

  • 需要把目标窗口设置到顶层

  • 设置鼠标位置后,用户的鼠标移动操作,会造成干扰。如果执行 mouse_event 可能点到错误的位置。

(by xrcdev  http://www.cnblogs.com/xrcdev/)

参考:

http://www.cnblogs.com/zhuiyi/archive/2012/07/09/2583024.html