QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5372|回复: 1
打印 上一主题 下一主题

MS Active Accessibility 接口技术编程尝试

[复制链接]
字体大小: 正常 放大
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 15:21 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>+ C  P! `$ ^& b+ \1 y
" b+ C, p1 |4 z
<TR>
) Z* f1 T0 f) J" n8 [5 }  _. t$ p<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>4 K  _0 n7 I$ \7 V1 q0 X
原 作 者:崔传凯' t  X6 q) j% Z* g) s: F$ V- G
原 出 处:vckbase
( n5 c# b) ^7 @0 V( y1 [6 d发 布 者:loose_went! o# I' A+ ~' z3 S( E
发布类型:转载
$ c; C3 ^& w: t) B3 ~! p; I2 P' I( f发布日期:2004-11-12( H& D4 p" A* r: u
今日浏览:6& `" d/ U3 w' `, [& q* B+ W% z
总 浏 览:234& f+ S% a  ~& E2 e8 t
</TD>' t$ \4 f+ K3 P3 q
<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE>( A0 l3 N4 X- L. H7 l+ E- C2 w
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
* u8 g% C8 m) p4 z2 C" Z
; a+ B5 r2 C2 n% q4 B<TR>, v8 N' Z2 r1 Y- {, U
<TD vAlign=top align=left bgColor=#ffffff>* S7 R( ?" G0 R4 Z, n
8 U- t8 i5 T/ T0 i4 p# S" M
<>" c# _6 [/ Q/ O$ ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 h4 q$ M% ]7 n
9 `; a7 u" S: Q6 E  v! u+ J<TR>
9 A9 Q- O: r- z/ O5 a. `0 ^<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 2 l) r. A% b; v0 u
way accessibility aids work with applications running on Microsoft Windows?. It
8 U' {- k$ s* ?provides dynamic-link libraries that are incorporated into the operating system
& C* F$ H* X8 G* F5 Qas well as a COM interface and application programming elements that provide
+ |: c& X' Z8 l0 ^reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>7 s6 J0 W/ T8 c" z
<>一、基础& x( p% j! ?7 O+ g
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。  Z/ S) }) L- ]) f6 @, d
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
" F/ F- |. X) T# S. d9 k1 p! r    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P># `+ z2 i" v# `( q1 T# U) r6 J0 q, {
<>二、Active Accessibility 原理. B/ I5 T3 i& `+ f1 ?
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
0 }4 r6 u% j5 \4 o  v6 R    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。! u- j0 C7 ?* Z
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。  z. [" h4 `, p3 Q/ {: h/ R: d
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
$ a4 t' K7 ^" t3 Q7 x) Q2 [4 j    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>. v2 S( N* c, a2 a' A$ Y
<>三、如何得到 IAccessible 接口指针. K2 G' \% v$ i! U5 E$ }* ]% P7 r
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
* S0 X1 J& v7 p    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。8 R/ N9 T4 W% `1 T) j2 n
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
5 _. Q9 U! }3 ?2 s8 R0 B    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。# ^+ a6 G9 p+ [2 w6 C4 j
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。$ c3 A) v* ]" l- A% M" Q% j
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
- w0 J9 A* m  h3 {4 b  i: r下面结合代码介绍一下它的用法。
; \8 Z2 H& C; z+ s1 d2 a; }我们来得到下面运行窗口的 IAccessible 接口指针。
6 H8 d  X/ J* e; P2 e9 r1 k3 u& ?
$ r% V( k. g6 ~0 `$ F* R; ]/ c  B8 ]<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>0 q4 O& |4 x: _
<>图一</P>
8 \6 o6 I' @# ]9 P3 S<>! Y" w! I( K/ C/ ^) K- F
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0># Y6 Q4 m/ G  ~3 ^+ R& e& a  W

