数学建模社区-数学中国

标题: MS Active Accessibility 接口技术编程尝试 [打印本页]

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>5 L4 d4 b" O& }* W5 ~- _. m
* M4 ?1 n' ?, _9 u, j# G" [1 n
<TR>
, Z% W  e3 N( b6 v. q6 a8 X6 q0 P<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
9 d% O# v- ]7 W  c& ^; C原 作 者:崔传凯
5 E( {0 P. g1 N" D" h8 H/ K/ g原 出 处:vckbase
  C, A( V* n2 |. o& x; Z发 布 者:loose_went
1 A0 O7 P0 Y- i, r8 C* X! \发布类型:转载
. D$ ]  i0 v6 Q" U发布日期:2004-11-12  m$ [6 U1 e8 b* L8 i5 G
今日浏览:6! X) X9 W! |  w
总 浏 览:234
/ M, L/ p! P, I( u</TD>+ `* u( i; E7 X- J$ e. E/ z$ n; B
<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>
8 f1 \2 I! w2 l5 i4 g4 h2 g<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
& P2 z7 O5 O1 g4 n1 }
4 u% Y7 x# X9 n<TR>
8 k" y& C+ ^3 l$ Q. o# T<TD vAlign=top align=left bgColor=#ffffff>% A6 Z7 t2 l2 m3 @. i2 T) c7 ?
( a" O- ]- g% C1 p& K
<>
6 I3 {& y+ C4 h& U0 h<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* o4 A; E  ^8 {) ~
# p: |9 V. j* \' C' ^
<TR>
0 F9 k* D6 @2 Z4 @<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 9 e. b" o3 K7 C2 F/ m, v0 ?5 ^
way accessibility aids work with applications running on Microsoft Windows?. It ( q9 ?8 r$ c! ]
provides dynamic-link libraries that are incorporated into the operating system
! J- l7 X+ A" @9 Xas well as a COM interface and application programming elements that provide ( y7 Z! z; O2 w" T1 h/ {% N/ y' h
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
8 k/ J+ \5 `! h! ~1 v<>一、基础
: I+ j$ e0 G2 t  P" D) H  S3 i    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
+ f4 L' j; Z8 q0 V  c    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。* O. z9 f% f% F# _; m
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>2 o' u. O$ k+ r# W9 r* D- C1 y
<>二、Active Accessibility 原理& r/ M: A% T0 H% Z
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
* B1 M1 |5 x6 i8 c; ?# x& a' U    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。4 Y- l! V: Z+ j  H
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。4 y/ X* L; i* x: B2 q0 \
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。: \8 x; K3 A. `
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
9 {- p, \" o0 a- ]<>三、如何得到 IAccessible 接口指针- u4 g/ \# g. Z1 t3 v
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。- y( M1 f4 I1 D! ~8 Z4 c
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
/ H) L2 r. _$ m( t    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。2 Z) Z; I9 _4 u1 G* A
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。2 |: L8 B+ u8 ?) m
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。: Q; z" L' H- ^$ C' f' M6 \/ s
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。$ G! X( t5 Q3 D- k5 {
下面结合代码介绍一下它的用法。) j) h  h/ \4 N0 q) R  J" k
我们来得到下面运行窗口的 IAccessible 接口指针。, ^5 g: Z) y( L9 g! j
" H$ e& A5 W2 o, {2 f8 A% L- Q
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
, f4 O- F1 \2 j: r<>图一</P>
) R( p) y, N, @! _9 j- c) y$ X<>
! F) m7 m( w7 ^3 @& t  {<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>$ A) {$ N2 y& S) I. Y' t
, o) t7 b; f, G7 n5 }
<TR>
- Y& l2 h0 i1 h! w& m5 F% L2 z<TD><FONT color=#00309c>HWND hWndMainWindow;, _! d; _" F3 ]% r  E" M
IAccessible *paccMainWindow = NULL;3 }1 j% U3 N' G' T- [/ h& L
HRESULT hr;) Z. ~! P2 `6 N' z6 u2 `( I: d
//得到标题为"运行"的窗口的句柄
3 Y  m, z6 L: n* J6 L& y& gif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
& i3 c5 G% N% @. d# i2 w# E{# W7 R( b) @3 j7 U
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);( q/ w+ F9 ]1 b8 ]* ]4 f' u: k
}3 A3 A% _3 W( l& |
else
" u" P# a( \" K: ?  \{
" w5 U! D, f( v2 |) V& }& J% T8 Z//通过窗口句柄得到窗口的 IAccessible 接口指针。/ D1 [/ R- {3 a7 P1 m+ w
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
4 I& J  v; p" @3 d+ `                                            OBJID_WINDOW,
! V9 O9 G/ ?4 ^+ q                                            IID_IAccessible,
$ c4 O+ B0 E/ y2 C7 Z                                            (void**)&amp;paccMainWindow)))5 j& t' q! H5 m, N
{4 x3 m# l, O. e7 q) s: c4 H
//……我们可以通过这个指针paccMainWindow进行操作。
* F' a6 H+ V* HpaccMainWindow-&gt;Release();
# ?+ p5 [. W7 Q( a3 S3 P        }
% f( l" L7 F% i* b, V}    </FONT></TD></TR></TABLE></P>8 |! Y) _1 C: p7 E+ [0 M. i
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!0 `; l5 s( ?6 x0 F2 @+ m
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
8 W& F* A% C6 O" b$ C) r, S    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
1 T% |4 f- P- j<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>* I# C4 B+ A! i* d, ~+ }
<>图二</P>
7 x# }' T5 \1 E# W* e$ j8 r0 B<>我们现在主要关注的信息是:Name、Role、Window className。</P>2 ~& ?# s" ?( ^! [0 r5 `* t- G% z
<>
; Q+ L$ r% ]1 a- S<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
3 t2 S, }5 M( q. K$ f/ @* I& T) Z2 j: A. s, l3 R" B
<TR>
& x+ g; t( V1 Q. l: Z( \- y<TD><FONT color=#00309c>Name = "打开(O):"
" t% e2 d3 }* V. JRole = "可编辑文字"
" G! f( `) t) z" W: DWindow className = "Edit"  </FONT></TD></TR></TABLE></P>1 N  z" z$ K; \6 \& y9 I
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
8 G6 Q7 O" c! Y' Z5 y2 R    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。5 Z  g% S/ s' q5 j! U
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>8 i! K3 ?" V) N
<>' E# Z# b6 @1 n7 V# g
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
( K  p/ |* u% t4 |# k7 ]" e" V4 N: I
<TR>
( C- f! @& d; D, v" n<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口: W- g) h+ d% Q2 `9 u9 b) z/ F
VARIANT varControl;    //子ID。' g9 @' F8 p1 V( R$ n& F+ a. z
$ D7 c% a7 w4 G2 X: v+ j; e* P
FindChild( paccMainWindow, 5 r( z, Q; i" b& g
           "打开(O):",
/ Y4 j9 B, v8 _. a/ {           "可编辑文字", - g4 D. r2 l/ R/ Z* F$ c* h
           "Edit",
6 ?1 }- @6 n# u* E: y) D; T! B           &amp;paccControl,
: l2 i# R/ J8 Z8 ^: c! g           &amp;varControl ) </FONT></TD></TR></TABLE></P>+ p1 a) [; S% v" ^5 B
<>第一个参数是先前得到的窗口 IAccessible 接口指针。3 k8 F% _, f9 p6 I0 L" U! L
第二、三、四个参数分别是名字、角色、类。/ R1 i6 s6 Q3 o6 a( Z
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>/ l1 A  r$ O2 `2 |& `& n5 b
<>  D/ g; O1 N. t, J5 P9 }; O
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>$ R6 Y9 E% }0 q( {( \

5 }% Q$ I6 P) {<TR>
3 m- ?, a/ p& g9 p  ^<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
. ^- G8 J  g( ~; ?' C8 `8 e                         LPSTR szName, LPSTR szRole,
' Y3 S% U$ C- Z) k5 Q! g                         LPSTR szClass,
# @. L, T+ \' [4 d0 v& q5 t                         IAccessible** paccChild, 9 U" A7 C+ N( w) D! R
                         VARIANT* pvarChild)
: T8 E1 Y+ \" T) Y$ p. I3 l5 `{
, Z% H. D" \7 AHRESULT hr;
; e8 r; ?' Y6 L6 ^  U/ along numChildren;/ o+ Z, l  N) l3 C* X9 G" D9 r) R
unsigned long numFetched;
- w' p# h1 D/ I* r7 |/ ]2 ^VARIANT varChild;
" d& |4 x5 f7 _1 K* Cint index;
( Q* _" f$ ?8 U$ p+ A) D4 U7 C; Q0 eIAccessible* pCAcc = NULL;: A8 `* ~& F& M) @" q0 Z
IEnumVARIANT* pEnum = NULL;# M9 q0 `' Y3 ]3 g! W
IDispatch* pDisp = NULL;8 J# r4 z/ B% m! Z
BOOL found = false;# u6 _! h) b+ ?+ L7 S
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];/ v. L' h4 K  L  F" R: r! [. F

" o" N% ~* v$ j//得到父亲支持的IEnumVARIANT接口
  z6 D/ j! y4 G9 _& lhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);1 k/ J% j# V( v3 D

1 o; |/ p7 n9 mif(pEnum)
5 N$ C5 {( \8 \2 P' V& _# gpEnum -&gt; Reset();
) g: L: H9 Y% h: z$ m8 [  ^7 z  c2 f2 a0 T, ^! C; C
//取得父亲拥有的可访问的子的数目
. {* {' }- l* m8 e7 opaccParent -&gt; get_accChildCount(&amp;numChildren);
) J9 S* W1 x3 f! ^3 p- a% j
5 {. O/ E0 \7 b) ?//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
, ?8 \; S7 F( f2 n% W. I+ Efor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
/ u2 n4 K. h1 [- l  \0 I3 o{& |. j  h1 b7 }. P$ L7 h6 q1 C
pCAcc = NULL;   G( w3 F% Z7 A, t
// 如果支持IEnumVARIANT接口,得到下一个子ID0 r0 g' m  I9 A8 R& y) q
//以及其对应的 IDispatch 接口
" B  O; \! M- Z! ~if (pEnum)
. V8 |) J7 i, J* E! V7 e1 B$ b4 r6 r: Ahr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
; v( D( g0 Y& ~else
2 v& v, l/ H! C/ n{9 }! F4 a* E, Q6 G: [% y& r1 w2 z
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
& S1 g$ U$ t) Y  K/ F+ y# dvarChild.vt = VT_I4;% ^) Y6 p" f8 ^
varChild.lVal = index;% a/ W# w) i3 N. O
}" ]( K- s) y# b; u& h1 b1 F
/ q. q9 H; G: j8 L( }
// 找到此子ID对应的 IDispatch 接口9 |" ?+ q8 d! P, e9 e% Q
if (varChild.vt == VT_I4)( i' N4 i5 x0 }* b2 l8 }
{
4 h8 e% e% d' w0 r6 Z! u//通过子ID序号得到对应的 IDispatch 接口
/ i0 _2 n/ n- i$ Y/ w/ A/ D" xpDisp = NULL;* f9 P& O$ i1 J0 t3 v
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);& p- r5 |7 d& E# W
}
& e. |' E0 S6 \2 A: n- S5 Uelse
1 X6 x# Y* I3 P. X- @//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
# g& g" B0 n% ?! t0 T& ?: e, ^pDisp = varChild.pdispVal;
/ e9 E, n7 L& k6 n, L
; \, a; p! s3 B9 T- k* ^9 @+ |// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
+ D4 t' O- x9 I$ Gif (pDisp). ]8 y- J/ u2 S  _1 j" R
{
3 d0 ]7 v7 _* i4 q( Q. {hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
: C2 M4 ^3 @& {+ [, Fhr = pDisp-&gt;Release();
* e  u. E/ L: M7 h}% Z3 Y2 o7 Z# I) a* R5 l+ D( Y

" f6 h4 g- M; E4 l: O( }. s// Get information about the child
$ k" m3 O" R& z4 U. {* [7 rif(pCAcc)
, c' H) Y) G" ?- {" _{5 I9 G; B/ o2 B+ C7 f0 A, m
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
5 o2 t1 O& C7 U9 n8 v3 ZVariantInit(&amp;varChild);
. x" o! f; E$ {varChild.vt = VT_I4;
, U8 Q% b( _$ M, B! n6 yvarChild.lVal = CHILDID_SELF;
/ R; d* G9 f0 @! w. g
3 u6 g- U, O9 V5 X*paccChild = pCAcc;
' ]& z1 Z6 w0 {# i' X! C1 j}  F1 j' J1 z* ]
else
! @; o& k# p* e; W1 m//如果子不支持IAccessible 接口
) A6 Y/ Z4 {' E. F*paccChild = paccParent;3 r: c+ `1 c7 q# ?

; O. F+ u$ V: h7 ~( @; q! r//跳过了有不可访问状态的元素
, B2 s9 u( o' P% W8 t* }' @) tGetObjectState(*paccChild, ( G" u9 [( ]( V, }3 D1 W
               &amp;varChild,
* U, Y8 \" w8 M, o               szObjState, $ I) P" ]# i( i" O' H; q4 a
               sizeof(szObjState));
9 g0 D9 b$ h" kif(NULL != strstr(szObjState, "unavailable"))$ N4 Z; _& b* G" h
{! f# a. ]1 w) R- u
if(pCAcc)
; Q3 c# v4 V) WpCAcc-&gt;Release();6 b( A1 b# }/ w) _# D' l9 d7 L$ g
continue;
$ \" @! Z! u7 L# |, {# l. `5 F: e}
& @. ~! b1 r" A: d& W. C//通过get_accName得到Name
7 G2 W5 s& g! h( ?. ?4 }GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));/ D3 W2 C; C% g( y* F
//通过get_accRole得到Role
1 E# N" \7 E9 d$ A# t+ f9 o0 BGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));/ B/ U9 v! A0 d5 d2 g, L9 a% w
//通过WindowFromAccessibleObject和GetClassName得到Class
! h6 ~; a6 x3 E; h* j$ tGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
4 ^5 \. `  |$ ]//以上实现代码比较简单,大家自己看代码吧。
0 I; X6 L# [8 r1 V# `% r' S7 P* e1 V) r, `5 Z- w0 {
//如果这些参数与输入相符或输入为NULL
1 C/ R, }" |3 f( |% nif ((!szName || + K3 u# |- G" N) H3 R. b
     !strcmp(szName, szObjName)) &amp;&amp;
0 p% t; b4 j3 D* @! p, h     (!szRole ||
7 M5 o) l; ]) t5 `* o  x$ o" Q  @: v      !strcmp(szRole, szObjRole)) &amp;&amp; ' o' R1 G2 a5 u
     (!szClass ||
% ]+ m4 T, A1 d% C' H      !strcmp(szClass, szObjClass)))
9 Z) E$ h1 g# j- U: s# {/ r{
  ^8 x- O2 r& Q, g4 i0 I% gfound = true;
9 F! e! C* ]$ J2 ?8 \*pvarChild = varChild;
( [: ]2 z8 `$ M5 dbreak;
+ T+ N6 y9 G. k5 Q}
* f8 q9 I% a; W8 h) _9 `( sif(!found &amp;&amp; pCAcc): f2 P* _9 [9 G/ g# B6 ^
{
# s' _) G" ]- _// 以这次得到的子接口为父递归调用
6 F5 W$ J! C1 t( B( s; Ifound = FindChild(pCAcc,
  Z8 |2 r3 N) m                  szName,
8 ^3 ]1 P6 W9 l' A: A                  szRole, # d0 H& C8 u+ s& s5 n0 |
                  szClass,
" P  @& s6 S+ \) Z1 e0 Y                  paccChild,
, _' L+ L) g( h                  pvarChild);3 [; Q. z9 y' y. o
if(*paccChild != pCAcc)) M9 E7 x) N$ u. d+ W
pCAcc-&gt;Release();' F1 [, F7 A( `' h
}4 k  Y* e# X- c+ I- l! o1 Q
}//End for: ]0 @4 q: V" I2 O, R( i

5 c7 b( G: q$ l) L1 E8 v// Clean up7 a, [+ U- d8 G2 x# o& O% w
if(pEnum)- H7 J0 d' E) m. A+ N4 r
pEnum -&gt; Release();# w" v4 K# P  e  p9 j
; e8 k$ s3 @0 J/ d& d" b  n9 t
return found;
6 r: ~1 K( Q2 A- }/ m5 X}
4 i2 ^  q% e$ h5 u7 r- m# r" t& N7 }) R0 Z
% K3 j1 g  @" q( C8 T* B) a0 ?// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
0 D$ Z& L& @* S6 L: H2 |" O6 S//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。# M# M* u- S, [' h7 m/ o5 }2 A
//将这些或数转换成相应的用逗号分割的状态字符串。
* Q& H3 W) P. `# c8 }0 r$ CUINT GetObjectState(IAccessible* pacc, / n) ]- z6 t3 f/ f& O
                    VARIANT* pvarChild,
% w& G# B* f$ G$ Y                    LPTSTR lpszState,
2 O! W) |) g: ~" ?/ m% `5 W) k                    UINT cchState)
; Z) o' R5 w3 F/ U( R8 F{* i! m- V& T: [! ~. c! Z
    HRESULT hr;
( V: r# M3 ]( b& A5 {" y    VARIANT varRetVal;4 e  a0 g9 R5 }; ?; ~

1 @$ A* f0 x6 o! `2 c+ V. v    *lpszState = 0;
; m5 ~7 b3 H: {1 n! H7 u, g1 Q* O: l5 U5 m* T
    VariantInit(&amp;varRetVal);5 K9 g0 ^2 h% m6 @

# l( q" Z- T* _, R% d5 F  C( H    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
1 I& ^- K3 E1 z5 M6 K1 R- M' j' U, M* c) v, N$ a
if (!SUCCEEDED(hr))+ A9 S" t+ s# E
        return(0);
2 w: e& {' ^  _. `, y& f7 v+ ^+ o
* q. |. t: C" O0 ?* g/ \DWORD dwStateBit;
% M( L) z4 r2 [' t8 i+ T, zint cChars = 0;
: E4 f; t* @8 ?( x6 X+ ~& w" H    if (varRetVal.vt == VT_I4)
$ h7 Y% D3 x: D. T{) \9 x5 \7 G: o7 i( e
// 根据返回的状态值生成以逗号连接的字符串。
8 {' F( p4 I) \2 k        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
& w+ @; h4 N, A/ R+ d3 A, I$ ]8 L               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; 8 `( y5 ]# a0 G
               dwStateBit &lt;&lt;= 1)6 W' b5 i1 ~. M. D
        {
; F+ c7 B! s: c1 C8 B            if (varRetVal.lVal &amp; dwStateBit)' d, Y) E/ U9 K+ c$ L4 y
            {
$ i) d  z$ f* \9 S. z                cChars += GetStateText(dwStateBit,
, h0 J- K4 u( V$ c$ _& u% Y                                       lpszState + cChars, 8 \0 y/ i, {$ c8 N8 l$ N9 U" V
                                       cchState - cChars);
9 z! B4 b1 y; s4 K  b( Q$ i! Y# t, |7 O*(lpszState + cChars++) = '','';2 K; x4 A6 m; I* m
            }2 F$ h) A3 x, X2 H
        }
9 n/ {! Q2 n0 \; Z' o3 w: tif(cChars &gt; 1)9 Y0 \- T1 d1 E7 a
*(lpszState + cChars - 1) = ''\0'';- \: S- n. g9 v) _8 [6 ~2 V
    }1 C5 s1 P% X9 n) e3 Z
    else if (varRetVal.vt == VT_BSTR)- O9 Z6 \9 A2 H0 r& D$ Y; N) A
    {
  `0 a$ l5 s1 x$ b8 [, i3 e& Q        WideCharToMultiByte(CP_ACP,
1 t0 K" r$ ]# X- N" f                            0, 9 x4 @. p- A/ o
                            varRetVal.bstrVal, : Z# q% x6 b' y! M2 W- @
                            -1,
. x# O; }1 F! I8 U* C) B                            lpszState,
1 M" ^4 }# V, t4 H  P7 E                            cchState, ) f8 e3 K/ ~7 T; R6 X, {5 C  u
                            NULL, ! X: D5 C+ C* p5 \
                            NULL);
# a% c- l% W& I    }' D4 y) I7 G) _5 ~( B4 D6 f6 `

2 d+ s2 L; u( z& W" o0 ~# u    VariantClear(&amp;varRetVal);" P9 ]; e2 e8 d8 }
- P# \- Z$ [' w  m$ {- y
    return(lstrlen(lpszState));8 V( N/ o# C" I5 P2 P4 z
}</FONT></TD></TR></TABLE></P>
8 g" U& N, y/ X5 ?$ X7 ?<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
- ~# e* _& ^  R, q+ ^3 n4 [2 h/ }! f# g
四、在 IAccessible 接口上执行动作
) n6 ]# @. ]0 j1 I' a* X9 C& G    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
9 n( z& l" z# C# A: w8 y    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。8 L! C7 l* q7 z
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>- J& F$ ]( |  x$ X9 t8 k
<>5 O& I% L2 R( m/ H* b- E
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) w8 ^2 u. q0 z% m. s

2 V' A! |* j, r<TR>! P9 z7 J1 b3 I
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
- J: A: Q+ U6 w" @: S3 ?: uif(1 == FindChild (paccMainWindow, "打开(O):",
" {" ?& V- N9 f7 q                   "可编辑文字", ( f* g: Q3 j. N+ D, e
                   "Edit",   e/ G' C( N9 m4 o- m
                   &amp;paccControl,
4 ~0 u; x" N3 r                   &amp;varControl))3 _- U- H6 U6 j5 {
{
$ s+ ^0 A+ q. L5 t! t//在这里修改文本编辑框的值
' w% M% C0 W) U9 y; p& xhr = paccControl-&gt;put_accValue(varControl,
1 a, \6 i7 d+ `: e" R6 H                                  CComBSTR("regedit"));3 y7 f/ @, R2 ]; r
paccControl-&gt;Release();
! x# u" P. R, v! f3 `1 W/ S. S7 sVariantClear(&amp;varControl);
4 O2 z, N5 W. y* Y' C, H/ d! g' m9 l}
1 `1 D7 j7 p1 t, Z8 i2 I" N% z5 ~, f7 g' D2 D. ]! }  o8 S8 m( g
// 找到确定按钮,并执行默认动作。# v* w% D- T& B" g+ t2 s5 c7 x6 t
if(1 == FindChild (paccMainWindow, ' G' q& a& s! M# M4 n
                   "确定",
$ g1 p! d6 ~$ h. m                   "按下按钮", 9 W0 C% t8 N0 i: P
                   "Button", 9 W6 }$ q. [9 h" c2 S
                   &amp;paccControl,
) @) n) k3 W1 x/ @/ c, Q3 t                   &amp;varControl))
" p4 t$ U! x2 y{
) z  P! l) I. `! Q3 L$ X//这里执行按钮的默认动作,即"按下这个按钮"- p# a8 c7 d  k
hr = paccControl-&gt;accDoDefaultAction(varControl);( b: n4 F! U4 x6 u; k- W8 |1 u6 {
paccControl-&gt;Release();5 b% D' i, _: a: ^! e
VariantClear(&amp;varControl);+ E. B3 r) F  ~* @
}</FONT></TD></TR></TABLE></P>
; @- R; H3 v, E' i<>现在,你会发现已经成功启动了注册表编辑器!!- i$ p- w/ j! w1 ^
</P>
" M1 U' J4 R: n$ ?: c$ y+ B<>五、模拟键盘和鼠标输入
6 o) i8 `/ Q: U( d% H7 F$ D1 o    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
* q+ N! ^) a: p# i. i    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。+ L) O" W& \) B0 k
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
& j. n% W+ |8 s( W1 H( D下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
0 H5 E9 M. D1 M* v' A<>2 K4 _% C8 A" E# |
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 r* T" [3 L4 K( u' V! R1 ?5 m3 m* f  T
<TR>
5 }8 k5 ^* [" M+ f3 R7 F: h<TD><FONT color=#00309c>INPUT input[4];
/ m6 c! u5 n) k+ X; A" s6 ^memset(input, 0, sizeof(input));
0 B6 b, \# T1 s$ S0 z2 p
- V- O. a2 _8 c+ j" k$ e" E//设置模拟键盘输入  I$ ?+ K# l7 Z! m2 }. {& a
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
0 m, @4 O- g) a' c0 Q3 B9 e  q3 Iinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;9 m+ w+ s7 q( t; J8 \
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;" w! Y; Z9 _9 e9 |
1 n6 l- n: W0 A, _
// 释放按键,这非常重要7 ~! i6 }' ^) Y
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
' _, z/ H) c+ x0 U/ y7 ^1 o
& n# t+ b+ R/ e: q2 S$ vSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>0 E4 w2 L# D5 y% u
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
& p# w) p) C$ L% \</P>+ d( K1 O5 d1 J5 I5 H! c( v5 S( f
<>六、监视WinEvents
) U- y; J1 v" e    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。1 }3 R3 R9 _: g+ Q" @0 g+ \8 q
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。7 C$ }2 P5 W9 d) B* O
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>" h2 i- b3 l0 G1 Y, A. ?
<>+ Q  n7 f0 C2 B: V% n
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
5 o1 E- ^4 @: |9 k! n' t6 }2 u" x5 t$ u8 x
<TR>3 b  Q. ?, ]% [
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
) Y: |. N: }( v/ p) B{
- \8 V& L) ~- ]' q8 P. R: ?4 MhEventHook = SetWinEventHook(
/ g8 V7 C) C( i+ T* KEVENT_MIN, // eventMin ID; p. u6 i. g, P% W+ n; F. s
EVENT_MAX, // eventMax ID2 C. f" w4 _' J, D( o+ z; V- t
NULL, // always NULL for outprocess hook, }/ K2 b# m9 c5 k& Y0 u- _% z
WinCreateNotifyProc, // call back function
" m, I3 {/ T* z: p2 \0, // idProcess
' X% G+ ~4 _3 G( z+ I9 j* K) I0, // idThread 0 q5 L1 h/ E+ l
         // always the same for outproc hook0 E/ _5 r4 M2 X
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);4 I  w. l3 ^0 O3 r6 U' h- L
}  </FONT></TD></TR></TABLE></P>
  s- z( w) G! K* [  G# S! _3 y/ B2 K: r' o, n) y
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
& b) S$ l7 c5 K, U" x1 I下面是回调函数:</P>
( ?$ S* B; p# [* Q<>8 i, v1 a3 j3 D6 B/ c( ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>: W. ?: |) v" |7 N8 r, h) g# P

: B8 s4 `% t6 K! i' h( Y6 R<TR>% x1 w0 Q& g* Z9 U
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(& n  @# f  v$ \
HWINEVENTHOOK  hEvent,
3 a( D% B: L. `DWORD   event,
) [* P& k( x6 p. N0 J6 s& @HWND    hwndMsg,
; l% B0 a# J  l/ RLONG    idObject,
* o: p& @0 ]% D" i5 GLONG    idChild,
- u& N9 ^' V/ l. n2 GDWORD   idThread,3 w4 A8 ~+ W% L9 ]7 d0 M
DWORD   dwmsEventTime8 k3 y& R' v3 ~5 _9 C
)
6 o' p5 i) q( X) X8 ~/ M1 J{
6 E0 v! ^+ Q- ~: a1 M* m- |; Z$ K  M# A- N
if( event != EVENT_OBJECT_CREATE)  s2 m! s8 d+ I( w
return;
3 Y4 b% g) x7 \" `$ g, a" o* k( n. i/ s, t" J( c& q
char bufferName[256];
& M9 z, j- `0 x4 B2 @6 KIAccessible *pacc=NULL;" L0 F5 ]$ |) f1 `: u  S; Y
VARIANT varChild;
3 l$ C0 ?5 g* u, s    VariantInit(&amp;varChild);  G6 d! D4 c/ ]2 `5 q: v$ q
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
5 L! Y3 e4 ^- u) i5 ]7 FHRESULT hr= AccessibleObjectFromEvent(hwndMsg,
+ n7 @7 s% _9 ]7 h                                      idObject, 3 m5 G+ j; \4 G6 F
                                      idChild,
- }' X- Y. U  `4 I' S                                      &amp;pacc, 5 J' \* V% [; g8 B
                                      &amp;varChild);
; O& F# t$ Z9 M: L' o7 ~
2 F$ Z- i3 S  r& P9 Jif(!SUCCEEDED(hr))/ G7 ]8 O3 [( t% L$ J
{
  c* a- }( I& XVariantClear(&amp;varChild);# E0 j. j9 Q% |/ _7 B8 n3 x
return;
/ f+ v: W' E4 F! `, H$ Y}3 e) ], v" @% F+ ]
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。8 s; R) c" S* M: S6 T3 a( Y
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));9 r( n" X2 g/ e/ P( V
if(strstr(bufferName, szMainTitle))7 t2 S" p" j; k6 B9 N' |& U# R
PostThreadMessage(GetCurrentThreadId(),
) _+ w: C( `: U) J+ o                  WM_TARGET_WINDOW_FOUND, ( \  T0 K4 z6 Y- p9 A
                  0, 1 n; r8 A+ J2 R) A2 p$ }
                  0);/ @! X+ n+ |' t4 d! o; \* Z
4 l8 w! H# n. g  z
return;
. |; c( _* c& G& L}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。1 M' H) ~2 E, _- q6 y2 o
/ c# n( T  w3 l" N( A
附录:& O; @7 d; }: v$ r  e' ^
) z# h8 m8 X& a4 o7 M/ b( w
关于IAccessible 接口/子ID对:
* h( W9 j% Y+ o1 i# o1 t; h    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
- n" n% a: @7 U1 z' @, p+ \2 W    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
7 T4 m0 x# z9 E4 W    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
$ K+ A" a9 A( ]( d' Q6 q! Z' e, b    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
1 T9 ^% v) n$ W/ S/ R1 t    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
; y' D6 |1 u. r: K. `2 ^" D) P/ m4 n6 h0 ?
注:
! u$ P  l' b/ C9 h  t) u    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)) w2 R1 j5 b5 c3 P; W. W  N: f& s1 a
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
1 e* P4 F7 V1 F# j7 b& `
7 {5 K) R+ n' A: w% S参考资料: 7 ?, A- \9 s$ z& N8 e3 F
<UL>+ w& c5 N; @& I3 i0 p' M! k
<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 # w' k% G. t6 S% G- m7 ]. n% @2 O
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5