wpf C# 操作DirectUI窗口 SendMessage+MSAA
原文:wpf C# 操作DirectUI窗口 SendMessage+MSAA
最近做一个抓取qq用户资料的工具,需要获取qq窗口上的消息,以前这种任务是用句柄获取窗口中的信息,现在qq的窗口用的是DirectUI,只有窗口句柄,没有控件句柄,句柄这条路走不通了。不过较新版的qq的部分控件实现了微软的IAccessible接口(称为Microsoft
Active Accessibility技术,简称MSAA),可以用另一套函数获取qq窗口的信息。不过要对窗口进行输入还是要靠句柄,上面说过,DirectUI的窗口只有一个句柄,因此模拟输入的时候不需要查找到具体的控件句柄,但要注意获取控件焦点,可能相对传统WinForm的窗口要简单点。
先介绍下和句柄操作相关的函数:
using System.Runtime.InteropServices;
[DllImport("user32.dll", SetLastError =true)]
static extern IntPtr FindWindow(string lpClassName,string lpWindowName);
根据类名和窗口标题查找句柄
[DllImport("user32.dll", SetLastError =true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle,IntPtr childAfter,
string className,
string windowTitle);
根据父句柄,前一个句柄,类名和窗口标题查找句柄,这几个信息可以通过VS自带的spy++查询。
[DllImport("user32.dll", EntryPoint ="GetDesktopWindow", CharSet =
CharSet.Auto, SetLastError =
true)]
static extern IntPtr GetDesktopWindow();
返回桌面窗口句柄,被我用来当前一个函数的父句柄。
[DllImport("user32.dll")]
public extern static int GetWindowText(IntPtr hWnd,StringBuilder lpString,
int nMaxCount);
获取指定窗口的标题,WinForm里的控件都是window,但在我们讨论的情况下window就只是窗口了。需要结合StringBuider类使用,不熟悉的可以去预习下。
[DllImport("User32.dll")]
public static extern int GetClassName(int hWnd,StringBuilder lpClassName,
int nMaxCount);
获取指定窗口的类名,也是用到StringBuider类。
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
将一个窗口显示到最前端。
[DllImport("user32.dll", CharSet =CharSet.Auto, ExactSpelling =
true)]
public static extern IntPtr GetForegroundWindow();
返回最前端窗口的句柄。
[DllImport("user32.dll", EntryPoint ="SendMessage")]
static extern int SendMessage(IntPtr hWnd,uint Msg,
int wParam,
int lParam);
模拟键盘或鼠标的输入,最常用的就是它了,后面具体介绍。
[DllImport("user32.dll", EntryPoint ="PostMessage")]
public static extern IntPtr PostMessage(IntPtr hwnd,uint msg,int wparam,
int lparam);
作用和上一个类似,不过SendMessage是等窗口处理完事件后返回,这个是发送消息后立即返回。顺便一提,原型本来是LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);类型改了一下,并不影响功能,只是方便写,其它函数也类似。
[DllImport("user32.dll")]
[return:
MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd,ref RECT lpRect);
返回一个表示指定窗口位置大小的结构,结构声明如下:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;//最左坐标
public int Top;//最上坐标
public int Right;//最右坐标
public int Bottom;//最下坐标
}
这里要说一下DirectUI的窗口一般都是用微软的api建立一个窗口,然后隐藏窗口,自己画一个窗口出来,所以窗口的实际大小要比看到的要大,拖动窗口时看到的那个虚线框才是真实的大小。
接下来是MSAA的相关函数:需要引用Accessibility,并using Accessibility;
[DllImport("Oleacc.dll")]
public static extern int AccessibleObjectFromWindow(
IntPtr hwnd,
int dwObjectID,
ref Guid refID,
ref IAccessible ppvObject);
通过句柄获取窗口对于的IAccessible对象,知道我为什么要先介绍句柄相关的函数了吧。用的时候需要包装一下:
private void GetAccessibleWindow(System.IntPtr imWindowHwnd,out IAccessible IACurrent)
{
Guid guidCOM =
new Guid(0x618736E0, 0x3C3D, 0x11CF, 0x81, 0xC, 0x0, 0xAA, 0x0, 0x38, 0x9B, 0x71);
AccessibleObjectFromWindow(imWindowHwnd, -4,
ref guidCOM, ref IACurrent);
}
[DllImport("Oleacc.dll")]
public static extern int AccessibleChildren(
Accessibility.IAccessible paccContainer,
int iChildStart,
int cChildren,
[Out]
object[] rgvarChildren,
out int pcObtained);
获取IAccessible对象的子对象数组,一般包装成下面的形式:
private IAccessible[] GetAccessibleChildren(IAccessible paccContainer)
{
IAccessible[] rgvarChildren =new IAccessible[paccContainer.accChildCount];
int pcObtained;
AccessibleChildren(paccContainer, 0, paccContainer.accChildCount, rgvarChildren,out pcObtained);
return rgvarChildren;
}
public IAccessible GetAccessibleChild(IAccessible paccContainer,int[] array)
{
if (!array.Length.Equals(0))
{
IAccessible[] children=GetAccessibleChildren(paccContainer);
IAccessible result = children[array[0]];
if (result.accChildCount == 0)
{
lb.Text += "error: parent:" + ((IAccessible)result.accParent).accChildCount +" role:" + result.accRole +
" state:" + result.accState;
return null;
}
int[] array_1 =
new int[array.Length - 1];
for (int i = 0; i < array.Length - 1; i++)
{
array_1[i] = array[i + 1];
}
return GetAccessibleChild(result, array_1);
}
else
{
return paccContainer;
}
}
按层级找到某个对象,array代表需要查找的层级,就是调用了前面的函数。
介绍几个IAccessible类的属性和函数:
accessible.accChildCount 子控件数
accessible.accValue; accessible.accName 控件数值和名字这两个属性vs提示是有的,不过不知为何运行时会报错,要换成下面两个函数才行,还有其它几个属性应该也是这样。
result.get_accValue(0);result.get_accName(0) VS对参数的提示是[object VarChild =Type.Missing],然而填Type.Missing报错,这参数好像表示子空间的序号,0表示查询本控件,嗯,填0就好。
Inspect.exe和AccExplorer32.exe都可以查询窗口的IAccessible层级结构和IAccessible控件的具体信息,第一个功能比较多,后一个有汉化版,大家自己取舍。
再介绍详细介绍一下SendMessage的用法。static extern int SendMessage(IntPtr hWnd,uint Msg,
int wParam,
int lParam);
Msg 代表操作类型,很好查的,常用的有:
//按下按钮
const int WM_KEYDOWN = 0x100;
//放开按钮
const int WM_KEYUP = 0x101;
//发送字符
const int WM_CHAR = 0x102;
//应用程序发送此消息来设置一个窗口的文本
const int WM_SETTEXT = 0x0C;
//当一个窗口或应用程序要关闭时发送一个信号
const int WM_CLOSE = 0x10;
//当用户选择结束对话框或程序自己调用ExitWindows函数
const int WM_QUERYENDSESSION = 0x11;
//用来结束程序运行,会关闭窗口所属的整个程序
const int WM_QUIT = 0x12;
//按下鼠标左键
const int WM_LBUTTONDOWN = 0x201;
//释放鼠标左键
const int WM_LBUTTONUP = 0x202;
//双击鼠标左键
const int WM_LBUTTONDBLCLK = 0x203;
//使用鼠标滚轮
const int WM_MOUSEWHEEL = 0x020A;
wParam和lParam是32位数。
按钮事件,wParam代表键值,具体可以查;lParam代表点击的点击次数、组合键等信息,msdn上有张表介绍各个位的作用,不过我没用过。
发送字符,每次发送一个char,wParam代表char的值转换成int类型就行,lParam为0。
对于鼠标点击事件,wParam代表组合键,没有的话为0;lParam代表点击的位置,低字为x坐标,高字为y坐标,即x+(y<<16),这个坐标是相对于屏幕而言的。
对于鼠标滚轮事件,wParam高字代表滚动距离,向上为正,向下为负,低字代表组合键,没有的话为0;lParam代表点击的位置,低字为x坐标,高字为y坐标,即x+(y<<16),这个坐标是相对于窗口而言的。
由于只有窗口句柄,使用滚轮事件,发送字符和按钮事件时,需要获取相应区域的焦点,我是用鼠标点击事件做的。
向DirectUI发送文本这件事困扰了我好久,试过SetText事件,没有作用,只能将文本拆成char数组发送字符。但发送中文就会乱码,SendMessage发送中文用的是GBK编码,String是Unicode编码,需要进行转换,相应的函数是有的,要先把String转换成Byte数组再转换成Char数组,解码器有好几种,试了好久发现下面这种组合是可以的。
using System.Text;
char[] charsC =
UnicodeEncoding.Unicode.GetChars(UnicodeEncoding.GetEncoding("GBK").GetBytes(strText));
高兴了好久发现,这样字母和数字乱码了。所以把正常的Unicode字符放入另一个数组,两个混着用。
char[] chars = strText.ToCharArray();
英文字符是用八位表示的,中文字符用16位,c#里char是16位的,在charsC里,相邻2个英文字符被放到了一个char里。所以,两个数组的长度是不一样的。GBK编码的中文字符从0x8140到0xfea0,键盘上的英文字符最大为0x7e,用0x7e7e作为分界区分中文和英文字符。啊,这样中英文都能发了。
不过后来的测试中发现有些汉字这样发送也会乱码,于是我决定自己把Byte数组转换成Char数组,以0x7e为界,大于的就属于中文字符,和下一个字节一切放到一个Char里,否则就是英文字符,直接转换成Char类型。
最后我在测试的时候发现直接把Byte数组发过去就行了。。以下是最终的函数:
private void sendText(IntPtr hwnd,String strText)
{
byte[] charCByte =
UnicodeEncoding.GetEncoding("GBK").GetBytes(strText);
for (int i = 0; i < charCByte.Length; i++)
{
SendMessage(hwnd, WM_CHAR, (int)charCByte[i], 0);
}
}
本文由本人查询资料整理加工而得,既为方便自己查阅,也为方便他人搜索。水平有限,如有错漏,希望大神能指点,既是帮我,也是帮了其他看贴的同志。
wpf C# 操作DirectUI窗口 SendMessage+MSAA的更多相关文章
- 【.net 深呼吸】WPF 中的父子窗口
与 WinForm 不同,WPF 并没有 MDI 窗口,但 WPF 的窗口之间是可以存在“父子”关系的. 我们会发现,Window 类公开了一个属性叫 Owner,这个属性是可读可写的,从名字上我们也 ...
- WPF通过附加属性控制窗口关闭
场景1 当使用 ShowDialog() 方式显示窗口时,通过定义附加属性的方式可实现在 ViewModel 中进行数据绑定(bool?)来控制子窗口的显示和关闭 public class ExWin ...
- WPF:拖动父窗口行为
原文 WPF:拖动父窗口行为 这次只是一个快速的帖子:当我点击并拖动特定的UIElement时,我需要能够重新定位WPF窗口.目的是重新创建在标准Windows标题栏上单击和拖动的行为(在我的情况下, ...
- WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性)
原文:WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性) 本文介绍如何使用 Windows 的 AppBar 相关 API 实现固定停靠在桌面上的特殊窗 ...
- 如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI
原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI 由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被 ...
- 转-JS子窗口创建父窗口操作父窗口
Javascript弹出子窗口 可以通过多种方式实现,下面介绍几种方法 (1) 通过window对象的open()方法,open()方法将会产生一个新的window窗口对象 其用法为: window ...
- [WPF疑难] 继承自定义窗口
原文 [WPF疑难] 继承自定义窗口 [WPF疑难] 继承自定义窗口 周银辉 项目中有不少的弹出窗口,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于Window自身的,但每个弹出框 ...
- [WPF疑难]如何禁用窗口上的关闭按钮
原文 [WPF疑难]如何禁用窗口上的关闭按钮 [WPF疑难]如何禁用窗口上的关闭按钮 周银辉 哈哈,主要是调用Rem ...
- 项目总结03:window.open()方法用于子窗口数据回调至父窗口,即子窗口操作父窗口
window.open()方法用于子窗口数据回调至父窗口,即子窗口操作父窗口 项目中经常遇到一个业务逻辑:在A窗口中打开B窗口,在B窗口中操作完以后关闭B窗口,同时自动刷新A窗口(或局部更新A窗口)( ...
随机推荐
- window下利用navicat访问Linux下的mariadb数据库
1.再Linux上成功安装mariadb数据库后,不管是在dos(敲命令mysql -h192.168.136.8 -uroot -p)下或者是navicat(创建连接)下连接mariadb数据库,会 ...
- Uploadify404无效链接
Uploadify404无效链接 在使用Jquery Uploadify插件的時候.会发如今请求中有个返回值为404的请求. 假如如今的location为www.aa.com/bugs/more. h ...
- [RxJS] Split an RxJS observable with window
Mapping the values of an observable to many inner observables is not the only way to create a higher ...
- Java设计模式之从[暗黑破坏神存档点]分析备忘录(Memento)模式
在大部分游戏中,都有一个"存档点"的概念.比如,在挑战boss前,游戏会在某个地方存档,假设玩家挑战boss失败,则会从这个存档点開始又一次游戏.因此,我们能够将这个"存 ...
- cmake使用总结(转)---工程主目录CMakeList文件编写
在linux 下进行开发很多人选择编写makefile 文件进行项目环境搭建,而makefile 文件依赖关系复杂,工作量很大,搞的人头很大.采用自动化的项目构建工具cmake 可以将程序员从复杂的m ...
- 【u016】无序字母对
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个 ...
- 切换-5.7-GTID复制切换成传统复制
mysql5.7 gtid和传统复制在线切换,5.7.6 之后 不用重启可以直接在线切换 基本环境 Master Slave MySQL版本 MySQL-5.7.16-X86_64 MySQL ...
- php课程 6-22 字符串格式化函数有哪些(精问)
php课程 6-22 字符串格式化函数有哪些(精问) 一.总结 一句话总结: 1.猜测一下$_GET()怎么来的? 函数赋值给变量的操作:$_YZM=get(); 这样就可以很好的解释哪些全局变量 ...
- 【a501】算24点
Time Limit: 1 second Memory Limit: 32 MB [问题描述] 几十年前全世界就浒一种数字游戏,至今仍有人乐此不疲.在中国我们把这种游戏称为"算24点&quo ...
- 数组类型转换失败:NSMutableArray和NSArray的相互转换
1.数组类型转换无效(错误) @property(strong, nonatomic) NSMutableArray *temp_Array; _temp_Array=(NSMutableArray ...