( c+ {6 s, S; T0 `; f8 S<TR>) {  }. G" s  m/ Q: U  F7 o
<TD><FONT color=#00309c>HWND hWndMainWindow;
* a! T3 p" D$ R/ @8 DIAccessible *paccMainWindow = NULL;6 Q$ l, T; m( A* [
HRESULT hr;
. ^, e' V/ a7 A/ G/ k//得到标题为"运行"的窗口的句柄
! K; I' @; n# R9 q: H/ Y0 _if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
# G  f! J3 W+ M  N+ A' E{
  N5 q  W, [9 n0 c/ C+ z: d0 pMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);3 l! i2 M. f! E6 u- b- x( p6 ^
}
$ T6 |- b7 U) Y9 kelse% o. D- C9 M; b; v6 m/ d% E/ o
{
) N! ~1 H3 U, \$ y3 q; h& [: ^//通过窗口句柄得到窗口的 IAccessible 接口指针。; v! c( Q0 R0 }2 K# P6 I/ C
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
& ^! M) U4 ]! Z9 h* q3 N                                            OBJID_WINDOW, 9 `- K( Y- |  Z/ t% J) |
                                            IID_IAccessible,
( [* ~7 ]. A6 f. C                                            (void**)&amp;paccMainWindow))). r) X7 P3 b$ N9 L! ]# V
{$ A% }0 L. y0 z/ @( _
//……我们可以通过这个指针paccMainWindow进行操作。2 A  c" P, K# z7 l8 {) K2 Z9 b; r
paccMainWindow-&gt;Release();0 w! J3 P% `  J" O
        }
: |+ K0 x( t/ A0 U" o}    </FONT></TD></TR></TABLE></P>2 z4 C* `6 b# c3 y' I. C
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
2 Z9 u. O! D. L( ~, g- @2 W    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
. e. i3 n4 J( m! i+ A    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
% q6 H- `2 C9 I# ^+ i<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
; A+ Z8 x5 y% j$ z/ U6 A<>图二</P>- J' a* l1 w4 J3 L5 b2 S7 h" L* v
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
! z- A6 ]6 X* k0 u% `<># C/ x4 D$ ^+ R$ G$ {
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; ~" K; Z; j* r% m
8 z) F/ m$ H# n2 E  i3 W- f+ u) ?
<TR>5 X& }# s' z4 C
<TD><FONT color=#00309c>Name = "打开(O):"* M( x8 k& j4 s% R/ p5 T
Role = "可编辑文字"
% C: o$ U8 T, q' ?Window className = "Edit"  </FONT></TD></TR></TABLE></P>
$ c* Z) X8 t+ e2 L) M  }/ M" R3 T<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
- a. Y: z6 b0 \; ?/ a$ {7 ^/ J    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。' z) m$ L; P$ L
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>- T7 Q6 g9 }2 r. `: o4 P" C6 t4 n
<>* Z" V2 e- e. ]0 b5 p
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>: z5 I) _( ~/ G6 f

% i/ [5 l+ R) U1 n1 ^( Y<TR>. B* f. {4 P/ d
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口) J; J; [" i2 ~+ ~
VARIANT varControl;    //子ID。7 y4 C8 p! {1 e$ x3 e' U
- j+ j5 O2 H0 M$ v, w0 c
FindChild( paccMainWindow,
# r( }- b5 K( c% y           "打开(O):",
& K6 M5 n! U7 n9 ]5 M$ v/ ~% j           "可编辑文字", . H& i: A  f& m
           "Edit",
$ S3 R- p; Y' _1 Y8 r) L/ g           &amp;paccControl, ! S$ t0 B, I4 I
           &amp;varControl ) </FONT></TD></TR></TABLE></P>0 h+ k/ g' C* I1 O0 B0 n, K  g
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
/ E8 O$ ^& Z$ K' y: j4 k第二、三、四个参数分别是名字、角色、类。2 K/ y& ~  ?9 E5 V; c
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>& [) Q5 M6 o( w
<>$ q& n) E- [7 o" f: v- X
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
0 _8 r; ~! m/ z/ K, i0 b( L. u" @
2 D1 g" v, ?: a" ]8 `4 c0 F5 A<TR>
  X" d5 ?( K# {8 p2 }8 E<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
