C#实现操作Windows窗口句柄:SendMessage/PostMessage发送系统消息、事件和数据【窗口句柄总结之二】

简介: SendMessage/PostMessage API 可以实现发送系统消息,这些消息可以定义为常见的鼠标或键盘事件、数据的发送等各种系统操作......

SendMessage向窗口或控件发送文本、按键、鼠标事件等消息

SendMessage的定义如下,用于向窗口句柄发送系统定义的消息,以及额外的数据。

LRESULT SendMessage(
  [in] HWND   hWnd,
  [in] UINT   Msg,
  [in] WPARAM wParam,
  [in] LPARAM lParam
);

SendMessage向窗口发送消息事件

向窗口发送文字(输入文字)

WM_SETTEXT = 0x000C表示发送文字消息,第4个参数表示文字内容。

/// <summary>
/// 发送文本到窗口句柄(或控件)
/// </summary>
/// <param name="hWnd"></param>
/// <param name="text"></param>
public static void SendText(IntPtr hWnd, string text)
{
    SendMessage(hWnd, WM_SETTEXT, IntPtr.Zero, text);
}

const int WM_SETTEXT = 0x000C;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);

我们通过帮助类项Winform的TextBox控件发送文本测试:

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

if (wndHandle != IntPtr.Zero)
{
    IntPtr txtHandle = WndHelper.FindWindowEx(wndHandle, IntPtr.Zero, null, "");
    if (txtHandle != IntPtr.Zero)
    {
        WndHelper.SendText(txtHandle,"我是文字");
    }
}

这个时候,如果有两个TextBox控件,则永远只会获取某一个(最后一个添加到窗体的TextBox),因此,只会向此控件发送消息文本。

这个时候,就需要通过遍历的方式获取到其他控件了。

向窗口发送键盘按键、发送回车键

/// <summary>
/// 发送回车键(Enter)到窗口(或控件)
/// </summary>
/// <param name="hWnd"></param>
public static void SendEnter(IntPtr hWnd)
{
    SendMessage(hWnd, WM_CHAR, VK_ENTER, 0);
}
/// <summary>
/// 发送键盘按键到窗口(或控件)
/// </summary>
/// <param name="hWnd"></param>
/// <param name="keyValue">按键的数字值KeyValue</param>
public static void SendKey(IntPtr hWnd, uint keyValue)
{
    SendMessage(hWnd, WM_CHAR, keyValue, 0);
}
const int WM_CHAR = 0x0102;
const uint VK_ENTER = 0x0D; // 13 Enter键
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam);
结合第四个参数可以发送组合按键。

比如,发送字母A的按键:

WndHelper.SendKey(txtHandle, 65); // 发送字母A

发送回车

WndHelper.SendEnter(txtHandle);
关于键盘输入更多的内容参见 Using Keyboard Input

向窗口发送鼠标事件

前面的代码中已经有使用SendClick发送鼠标事件的代码,下面对此进行下介绍。

  • 发送Click点击事件
/// <summary>
/// 发送点击事件
/// </summary>
/// <param name="hwnd">控件句柄</param>
public static void SendClick(IntPtr hwnd)
{
    SendMessage(hwnd, WM_CLICK, 0, 0);
}
/// <summary>
/// 发送点击事件
/// </summary>
/// <param name="hwnd">控件句柄</param>
/// <param name="x">鼠标位置x</param>
/// <param name="y">鼠标位置y</param>
public static void SendClick(IntPtr hwnd, int X, int Y)
{
    int lparm = (Y << 16) + X;
    SendMessage(hwnd, WM_CLICK, 0, lparm);
}

