Active Accessibility(1)

来源:互联网 发布:fast迅捷网络手机软件 编辑:程序博客网 时间:2024/05/01 17:02

 

 下载源代码

Microsoft© Active Accessibility 2.0 is a COM-based technology that improves the

way accessibility aids work with applications running on Microsoft Windows?. It

provides dynamic-link libraries that are incorporated into the operating system

as well as a COM interface and application programming elements that provide

reliable methods for exposing information about user interface elements.     

基础
    Microsoft
© Active Accessibility 是一种相对较新的技术(1.0版在19975月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
    Active Accessibility
的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
   
每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。

 Active Accessibility 原理
    Active Accessibility?
的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
   
当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
   
内在支持 IAccessible UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
   
如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
    Active Accessibility
名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。

 
如何得到 IAccessible 接口指针
   
每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
   
有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPointAccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChildget_accParent
    IAccessible
接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
    Active Accessibility SDK
提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT EVENT_SYSTEM 开始。
   
好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
   
因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
下面结合代码介绍一下它的用法。
我们来得到下面运行窗口的 IAccessible 接口指针。


图一

HWND hWndMainWindow;

IAccessible *paccMainWindow = NULL;

HRESULT hr;

//得到标题为"运行"的窗口的句柄

if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))

{

               MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);

}

else

{

               //通过窗口句柄得到窗口的 IAccessible 接口指针。

               if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,

                                                           OBJID_WINDOW,

                                                           IID_IAccessible,

                                                           (void**)&paccMainWindow)))

               {

                               //……我们可以通过这个指针paccMainWindow进行操作。

                               paccMainWindow->Release();

        }

}   

现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
   
首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
   
然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:

图二

我们现在主要关注的信息是:NameRoleWindow className

Name = "打开(O):"

Role = "可编辑文字"

Window className = "Edit" 

    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows --以唯一的来表示这个元素。
    FindChild
函数显示了一个基于 Active Accessibility /子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/ID对(见附录)。
下面我们开始取文本输入框的 IAccessible 接口指针。

IAccessible*           paccControl = NULL;//输入框的 IAccessible 接口

VARIANT                 varControl;                          //ID

 

FindChild( paccMainWindow,

           "打开(O):",

           "可编辑文字",

           "Edit",

           &paccControl,

           &varControl )   

第一个参数是先前得到的窗口 IAccessible 接口指针。
第二、三、四个参数分别是名字、角色、类。
2个为返回参数包含了 IAccessible 接口/ID对。下面是FindChild的实现。

BOOL FindChild (IAccessible* paccParent,

                         LPSTR szName, LPSTR szRole,

                         LPSTR szClass,

                         IAccessible** paccChild,

                         VARIANT* pvarChild)

{

               HRESULT hr;

               long numChildren;

               unsigned long numFetched;

               VARIANT varChild;

               int index;

               IAccessible* pCAcc = NULL;

               IEnumVARIANT* pEnum = NULL;

               IDispatch* pDisp = NULL;

               BOOL found = false;

               char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];

              

                               //得到父亲支持的IEnumVARIANT接口

               hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);

              

               if(pEnum)

                               pEnum -> Reset();

              

//取得父亲拥有的可访问的子的数目

               paccParent -> get_accChildCount(&numChildren);

              