- e7 _( d. o9 x9 l                         LPSTR szName, LPSTR szRole, . M) n& G; c8 l3 y, H* Y# `, [
                         LPSTR szClass,
0 {; X! H2 Q1 H1 h; d; O                         IAccessible** paccChild, , g4 ^9 ~+ r  R( }# k0 u! z
                         VARIANT* pvarChild)5 p/ v- B' e2 O
{
( G4 P, n2 Q; I4 bHRESULT hr;
4 Y7 P5 Q. r/ U' ylong numChildren;
$ R  k- f! Q2 A8 S4 L" sunsigned long numFetched;
- B' K5 U! ~0 D/ O/ tVARIANT varChild;
( r4 G: ~+ a$ X' Eint index;9 t! t( {3 k( c! O6 \7 ^
IAccessible* pCAcc = NULL;* G! C6 g9 X5 g" K$ G- i6 T/ f
IEnumVARIANT* pEnum = NULL;  c% h: W& X8 c1 n% B
IDispatch* pDisp = NULL;8 X: P3 R8 ]3 ]/ w1 y4 g- s
BOOL found = false;. q7 x8 Z# l) X. A, i
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];# T  \! s- n2 r- {$ y

# q/ s1 c8 }8 H# G//得到父亲支持的IEnumVARIANT接口
2 q) @+ o6 i8 J1 y4 k; J  [- r3 _$ Phr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);2 f& d+ f  A) m

  b2 c& H* `6 z) h0 V4 iif(pEnum)
+ M3 n' \$ W7 s* VpEnum -&gt; Reset();+ u1 i, a$ I9 Y; [/ y8 O) u
/ ?. }% {0 `2 S) t5 X" o
//取得父亲拥有的可访问的子的数目
# u6 C7 i: o( t% \6 x" PpaccParent -&gt; get_accChildCount(&amp;numChildren);
" k! y- M* c" ?0 f8 d
9 f) D4 a5 o" I//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
# J) e) F0 w1 q+ ^for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)- }8 g$ w0 s$ C
{, z3 m$ g1 z9 u& j% A7 h" l
pCAcc = NULL;
8 f; ]* G( T+ l) X// 如果支持IEnumVARIANT接口,得到下一个子ID
. {$ Z& [4 E4 s" a" l# i0 g; ?//以及其对应的 IDispatch 接口
8 C. ^7 b  f. M' ?; ?if (pEnum)
# `8 X- b4 Z2 q7 }. Ehr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); ' G' T% Q9 A3 Z* t% m" u, @6 D
else
; X6 _3 ?$ `2 O; J( x) X( ?{
; i6 s6 ?& I: @' g8 E" t$ [6 M//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号( Y8 _' V2 b+ L# ]$ k" V
varChild.vt = VT_I4;5 S" ~: S5 Q* w/ L6 B. e+ d8 L- A2 @
varChild.lVal = index;
4 C. G2 I1 y6 B1 \& C4 _' X) @, h}/ S- U7 J* @  ^2 x' G( ]9 g

4 o/ Z* t+ J/ H2 ]5 ^// 找到此子ID对应的 IDispatch 接口
* \' w/ Z6 b1 M0 Yif (varChild.vt == VT_I4)5 u4 i4 D  l# w: s7 g# b) n
{# n: K/ Z" U3 K; k: r/ w+ K. |  P7 X
//通过子ID序号得到对应的 IDispatch 接口
6 ]2 i  K+ n7 L; j- ]6 mpDisp = NULL;' J! M) A& o" f- _$ X4 q& Y# V
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);, u- y" h/ E# r$ z6 T0 J9 ^" f
}: l1 _6 t6 Y: @! s
else
# e) s% ]* u; w7 U9 T1 N# S3 d//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口: Y4 {' ]0 d( }# a" ^: `9 K
pDisp = varChild.pdispVal;3 m* @' i% c6 @8 X7 Y: K

! j$ f  i( d2 ~0 Q% k// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc8 b* j0 k9 T( G$ l5 s; B* O
if (pDisp)' |7 q) z' ^' s; Y
{3 }6 u6 K1 {) J, ?
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);( K3 q% l0 g9 w5 S1 T5 X
hr = pDisp-&gt;Release();
5 s8 V: a+ W6 j4 c% s- ?! K- L9 q}
) I7 e4 y6 e- K* P8 a. R, _
# e: a. a. B& i+ M8 C// Get information about the child
7 }8 F+ f2 F- Iif(pCAcc)
: z1 w5 T& |( f$ r) _$ [/ p{' s7 T- V/ C6 z
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
- f) ^7 J6 j5 K, t: Z6 }VariantInit(&amp;varChild);
9 {# m( Q, A4 F* |varChild.vt = VT_I4;
. y9 c, }5 m5 a* j! rvarChild.lVal = CHILDID_SELF;
& `7 W# K( E- V# r$ z7 D6 m7 A, g) b' m
# W" k; F; ~8 t; I- [*paccChild = pCAcc;4 i: N6 T0 G  `- R4 P: a; T6 |
}; q8 F$ H5 a6 w. e0 |. S6 Z3 Z
else1 I- S: c$ N+ f& L0 P. j
//如果子不支持IAccessible 接口9 c1 }6 r+ L6 I( G" T
*paccChild = paccParent;% B; J1 f% G4 [8 x* j

% n' t' }- L$ u+ G) T//跳过了有不可访问状态的元素6 |  {! V$ D6 j* [/ b" ^' V$ [
GetObjectState(*paccChild,
7 T5 L6 u( H( G5 V               &amp;varChild, $ d" C2 T+ K$ N( c
               szObjState,
/ l3 R; i; f+ ?8 R, O" p4 r# E               sizeof(szObjState));
7 F3 [, @0 x' S4 r4 wif(NULL != strstr(szObjState, "unavailable"))
' h; F% K- B5 M- H5 f2 e4 W) V" K$ \{
/ N2 g( w) V/ b: R2 T5 ~( gif(pCAcc)4 `0 B' J4 t# F
pCAcc-&gt;Release();
7 T' v4 U- _7 `  x8 qcontinue;
+ T0 k- ]$ d  D! V  a6 Z( F( X  l}* [6 z) i6 R6 p- ]/ U8 q+ e
//通过get_accName得到Name0 j. q) @# S  d/ G/ j, Y) }" H* v
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));; E$ W" q1 w( ]* L" p
//通过get_accRole得到Role
+ K! E" [* D3 f; w$ YGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));3 o: h0 N& _8 v7 V8 \0 ~1 A
//通过WindowFromAccessibleObject和GetClassName得到Class1 H4 T9 e: y% B. J  r1 G9 _9 v
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
7 o3 _* N2 Z; y( ~1 F7 i2 [+ m. p: z//以上实现代码比较简单,大家自己看代码吧。! G( r+ K: I% S, k+ K% Z+ s

, O) _- C( u4 p. d, r8 w//如果这些参数与输入相符或输入为NULL
. v1 R1 S) H6 R0 Lif ((!szName || % K! r, H# D( K6 f& o
     !strcmp(szName, szObjName)) &amp;&amp;
; u0 g& P' [& W, o% h, h3 M     (!szRole ||
4 f. ]1 p* j/ I" ^. M1 G      !strcmp(szRole, szObjRole)) &amp;&amp;
; b  ^! X5 F- _, l, x/ G. d     (!szClass || 3 C2 N8 R: ^3 T: h$ r- u
      !strcmp(szClass, szObjClass)))
  O# I' F& M. u{7 N# [1 w) a- r: }; V# k
found = true;; v/ U8 @9 M! K3 Y& L3 [2 m
*pvarChild = varChild;0 y7 L' e: h6 |/ f5 b% R
break;
- j1 n/ Y# m! }" C2 A3 \/ v5 q}) D% h' W8 V6 z6 o1 r8 G! s8 m
if(!found &amp;&amp; pCAcc)
. ^! C3 b- d, t; l7 X2 d{4 D5 c7 }$ z3 Z" |
// 以这次得到的子接口为父递归调用
; n9 G& l/ Q9 ?# v. N: Dfound = FindChild(pCAcc,
! o1 ?  A6 C3 a$ C* _+ p8 D                  szName,
" s; t( b8 z% S  l9 W7 Y" D                  szRole,
: l; I6 F2 r3 v6 T* {                  szClass,
& O$ {( _' A9 {+ b. H; H+ W+ e                  paccChild, $ W% R" Z+ {; @6 q2 h
                  pvarChild);. {; s  y0 F! m: P0 e
if(*paccChild != pCAcc)
6 a% g- y/ O5 N6 jpCAcc-&gt;Release();
; z5 y, G1 L' C0 Q}
2 ^6 f$ p' i6 [5 f0 y  V7 O- o+ C}//End for
# W1 G$ W$ k4 E/ N. `* {+ x( ^0 e1 r. R5 {- m. ^- F4 Y
// Clean up
) ~! z$ ]% x6 e4 m# e8 Tif(pEnum)
8 z+ v$ T, v7 {( I1 c2 X% ApEnum -&gt; Release();+ d' ]+ V4 `& i6 L+ q# L
" d, h4 O7 _# S8 A
return found;( M$ Y! {# B( D" Y( n. ~: V
}
: k2 _& I( b& u
& h7 b) ?/ _+ g; J$ V, s// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
0 D8 B: q5 t' d) O+ ?& g//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。; W( b- X) g9 y: P
//将这些或数转换成相应的用逗号分割的状态字符串。
! \7 m5 U2 Y5 G4 O" n( ^8 z. QUINT GetObjectState(IAccessible* pacc,
7 w0 S6 z; N  H                    VARIANT* pvarChild,
, }& R% G$ Y7 i/ v# p                    LPTSTR lpszState, ( z. q2 Z' q% P
                    UINT cchState)
/ {. Q, f* G. p+ ]{( j- F% ?: y- `) A
    HRESULT hr;) r; q  f- a, E5 y# _
    VARIANT varRetVal;
. [$ G2 i8 p$ N+ d% ^) \1 y! L3 j( [4 Q9 |$ {2 t6 m
    *lpszState = 0;
/ E( v+ z& b& i) w/ n  i6 @# g
) T4 p+ u3 I1 C, V7 w% C6 m    VariantInit(&amp;varRetVal);7 |. S* [! K! g! K0 P
8 [/ \0 x+ F8 ]4 c
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);1 p; `' z* |9 K) {2 u: y) ~

8 D! D0 V% o* E" L9 R/ q6 jif (!SUCCEEDED(hr))# n% N" U" \4 }
        return(0);. h' S$ f. K- K" t4 M
+ [/ L- F  n5 i
DWORD dwStateBit;/ O' w1 r, S! o# x( f7 ^
int cChars = 0;0 o  A: ~+ w  [2 V/ M- u
    if (varRetVal.vt == VT_I4)$ Q; |& ]  O, V$ H
{
) g( h# n$ r& }// 根据返回的状态值生成以逗号连接的字符串。6 _( g$ V* D# ~$ `. e7 E
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; % Q, `9 I0 A4 v+ b/ o# s
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
( k' T. g* U, H! S  ^               dwStateBit &lt;&lt;= 1)  ?: F% ?0 ]# F. S7 V$ O1 U
        {% U; m8 l: r) H+ |! b( Y
            if (varRetVal.lVal &amp; dwStateBit)
  V9 {; C; b0 ]! \# k# }            {
; ^: \$ A- B; u                cChars += GetStateText(dwStateBit,
' `3 ?6 S$ {! m8 h7 ?( l                                       lpszState + cChars, ; h; b- B  Y8 c: R6 {# n
                                       cchState - cChars);! Y5 z: K  w8 ?
*(lpszState + cChars++) = '','';
  X8 N% P) O4 o4 x1 T, M: S3 \            }
, I6 c& Q; a1 i& C& c1 U        }4 I+ X5 {7 ]& ]- D) e
if(cChars &gt; 1)
- F4 F+ t. r& g/ R: a0 e*(lpszState + cChars - 1) = ''\0'';" a# N/ ?; i0 ^, ?
    }
& @9 Y$ I' E9 m% X9 z' @1 f    else if (varRetVal.vt == VT_BSTR), P9 `/ H" L8 _# i0 d4 N, o
    {- N, r6 ]6 [7 n4 \
        WideCharToMultiByte(CP_ACP,
' ?" S4 ~8 n: u/ R/ u                            0,
% f4 D5 p  s9 z9 f* U9 j# o                            varRetVal.bstrVal, ' q- j5 y3 n' H+ o% H9 n9 s& H/ _
                            -1, ' ]) D, z5 z( H- v* X% [4 {5 W) A1 v1 ?
                            lpszState,
7 Y4 r  H" [" Y) ]/ x$ d3 E" M! b                            cchState,
/ g5 m9 e: k0 Q% }) x- F                            NULL, " t  V# q. t' [" Y: B7 \
                            NULL);3 K8 ]/ `# B1 ~
    }
1 M& X! n8 `/ j& @" T0 C+ g
0 s/ [$ v! Z3 ^2 p+ L; U5 a    VariantClear(&amp;varRetVal);
' `$ |' w5 e4 y2 h, |( ~
$ {+ g! r$ M3 H+ \2 q4 q    return(lstrlen(lpszState));
; G) D. _) i" |' E}</FONT></TD></TR></TABLE></P>+ u+ t* M, t% E( w+ N
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)1 _8 v* ~9 s& j4 p( W+ K+ H

) m' `+ h% \# m1 n* T# L' e/ m四、在 IAccessible 接口上执行动作8 x, d3 e2 n# ?% h2 X9 J6 U9 B$ z
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
9 H8 t2 X) }# O8 `    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
; x# @# k& ?' f. `* D4 N! J    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>8 T( a* D( J3 J' k5 x
<>
& A6 A, L6 S8 R' V5 @3 K3 ]' r<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' q7 K4 y* D' m, {  K6 d& W3 I
+ N) M% y& j( p, ?
<TR>- t0 p9 d- j/ t; b3 V5 \
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
+ z" S$ ]8 Q  T. Q) \/ B( B6 Yif(1 == FindChild (paccMainWindow, "打开(O):",
3 c0 r4 [( ]; D  V8 Q1 L, \* ^! T                   "可编辑文字",
+ T. m5 D; g# f) b; I                   "Edit", ' ~, n+ n# E6 ^  t% s
                   &amp;paccControl,
  n4 |2 Y; Q. e2 [4 S" i. S  r- H                   &amp;varControl))
" T. q% ]  Z/ z% ]{1 L& T: F3 u/ K! d/ y( d' m
//在这里修改文本编辑框的值$ J/ W8 K) L. d! r
hr = paccControl-&gt;put_accValue(varControl,
- R3 p5 W4 d! N$ J. {. e& P: S                                  CComBSTR("regedit"));0 e: n9 D- a3 r: ?! H7 m" R$ y/ B
paccControl-&gt;Release();# j, i5 S# q+ R5 i1 T5 U
VariantClear(&amp;varControl);
  Z/ ?; c' W( m7 }}
+ W6 N7 Q$ W9 [; I  H, D
# |" O6 k% Q  F( F' _" s// 找到确定按钮,并执行默认动作。' O! c5 p7 B2 Y( \6 E# i# X
if(1 == FindChild (paccMainWindow,
' k/ t6 r' V3 x6 V                   "确定", * n: A; E* l$ u# u; a" J7 ?+ F( \
                   "按下按钮", * t& ^5 R% O+ Q8 [' t
                   "Button", " D; }) E+ S! z7 Z
                   &amp;paccControl, 1 `( ^: @4 z" I$ @
                   &amp;varControl))  D* z* e) |4 N) M* ~% U( w8 ~
{" n" z* t, R: t9 J
//这里执行按钮的默认动作,即"按下这个按钮"
7 l; D, t5 h8 U5 s9 k& Ohr = paccControl-&gt;accDoDefaultAction(varControl);# G* G3 z7 `# ^
paccControl-&gt;Release();
8 M% Q3 l9 |6 g" C5 c; hVariantClear(&amp;varControl);% \4 i% z" B( t0 O2 f
}</FONT></TD></TR></TABLE></P>
% w' @, {# f- ~<>现在,你会发现已经成功启动了注册表编辑器!!# V; L/ S2 X. N4 @# q# P
</P>- v7 u) _- T8 z+ R4 |4 T, C. \+ h
<>五、模拟键盘和鼠标输入
  U" |5 B2 {# ?/ H& Z    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。) L( O' o. J( R. @8 W5 T
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。# Q% L9 f- f# T' U( O
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。7 Q. ~6 B' \; A
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>. L* `7 d: F; q0 i) L' H
<>* L' X3 f$ p" `) z9 {0 ^
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
- S' U, ]% Z9 }7 V
1 y- m& B' s2 ~0 E4 m<TR>) r' b, n+ z5 k0 v3 _4 a9 F& p
<TD><FONT color=#00309c>INPUT input[4];
$ D: N; q9 N+ G: ^+ [memset(input, 0, sizeof(input));. L6 S8 r8 C* A" I0 T5 c

  D" v8 I" x0 g# i//设置模拟键盘输入* n+ f" Q; @* x9 R0 R
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
0 ~6 c6 E7 U( ^: r" q8 ginput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;" E* ~: o# M+ i# \) s* `) S
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;' w+ D. [! U; b+ d4 z

& }* a  x$ c9 e# w: w* t3 W// 释放按键,这非常重要
& ?8 Y' [% X  R; pinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;7 \; ^, j* o, c. h* P- X

# I" Q$ g, m: q) T. ]3 k; ySendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
! G3 H' K, [- t/ ~& T8 Q* m<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)% J1 r3 E$ n7 w
</P>
- h; {  [6 x  h) E9 K9 N) H% C$ y<>六、监视WinEvents
9 g: D9 x, f9 {! y    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。" r! U& o+ c4 B$ G
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
. X6 j1 k4 v8 x0 l) i    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
3 I5 a4 u' X, i7 S6 ?! x<>- i8 I- S1 m: R. T; T$ Z# C
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) r" S# a' J. T  [2 c1 }. y3 L

7 W  Y6 [# F! P1 c( n  ]<TR>; i' x7 j, u+ U
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
# h/ c; B5 w0 V{
& [" Q& b8 W6 K( T4 f; ahEventHook = SetWinEventHook(
# i8 v7 b1 M; T: d; Q+ LEVENT_MIN, // eventMin ID
8 x' p- [8 [* J( Q( fEVENT_MAX, // eventMax ID8 z4 u* |# t7 B8 H" ?( w
NULL, // always NULL for outprocess hook9 {0 V. ~+ @1 C
WinCreateNotifyProc, // call back function9 x9 H6 d8 S* L8 f
0, // idProcess
( C1 c, ^  Y+ ?: w' L4 D7 l0, // idThread " d# Y5 Y  a, w: }2 D5 v0 B- w
         // always the same for outproc hook
0 H. V$ n4 ^# P  ~! \$ p7 kWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
5 |1 Q& R- t1 `8 \# Q# V% S" k( k1 v}  </FONT></TD></TR></TABLE></P>
- _# g4 \8 ~6 Z& L" G7 `8 b5 l* n- a7 ?$ W* k
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。  m) k( q6 a# Y1 L
下面是回调函数:</P>
1 j! w7 h- o. K& W1 v<>
1 h! e- ?0 C6 w% V<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& ]0 M% C) G: U

# ^$ u; H, H. M- h& I, D2 r/ Y; X<TR>2 e4 A. x9 f- m( v  R+ L  e
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(3 r. A0 \# l# y( y- a1 a  \. i
HWINEVENTHOOK  hEvent,0 X) X# p( `" g4 c, l* ]& l  f& q
DWORD   event," b0 Q0 w1 t7 S! S$ `- z6 e7 `  H$ w% Q
HWND    hwndMsg," b: g2 R: h, {( w& z! q; d2 R
LONG    idObject,
& a1 W/ K7 P0 L0 t  q) b- _1 BLONG    idChild,/ A+ e$ x! S1 T: V& Z( U
DWORD   idThread,' K0 j! @9 ^" B: `
DWORD   dwmsEventTime8 [  B5 `) w0 o0 u2 i! f# {" E
)$ z& j0 m2 Z; f: H
{
5 y1 r9 X! A( _& x. m& \9 i2 W. \- A
if( event != EVENT_OBJECT_CREATE)
, @" M2 d( U! Hreturn;
+ a" B; c9 \+ ~7 |
0 e" D! \& @0 h* C% L0 cchar bufferName[256];
3 ~; u  @: Q- ~9 Z) w' QIAccessible *pacc=NULL;, Y9 _% {3 p# M7 j0 K" o0 C
VARIANT varChild;
& {! a; G+ x% {4 H    VariantInit(&amp;varChild);
  l& r: a; z3 W2 g8 z  v1 h9 ]//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
. g+ ~. s# Q. K# Q4 aHRESULT hr= AccessibleObjectFromEvent(hwndMsg, 4 a9 h/ A3 c5 d6 ^. m
                                      idObject, ! P' P9 v0 d, h" s
                                      idChild,
' h: m% x) U. i; F( K5 F                                      &amp;pacc, : a( ^; n' e( i, i, h! J9 q
                                      &amp;varChild);
- I; Z) Q+ b$ u, ~( H; J6 f4 R/ F* f
if(!SUCCEEDED(hr))
# f/ U/ k% \& m1 P1 Y: e" L{
& h! d2 K5 W* B; Q1 K3 U& xVariantClear(&amp;varChild);
  ?# f) q+ f3 ureturn;
9 S5 g8 b& `" @* c}
8 {, [# J; B$ t% D' }//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
7 _8 l+ ]7 t% ~! n$ HGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));/ s, J% Z0 ^! f3 Y6 K: G
if(strstr(bufferName, szMainTitle))
" n% G0 ~4 W7 h& h9 \PostThreadMessage(GetCurrentThreadId(),
/ _0 c: H6 N: `. `, E: u* ?                  WM_TARGET_WINDOW_FOUND,
  E8 l$ @/ Z9 X& {( I# K                  0,   F! `0 Q+ k+ d: y/ x
                  0);' r; N) I# c$ f: z' S3 y$ A, c" s

7 p8 G; p+ v& [return;3 k6 H! r5 Y7 m. \  L0 d# J
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
. }. M2 L& @7 l) N8 t: t1 M8 Q6 }" [( d' v% B) [7 Z
附录:+ _6 c" g* X7 x3 n- U1 n
: Y( ]2 _, N( H; X4 k$ g" n
关于IAccessible 接口/子ID对:
4 i& N9 ]3 ~+ @% y' c0 _    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
4 R$ n) B, o# J% c    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!& u6 i, U4 L  [8 r2 v+ ?
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
3 X6 {& h) ?* J0 d! m: b    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。1 z$ x. H1 [1 ~' w: c
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
4 o, |9 H6 O, J* L: ^
8 j+ U7 E: _+ c; E% ]注:+ b3 j" T* {" J3 r  |9 s6 e
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
' F* h2 Z* E% v: c  b6 R    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
% M3 X8 N# Q& n  `3 Q: v. ^
5 j* ^& W& Y7 v, S) y+ l' K+ b; Q, o参考资料: " J3 J" j9 a( H* E8 K
<UL>; m8 S$ E2 A' j  N% C! r" |
<LI>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
  d: G0 E7 ^) o+ T( K<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
xShandow        

43

主题

1

听众

385

积分

升级  28.33%

该用户从未签到

国际赛参赛者

新人进步奖

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2025-8-25 08:16 , Processed in 0.456320 second(s), 57 queries .

回顶部