Active Accessibility(2)

 
 
IAccessible 接口上执行动作
   
有了表示一个可访问的 UI 元素的 IAccessible 接口/ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
   
最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction
   
如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。

//在文本输入框输入"regedit"

if(1 == FindChild (paccMainWindow, "打开(O):",

                   "可编辑文字",

                   "Edit",

                   &paccControl,

                   &varControl))

{

               //在这里修改文本编辑框的值

               hr = paccControl->put_accValue(varControl,

                                                 CComBSTR("regedit"));

               paccControl->Release();

               VariantClear(&varControl);

}

                              

// 找到确定按钮,并执行默认动作。

if(1 == FindChild (paccMainWindow,

                   "确定",

                   "按下按钮",

                   "Button",

                   &paccControl,

                   &varControl))

{

               //这里执行按钮的默认动作,即"按下这个按钮"

               hr = paccControl->accDoDefaultAction(varControl);

               paccControl->Release();

               VariantClear(&varControl);

}

现在,你会发现已经成功启动了注册表编辑器!!

 
模拟键盘和鼠标输入
   
让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
   
使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
    SendInput
接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
下面的代码将模拟 ALT+F4 按键来关闭窗口。

INPUT input[4];     

memset(input, 0, sizeof(input));

 

//设置模拟键盘输入

input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;

input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;

input[1].ki.wVk  = input[3].ki.wVk = VK_F4;

 

// 释放按键,这非常重要

input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;

 

SendInput(4, input, sizeof(INPUT));

具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)

 
监视WinEvents
   
监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
   
监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
   
好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:

if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))

{

hEventHook = SetWinEventHook(

               EVENT_MIN,            // eventMin ID

               EVENT_MAX,           // eventMax ID

               NULL,                      // always NULL for outprocess hook

               WinCreateNotifyProc,                            // call back function

               0,                                                          // idProcess

               0,                                                          // idThread

         // always the same for outproc hook

               WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);

}   

第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
下面是回调函数:

void CALLBACK WinCreateNotifyProc(

 HWINEVENTHOOK  hEvent,

 DWORD   event,

 HWND    hwndMsg,

 LONG    idObject,

 LONG    idChild,

 DWORD   idThread,

 DWORD   dwmsEventTime

 )

{

              

               if( event != EVENT_OBJECT_CREATE)

                               return;

              

               char bufferName[256];

               IAccessible *pacc=NULL;

               VARIANT varChild;

    VariantInit(&varChild);

               //得到触发事件的 UI 元素的 IAccessible 接口/ID

               HRESULT hr= AccessibleObjectFromEvent(hwndMsg,

                                                     idObject,

                                                     idChild,

                                                     &pacc,

                                                     &varChild);

              

               if(!SUCCEEDED(hr))

               {

                               VariantClear(&varChild);

                               return;

               }

               //得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。

               GetObjectName(pacc, &varChild, bufferName, sizeof(bufferName));

               if(strstr(bufferName, szMainTitle))

                               PostThreadMessage(GetCurrentThreadId(),

                                                 WM_TARGET_WINDOW_FOUND,

                                                 0,

                                                 0);

              

               return;

}   

…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。

 
附录:

关于IAccessible 接口/ID对:
   
让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
   
第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID1,第二个子控件的ID2,依次增长!
   
这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
   
记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF
   
呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!

 
注:
   
我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
   
还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。

 
参考资料:

  • 1 Dmitri Klementiev写的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》及其源程序。http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx
  • 2 MSDN中的相关章节。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值