//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。

               for(index = 1; index <= numChildren && !found; index++)

               {

                               pCAcc = NULL;                      

                               // 如果支持IEnumVARIANT接口,得到下一个子ID

//以及其对应的 IDispatch 接口

                               if (pEnum)

                                              hr = pEnum -> Next(1, &varChild, &numFetched);              

                               else

               {

                               //如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号

                                              varChild.vt = VT_I4;

                                              varChild.lVal = index;

                               }

                              

                               // 找到此子ID对应的 IDispatch 接口

                               if (varChild.vt == VT_I4)

                               {

                                              //通过子ID序号得到对应的 IDispatch 接口

                                              pDisp = NULL;

                                              hr = paccParent -> get_accChild(varChild, &pDisp);

                               }

                               else

                                              //如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口

                                              pDisp = varChild.pdispVal;

                              

                               // 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc

                               if (pDisp)

                               {

                                              hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);

                                              hr = pDisp->Release();

                               }

                              

                               // Get information about the child

                               if(pCAcc)

                               {

                                              //如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF

                                              VariantInit(&varChild);

                                              varChild.vt = VT_I4;

                                              varChild.lVal = CHILDID_SELF;

                                             

                                              *paccChild = pCAcc;

                               }

                               else

                                              //如果子不支持IAccessible 接口

                                              *paccChild = paccParent;

                              

                               //跳过了有不可访问状态的元素

                               GetObjectState(*paccChild,

                                              &varChild,

                                              szObjState,

                                              sizeof(szObjState));

                               if(NULL != strstr(szObjState, "unavailable"))

                               {

                                              if(pCAcc)

                                                             pCAcc->Release();

                                              continue;

                               }

                               //通过get_accName得到Name

                               GetObjectName(*paccChild, &varChild, szObjName, sizeof(szObjName));

                               //通过get_accRole得到Role

                               GetObjectRole(*paccChild, &varChild, szObjRole, sizeof(szObjRole));

                               //通过WindowFromAccessibleObjectGetClassName得到Class

                               GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));

                               //以上实现代码比较简单,大家自己看代码吧。

 

                                              //如果这些参数与输入相符或输入为NULL

                               if ((!szName ||

                                    !strcmp(szName, szObjName)) &&

                                    (!szRole ||

                                     !strcmp(szRole, szObjRole)) &&

                                    (!szClass ||

                                     !strcmp(szClass, szObjClass)))

                               {

                                              found = true;

                                              *pvarChild = varChild;

                                              break;

                               }

                               if(!found && pCAcc)

                               {

                                              // 以这次得到的子接口为父递归调用

                                              found = FindChild(pCAcc,

                                                                szName,

                                                                szRole,

                                                                szClass,

                                                                paccChild,

                                                                pvarChild);

                                              if(*paccChild != pCAcc)

                                                             pCAcc->Release();

                               }

               }//End for

              

               // Clean up

               if(pEnum)

                               pEnum -> Release();

              

               return found;

}

 

// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,

//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。

//将这些或数转换成相应的用逗号分割的状态字符串。

UINT GetObjectState(IAccessible* pacc,

                    VARIANT* pvarChild,

                    LPTSTR lpszState,

                    UINT cchState)

{

    HRESULT hr;

    VARIANT varRetVal;

              

    *lpszState = 0;

              

    VariantInit(&varRetVal);

              

    hr = pacc->get_accState(*pvarChild, &varRetVal);

              

               if (!SUCCEEDED(hr))

        return(0);

              

               DWORD dwStateBit;

               int cChars = 0;

    if (varRetVal.vt == VT_I4)

               {

                               // 根据返回的状态值生成以逗号连接的字符串。

        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;

               dwStateBit < STATE_SYSTEM_ALERT_HIGH;

               dwStateBit <<= 1)

        {

            if (varRetVal.lVal & dwStateBit)

            {

                cChars += GetStateText(dwStateBit,

                                       lpszState + cChars,

                                       cchState - cChars);

                                                             *(lpszState + cChars++) = '','';

            }

        }

                               if(cChars > 1)

                                              *(lpszState + cChars - 1) = ''/0'';

    }

    else if (varRetVal.vt == VT_BSTR)

    {

        WideCharToMultiByte(CP_ACP,

                            0,

                            varRetVal.bstrVal,

                            -1,

                            lpszState,

                            cchState,

                            NULL,

                            NULL);

    }

              

    VariantClear(&varRetVal);

              

    return(lstrlen(lpszState));

}

好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)

原创粉丝点击