[DllImport("user32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
/// <summary>
/// 点击消息
/// </summary>
const uint WM_CLICK = 0xF5;

直接调用SendClick方法即可。

  • 发送鼠标按下、抬起事件,模拟点击
/// <summary>
/// 发送鼠标点击事件,包含MouseDown和MouseUp
/// </summary>
/// <param name="hwnd">控件句柄</param>
/// <param name="x">鼠标位置x</param>
/// <param name="y">鼠标位置y</param>
public static void SendMouseClick(IntPtr hwnd, int X, int Y)
{
    int lparm = (Y << 16) + X;
    int lngResult = SendMessage(hwnd, WM_LBUTTONDOWN, 0, lparm);
    int lngResult2 = SendMessage(hwnd, WM_LBUTTONUP, 0, lparm);
}
/// <summary>
/// 鼠标按下
/// </summary>
const uint WM_LBUTTONDOWN = 0x0201;
/// <summary>
/// 鼠标抬起
/// </summary>
const uint WM_LBUTTONUP = 0x0202;

向窗口发送关闭指令

public static void CloseWindow(IntPtr hwnd)
{
    SendMessage(hwnd, WM_CLOSE, 0, 0);
}

const int WM_CLOSE = 0x0010;

SendMessage实现在进程间传送数据

SendMessage的另一个巧妙的作用是实现跨窗体,或跨进程间的数据传递,当然,在传递时需要在对应的窗口处理函数WndProc中进行数据的获取。

对于Winform,即需要重写WndProc函数接受数据传递。

对于同一进程内的不同窗体间的数据传递,理论上,SendMessage可以使用任何消息传递额外数据(应该是不会产生额外操作的消息);

但是对于跨进程间的数据传递,则需要使用WM_COPYDATA = 0x004A消息类型实现,它用于传递只读数据。

更具体的介绍参见后续的文章。

SendMessage和PostMessage函数【更推荐PostMessage】

SendMessage用于向Windows或窗口发送特定的消息,SendMessage会调用窗口处理程序,并且直到窗口处理程序处理完消息才会返回。

所以,SendMessage发送消息会阻塞当前线程的执行,直到消息处理完成,这一点需要特别注意。

PostMessageSendMessage的作用一样,但是PostMessage是将消息发送到创建窗口的线程关联的消息队列中,并立即返回,不会等到消息处理完成。

PostMessage相当于异步发送消息,不会阻塞等待。

在多线程应用中推荐使用PostMessage,可以在各自创建的窗口间进行安全的通信,非常有用。

另外的想要发送消息,并立即返回,可以参考SendMessageCallbackSendNotifyMessage

PostMessagePostThreadMessage 函数则是发送到线程消息队列,并立即返回。

关于SendMessage和PostMessage的返回

SendMessage的返回表示消息处理的结果、消息处理后的返回值,由于无法控制另一个窗体内的消息处理,实际获取的结果作用并不大。可以作为消息是否正确处理完成的参考。

PostMessage返回表示是否发送成功,是一个bool值。但并不能决定何时执行、是否执行,只是成功发送到了消息队列中。

扩展 The difference between SendMessage and PostMessage

SendMessage发送的消息和系统定义的消息类型(消息列表)

参见官方文档Windows消息和消息队列

参考

相关文章
|
4月前
|
计算机视觉 Windows Python
windows下使用python + opencv读取含有中文路径的图片 和 把图片数据保存到含有中文的路径下
在Windows系统中,直接使用`cv2.imread()`和`cv2.imwrite()`处理含中文路径的图像文件时会遇到问题。读取时会返回空数据,保存时则无法正确保存至目标目录。为解决这些问题,可以使用`cv2.imdecode()`结合`np.fromfile()`来读取图像,并使用`cv2.imencode()`结合`tofile()`方法来保存图像至含中文的路径。这种方法有效避免了路径编码问题,确保图像处理流程顺畅进行。
420 1
|
17天前
|
存储 缓存 安全
硬盘数据恢复:恢复硬盘数据的9个实用方法(Windows版)
无论是工作文档、家庭照片,还是其他珍贵的数字资产,数据丢失总是一件让人头疼的事情。然而,当硬盘发生问题时,不必过于慌张——只要正确应对,许多数据都可以被成功恢复。本文将从常见数据丢失原因到具体恢复方法,为您提供全面的硬盘数据恢复指导。
|
1月前
|
数据库 数据安全/隐私保护 Windows
Windows远程桌面出现CredSSP加密数据修正问题解决方案
【10月更文挑战第30天】本文介绍了两种解决Windows系统凭据分配问题的方法。方案一是通过组策略编辑器(gpedit.msc)启用“加密数据库修正”并将其保护级别设为“易受攻击”。方案二是通过注册表编辑器(regedit)在指定路径下创建或修改名为“AllowEncryptionOracle”的DWORD值,并将其数值设为2。
275 3
|
22天前
|
数据库连接 数据库 C#
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(上)
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(上)
|
22天前
|
数据库连接 数据库 C#
Windows下C# 通过ADO.NET方式连接南大通用GBase 8s数据库(下)
本文接续前文,深入讲解了在Windows环境下使用C#和ADO.NET操作南大通用GBase 8s数据库的方法。通过Visual Studio 2022创建项目,添加GBase 8s的DLL引用,并提供了详细的C#代码示例,涵盖数据库连接、表的创建与修改、数据的增删查改等操作,旨在帮助开发者提高数据库管理效率。
|
2月前
|
SQL 缓存 分布式计算
C#如何处理上亿级数据的查询效率
C#如何处理上亿级数据的查询效率
40 1
|
3月前
|
存储 C# 开发者
枚举与结构体的应用:C#中的数据组织艺术
在C#编程中,枚举(`enum`)和结构体(`struct`)是非常重要的数据类型。枚举用于定义命名常量集合,提高代码可读性;结构体则封装相关数据字段,适合小型数据集。本文从基本概念入手,探讨它们的使用技巧、常见问题及解决方案,帮助开发者更好地利用这些特性构建健壮的应用程序。
50 8
|
2月前
|
中间件 数据库连接 API
C#数据分表核心代码
C#数据分表核心代码
43 0
|
4月前
|
缓存 NoSQL Linux
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
142 1
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
|
3月前
|
C#
C#一分钟浅谈:委托与事件的实现方式
本文详细介绍了C#编程中委托与事件的基础知识及应用场景。首先解释了委托的概念,包括定义与使用方法;接着介绍了事件这一基于委托的特殊类型,展示了如何在类中定义事件及跨类订阅与处理事件;最后讨论了常见问题如事件未处理异常、重复订阅及内存泄漏等,并提出了相应的解决方案。通过本文,读者将全面掌握委托与事件的使用技巧,提升应用程序的设计与开发水平。
142 7