数学建模社区-数学中国

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

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
) S0 b- m, }- g0 M* v8 a5 B% ?8 D, a. x, v# ?0 `
<TR>
$ v, p, f- M( Q: _* J9 c) f" P: L4 r$ p<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>7 x( R5 \9 f2 u$ M4 b/ \' F! C
原 作 者:崔传凯
0 i3 V" a% V6 N' f8 C8 V/ c原 出 处:vckbase; f- L& f0 A. ]" n$ Y* O
发 布 者:loose_went- n) b9 k1 G9 g& X* d9 n
发布类型:转载
% H/ M, N- ]* D5 A& f, _发布日期:2004-11-12
2 ^; `0 b/ ]6 \6 ]今日浏览:60 x! c. u! E* Q2 g6 X
总 浏 览:234
/ @. p* e$ q7 M- {; d</TD>$ l5 `. P* Z' M( ?8 \! S
<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>
( ^/ V* \$ g) z( z. T4 M<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
/ S( B+ ]8 x# w8 w: ?
( C& H" x. \7 V) i1 s<TR>- D9 C8 p/ X2 S, i9 ~) B2 B
<TD vAlign=top align=left bgColor=#ffffff>! U  t6 ?" q3 u9 M2 r* A
# _! b8 h: F% x9 Y% \6 ]3 l
<>) j; v$ n4 w2 L+ a3 h  X
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
: ?: q: C7 U; o7 W! Y8 g5 E5 j: t, v# a
<TR>) r+ G# j6 V2 j! Y# f
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
# d9 ]! {! r/ y( ~, _1 ~5 mway accessibility aids work with applications running on Microsoft Windows?. It # I4 N; T7 M  m1 {8 A$ d* O
provides dynamic-link libraries that are incorporated into the operating system * ^: t* P+ G: {# f. {2 |* x& x
as well as a COM interface and application programming elements that provide ) s0 s' {4 v+ l1 d. E8 b
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>' N+ H( ?7 v  g. b
<>一、基础: d+ ]; I" J5 o+ g, c! [" {
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
# _& V7 C, B9 W. b) a    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
  e2 n/ C3 J3 Q2 t    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
4 x0 M: x; h+ B$ a6 n<>二、Active Accessibility 原理
+ J$ {. K; o' l. H8 I% m    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。* \! H, v0 r6 N
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
% Q- N! g0 {& ?4 T1 V" ^    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。9 D/ ^7 b( n1 H5 o" x
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。- u/ ^8 x. p7 D9 m$ X! Z
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>5 [& v6 i: c2 [# v' R% f5 Y
<>三、如何得到 IAccessible 接口指针
/ E# V1 b/ ~- {/ N# y7 U6 Z9 a, R0 H    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。# V1 h6 \6 |$ |! b1 r' p
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
, a! I4 R6 J- y" e    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。; C6 E3 O# N7 ~5 T- ?
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。1 _1 Q6 c; t9 c
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
8 K; K# _1 P5 ^- t1 U5 T! F1 x    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
* U- S+ S& ?# n9 K$ k下面结合代码介绍一下它的用法。5 [! R( r9 L7 B- V  _& w
我们来得到下面运行窗口的 IAccessible 接口指针。. q9 S1 f; `( j. l. _6 {: A
: j9 W1 F9 L- a* t
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>% b% k/ }" b; U0 c
<>图一</P>6 W( C! D( B$ F( v! l
<>7 `# o: K# ]- q5 Y* S& o" X4 x
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' ^: m* [8 _% q& Q! R0 N. K$ H
9 K, O  e% P$ i5 S# ^$ I" g. W
<TR>
* g9 t! T" b" f; z+ ~; r* o: z; l<TD><FONT color=#00309c>HWND hWndMainWindow;1 F& x& d8 N# ]
IAccessible *paccMainWindow = NULL;
) f4 `; E( s" U0 x  j% T. z, HHRESULT hr;/ \- ]" U/ a4 X% c9 p& Y: T) b
//得到标题为"运行"的窗口的句柄* X1 I  {3 L. k# _% G9 c
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
- {: V+ R8 T0 }+ t4 u( Y) `1 \{, U% Z3 O. M5 q) Z: c
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
4 g/ C" `1 x* \}
* B' J/ S7 X& H: C. l8 v" Melse6 P0 _" K, A2 ?5 J) A
{
9 s# M& e1 u2 i- f4 u. b//通过窗口句柄得到窗口的 IAccessible 接口指针。: \: {# c  b' J
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
7 d: _9 e, H6 q( t1 m0 R) ^                                            OBJID_WINDOW,
( h# z; n4 v  a                                            IID_IAccessible,  v  p$ v- {) s6 H4 M; a# I
                                            (void**)&amp;paccMainWindow)))/ g: N+ M  G; U( i3 ]  @
{( S6 |- A# t% i
//……我们可以通过这个指针paccMainWindow进行操作。
5 {9 f' z8 _9 VpaccMainWindow-&gt;Release();- U# J" }" \! ?9 |$ e
        }
' ^( m8 r, c9 M0 R  v1 Q. ]5 J}    </FONT></TD></TR></TABLE></P>% R0 y4 S) M3 s( P6 Z: X
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!1 _/ \& A+ A1 M! |& s  c: |
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……2 _. v% o. y) E3 o7 c% f
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
5 g. H* y# J$ N3 l2 R<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
2 ~) ^6 j3 s3 ]6 }& ]) ?8 b<>图二</P>3 ]2 f3 y, J  u7 i& Z, g$ `6 x& T
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
! q( w; e, Z' _6 L+ M% j) I( r! a# _' c<>) X4 N! w( Z; ]- e/ H
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 j" O- N; ~0 p- Y2 Z/ y' a- ?3 f3 H
. n1 }; `& F6 u6 z" X- L' S<TR>; g) k% r# p" y9 ^4 [2 q! D* h
<TD><FONT color=#00309c>Name = "打开(O):"3 b5 _! |2 M% z
Role = "可编辑文字"
- e/ X. y& [( {4 |Window className = "Edit"  </FONT></TD></TR></TABLE></P>
1 W5 O! U* K$ _( q5 |6 Z8 W<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
8 r1 h" f7 f* M/ i0 N# r6 ]    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
3 F7 ?0 F3 }+ @! i) c下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
7 u9 h5 R2 d* F' F" f$ g# v) Q2 M8 N+ B- Z<># @$ F, {1 B& f7 f
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
8 y* p7 a7 z; f8 j& z, d$ ~1 c8 t7 k4 b/ n
<TR>
7 u: A- r- A  a& O<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
5 D+ }4 _4 C9 k* j4 I- E, sVARIANT varControl;    //子ID。
8 r% W& r, _  X
$ h/ A# w$ ]  m: CFindChild( paccMainWindow,
/ M) g8 {* P2 ?3 N) w           "打开(O):",
9 B" w2 e: U  z           "可编辑文字",
# B( z. x+ k8 M7 C  e, N           "Edit", ( T5 J1 z" w0 t1 [5 ~6 m
           &amp;paccControl, ! y  j! W, o4 ]  I0 `* e. h
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
; Q2 y7 S+ b$ ^3 _. V' O) j<>第一个参数是先前得到的窗口 IAccessible 接口指针。% L- ?9 a: Z3 x  R7 B' E. D! K! Q
第二、三、四个参数分别是名字、角色、类。9 X, v" S& L/ t2 [6 w% Z
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
+ H- y0 {: T6 x! @+ T9 l( B<>9 ]( U3 c. H, q/ t8 i& v
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>9 h1 C7 v! Z8 ?, K5 q; k' g

' Q: Z* a2 j! z0 l+ @<TR>
$ J  h1 i4 \7 h<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, & v2 ]0 H" k! x) m
                         LPSTR szName, LPSTR szRole,
. h3 T/ N* K' J; ~, F# N% h                         LPSTR szClass, 8 |5 A, H( R6 R
                         IAccessible** paccChild,
8 X6 U% I% Y( h                         VARIANT* pvarChild)
! ?- S( o8 d! N% B( l{: s; ?3 q* s( S! H+ c
HRESULT hr;+ P2 V8 ]' ^; W; R6 x% `/ h$ g
long numChildren;
* X( t1 F2 C' @( o1 g. `unsigned long numFetched;, o* A- V/ Y8 a2 p
VARIANT varChild;
) d6 H* a9 h# n6 @7 K% p3 ~int index;1 `3 M  c0 a1 `+ Q* e
IAccessible* pCAcc = NULL;
5 J0 @' U7 x9 w2 O* u  P& j( `IEnumVARIANT* pEnum = NULL;8 m" I; \5 ?& l* `5 i. l  q. A
IDispatch* pDisp = NULL;# m! p% P0 Y% ^: Y- H0 K
BOOL found = false;
- H' y# D: _) c5 R8 lchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
/ H: [/ h0 Q4 ^% Y3 {. B- u* p0 p! Y1 f0 {! I$ ]3 Z& }
//得到父亲支持的IEnumVARIANT接口' R) I9 Q2 C9 L% s- a% o- N( s
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);5 |+ c8 c1 }* J* n# T: y. H! H1 D  ?/ t

; D8 h% U# X  M# @; Kif(pEnum)( N, }- ~. ?9 t. C' B( {" N
pEnum -&gt; Reset();" |" f; m. w5 `8 E* J' k5 U
+ P+ F- N9 y4 K
//取得父亲拥有的可访问的子的数目
- d7 Y* M* U  J+ [; OpaccParent -&gt; get_accChildCount(&amp;numChildren);' B6 e( y. D) R+ F5 I
2 n2 @" Y. ]% K; `, m8 ~
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。9 v) K/ u+ m, l5 X1 F
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
  Y+ C1 e/ W- c' g+ ~+ s{
, I0 c1 G: j/ }% p$ _pCAcc = NULL; 8 J$ ]0 K7 o7 C3 T5 T# e  W' B$ c
// 如果支持IEnumVARIANT接口,得到下一个子ID# T0 [9 A  G- T  `
//以及其对应的 IDispatch 接口# ^6 ?, F  J% ?! G+ F
if (pEnum)
8 G  e# x8 v! k" O$ X/ mhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); . L+ q1 }" z5 C( V( U  K) X
else& V7 F: y$ a3 m$ Z) b: z. V: K
{7 A# x7 q/ w# i$ e
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号) K. ^7 W" X. |: z2 n+ r! K
varChild.vt = VT_I4;
* ^. o( e! O9 i2 A: x! O* lvarChild.lVal = index;7 r& Q; j7 {; h8 W) N; |
}6 V3 k1 O+ k/ t& i. }! ~& u
! p1 X' b& }+ r
// 找到此子ID对应的 IDispatch 接口
. m* L' P. o: j5 z, y; I$ i) ^5 B3 jif (varChild.vt == VT_I4): t% \5 _3 |8 J& W8 \
{
, G/ s9 o$ b% \: o8 x: z//通过子ID序号得到对应的 IDispatch 接口
: t0 w! k! a. D1 P, f. i' xpDisp = NULL;+ M: w* v! U0 t5 ]
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
. y: ?/ x6 h; e" o, [0 a! h  E! D}1 m% I' }, p. g( R
else. c! J( y0 R9 X! d
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
; D! o8 O4 x# z; T2 d' ?5 [pDisp = varChild.pdispVal;: H' Q; d2 f) {' u  d

, h, }) v9 |4 @% a. ~// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
( B9 m6 u# |3 ?8 f4 yif (pDisp)* \+ t" [8 W0 [, C. r% v0 \
{
: M  d4 ^7 L0 s, V6 r  Ghr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
4 x& ?! T  d" Zhr = pDisp-&gt;Release();4 v3 n4 k+ j4 n* K
}* ]: K! h3 o! L7 D
! a8 {& o, p: C; w+ \) O' {* A
// Get information about the child
4 H/ F6 t! a5 tif(pCAcc)& |. _5 x5 h0 g+ U9 o
{) l# ?0 C3 y0 V
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF) Z$ d$ W3 k& E6 f
VariantInit(&amp;varChild);) n# h' l8 z( ?
varChild.vt = VT_I4;) Z1 s9 o* z. f
varChild.lVal = CHILDID_SELF;
" @! Z7 s% s: E) h) }) s* z% r9 \7 {9 n% O6 G
*paccChild = pCAcc;
4 c; d9 Y+ S/ t. Q5 g8 I5 S, J) F* Y}, m$ K/ _$ f) D- u4 S3 \
else! w9 Z: H5 E4 f5 t
//如果子不支持IAccessible 接口
2 S1 @2 Q9 F! a) E$ A; }8 u$ Y# k*paccChild = paccParent;
/ M& t( L# `* F6 b$ N8 r" `5 A+ h; j, L- G: V
//跳过了有不可访问状态的元素
. [6 G! Z" e1 b2 }' T- GGetObjectState(*paccChild, 7 }8 G, t$ \8 a+ G3 j
               &amp;varChild,
  G; p7 S* T* C8 z* L. U! i               szObjState, , Q+ Z1 c% H3 S0 D2 }6 K9 O# H' [  Y6 q
               sizeof(szObjState));3 y8 v+ H0 K: g5 H4 P+ w) w, I
if(NULL != strstr(szObjState, "unavailable"))& ?+ G) K) W  T
{- @& o- {6 M8 y# ^% [, B  ~
if(pCAcc)5 d# b0 d$ N1 k; w
pCAcc-&gt;Release();$ @. p4 _6 v/ A
continue;6 o. P; @4 O+ h# H
}& j: Z. i) H- _) q5 N) a' ], m
//通过get_accName得到Name
' G6 @2 v0 R, NGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
( m0 H8 p4 S. A9 z" M//通过get_accRole得到Role/ @' A' z) x, s# f  b- |
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));# z) u- m! K6 n; V- z) R
//通过WindowFromAccessibleObject和GetClassName得到Class7 ~8 Y( D% G- Q5 S: w' W9 H) r
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));4 o) n3 a' s0 l( e. d8 w, B
//以上实现代码比较简单,大家自己看代码吧。
, K; W' g7 {- r; }1 [; @
' Y4 U& F. t& z4 Y) {/ S' O, G//如果这些参数与输入相符或输入为NULL
  y! Z- E* ^: V: D' Wif ((!szName || 6 x( ~) D  w, p& Y- s/ ]& K
     !strcmp(szName, szObjName)) &amp;&amp; * Z+ B- {( Y0 ]7 O* g3 @& X9 ?
     (!szRole ||
0 e% Q% M' @+ b5 L      !strcmp(szRole, szObjRole)) &amp;&amp;
3 U& J8 P. D& j. s. E5 ]% A1 t1 B     (!szClass ||
  v; t  w3 B# b  F      !strcmp(szClass, szObjClass)))! N4 T. A- {; Z4 M  S
{
* |5 z# n3 m( T/ V/ Q5 Ifound = true;
* C  c% I/ N$ P0 L# I2 J: r*pvarChild = varChild;
) v* }( i1 N3 h1 d' j( q  ~break;& Y4 R" O) E) ?7 X" b4 Y0 J. r
}
3 o; Q  c; N, H9 ?* o* N: bif(!found &amp;&amp; pCAcc)2 s' W' i$ i! M! I7 A
{
& s. a0 F# F( A, I// 以这次得到的子接口为父递归调用9 ]/ t6 a. Y+ h
found = FindChild(pCAcc,   f* Y$ o& ?4 x
                  szName,
: _& V* T6 D5 {0 N' P# F                  szRole, 4 y+ c! N2 c% D9 [( h6 r
                  szClass,
9 P$ ~9 K1 p& P8 v6 h/ N2 m                  paccChild, 9 g+ s+ c; V( I- G- {% C& X6 h
                  pvarChild);
# _) P. Z9 ~# dif(*paccChild != pCAcc)1 z4 _- ]0 A, c8 t6 E2 B
pCAcc-&gt;Release();# b& p- F* r. O! \  _
}  A$ O3 j- I; F9 ?3 o2 T1 A
}//End for% r* D* [( q; ]; `

% n: E) _1 p5 ?% R" d& y% n8 Q. `0 |// Clean up" N, c- z6 i. D3 Z4 c* D
if(pEnum)
6 K- n) i3 X0 NpEnum -&gt; Release();
, C6 W9 N! x) x
9 j0 O1 a. D: \% M1 Y0 mreturn found;  g! y1 i5 M0 ^" [
}3 O& }$ F) t* T% k

% _' l/ d8 K  @7 s( {7 _/ `, F// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,; M* ]7 V, Z$ O4 R7 R2 Y2 a
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。3 u- r) \: S# U/ ]
//将这些或数转换成相应的用逗号分割的状态字符串。
+ p6 P; j/ B. |/ nUINT GetObjectState(IAccessible* pacc, % A- g; _) i: A* x( z4 r
                    VARIANT* pvarChild, ) e" f5 E6 l* _4 z' |# E
                    LPTSTR lpszState,
( C! L+ L. E  d+ }                    UINT cchState)$ y9 l1 j$ A  I* D3 n
{
" Z. u9 A) Z  \! ~* p" K6 ^) w    HRESULT hr;
0 E0 K2 T2 d/ O4 V1 i* ^    VARIANT varRetVal;
5 q, e5 |) w$ @2 v; }
1 b2 q4 [. s: x/ w    *lpszState = 0;* m$ A( F+ C3 K
& t! |$ K( X  `) V7 V/ i
    VariantInit(&amp;varRetVal);
5 A" Y: K6 Q+ T8 }1 o) D( i% g' b( a& b- v. q6 \5 I, m
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
% n' `9 G- G8 I1 t  R% J0 b# X' U; B, a( l
if (!SUCCEEDED(hr))
5 o. t6 g. O8 K/ N7 K1 L$ u        return(0);/ w8 e/ T& o, T3 `7 X% @/ W
" u+ t7 |7 G* y0 V9 X) y
DWORD dwStateBit;' P+ u9 p/ Y# ]1 n. ]5 k1 v
int cChars = 0;( n3 D: O5 B8 o  R
    if (varRetVal.vt == VT_I4)4 `4 g2 c2 a5 }
{0 x6 |9 G; O: W/ C; l/ m+ Z4 I
// 根据返回的状态值生成以逗号连接的字符串。
% ?, _( Q; }1 d  F        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; + b! H8 l# \  X' t0 K! `
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; . N) V: ]2 J# Q% Y3 X' `
               dwStateBit &lt;&lt;= 1)
$ }. Q' k1 F7 e0 E: |0 V        {: y# o$ o7 P5 M: T5 |" T
            if (varRetVal.lVal &amp; dwStateBit); X! H( d  @4 b, a( X  ]* z- A
            {; f: N: w# b/ M0 V
                cChars += GetStateText(dwStateBit, 8 u% L' ]# ?( H9 B  q2 ]+ E( K
                                       lpszState + cChars, 0 W+ m5 b+ d% E! J. c4 y
                                       cchState - cChars);
' [5 @5 n% k2 k- P) s*(lpszState + cChars++) = '','';
/ N& c7 I- {: K8 {  g: \9 |            }
  I. Z0 r3 `- l- c, D- ?        }7 e# `7 H# _% X0 n3 s
if(cChars &gt; 1)
. I& Q  d9 Q/ V1 V. D*(lpszState + cChars - 1) = ''\0'';
  ^1 Y' X# y: f7 n: W* c9 w# v; U    }
. |8 b" |% o$ @& }$ ?' A/ `    else if (varRetVal.vt == VT_BSTR)4 _- r/ o5 G2 b
    {
/ w7 V( B: I# I        WideCharToMultiByte(CP_ACP,
- G$ K7 F, `& _( g                            0, 0 }7 W$ q5 y" ~% C+ X
                            varRetVal.bstrVal,
! w1 T3 {) I% y: I4 d                            -1, # \. {# }  n% ~0 E. j! ^1 B
                            lpszState,
( b5 J( X: x$ I/ {                            cchState, ( s- p3 X* P6 f. a  |9 i2 W
                            NULL, + U' n& ]. R. |, Y5 k. P
                            NULL);
8 @2 O2 R4 H1 n8 u- D& x    }
5 R' \1 A2 ?/ C+ P( J3 |
2 m$ r  h, e) t- Y- A% S# ~    VariantClear(&amp;varRetVal);) c: d, f$ z, i$ S4 f$ n

6 f' C- D( l) Q7 h9 k) m    return(lstrlen(lpszState));
7 N5 Q( J8 ~$ q}</FONT></TD></TR></TABLE></P>/ w5 n, ?* y- H( Y$ k
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
7 l$ \: k" U5 s3 d0 f" e( V
  w* d( C3 R' R6 k/ R) a6 I8 e& T0 Q四、在 IAccessible 接口上执行动作
% g/ V7 i& {$ J: x    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
/ s2 L" p1 K( f    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
: o& q2 ?0 y* p* H' @8 A/ X! t4 J    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
" p0 o: R% f4 h1 @* r7 R- W3 ?<>* F9 C& W, \6 q
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
. ?  Z, x! U4 U' x; p" }# L9 l
, S" O' i9 ~% _! V2 A: d8 N8 Q4 \<TR>
/ S; M3 X3 L( v/ V$ ?) @/ s* N<TD><FONT color=#00309c>//在文本输入框输入"regedit"1 n& U; m7 g0 p  p" g. C7 e
if(1 == FindChild (paccMainWindow, "打开(O):", , r- a1 Q! l0 a: Z. \' c0 G
                   "可编辑文字",
- U, [9 u8 I3 {. x; }. r6 X( m7 J                   "Edit", % _) l# E2 o6 \$ X( N* |0 ]
                   &amp;paccControl, & _2 u4 O- B/ G- K& X
                   &amp;varControl))
& Y5 L: ~5 C. j5 z. J. Z{8 v+ V. Q  ^/ t" S3 ~8 I! }" X
//在这里修改文本编辑框的值5 l% v9 l! P: X; A5 z
hr = paccControl-&gt;put_accValue(varControl, $ j" _* F" ]5 x6 H# ^
                                  CComBSTR("regedit"));% ?% n9 y8 @5 V0 X1 M0 p2 [$ h- E# `
paccControl-&gt;Release();
& Y3 N: V* M( NVariantClear(&amp;varControl);3 u+ |9 w- N+ x* }
}4 X$ j  }; W# h' n" B5 j
2 K$ Z, s! n. r3 z7 f  c
// 找到确定按钮,并执行默认动作。
0 O* j+ ^" m0 W+ dif(1 == FindChild (paccMainWindow,
9 S' U% P! i, h                   "确定",
' v8 E0 k; u& J: o/ C$ g% t9 i* \                   "按下按钮",
# r4 P5 H! u4 j                   "Button",
  c  r1 M6 f0 H4 g2 v, B: l                   &amp;paccControl, 1 E. I, m* [8 {  P$ L( V# l2 [9 E
                   &amp;varControl))
. ~2 O; ^) {+ \3 O* M7 ]{
; ?- B- w  Y' C! u: V1 j) t//这里执行按钮的默认动作,即"按下这个按钮"+ ^, B+ c# h: M1 X. k4 z( z9 c
hr = paccControl-&gt;accDoDefaultAction(varControl);
- ?6 z8 |4 C0 z! ^1 \: cpaccControl-&gt;Release();' Y( r. U3 r$ K& W$ ^. Y2 U7 m
VariantClear(&amp;varControl);
6 e( T5 y  o! {9 ?) ^1 Y  [}</FONT></TD></TR></TABLE></P>2 ~" O3 t& \) V6 x) P
<>现在,你会发现已经成功启动了注册表编辑器!!  k1 R$ T6 H: e
</P>
# J4 ^/ }. p; z% X- O: k<>五、模拟键盘和鼠标输入! z  L  Y+ L+ \# [: d+ [
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
2 l/ w% c- c) N! k! b+ `- b    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
8 s: |% T- d# y! B    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
8 [$ r$ h/ e% v( }" |下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
( d( N( w# I3 g6 _9 k<>: V4 z" |5 m8 k7 M/ l
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 C! A; n9 C; M

7 c" B, Z, J1 b& Z+ O<TR>4 n# v- g) j& O6 d8 |. \
<TD><FONT color=#00309c>INPUT input[4];
4 U9 I  l- v3 J+ a2 smemset(input, 0, sizeof(input));/ z* [% C$ k9 L# U

* Q; T7 k6 S2 J: b! r//设置模拟键盘输入
7 o0 _! p' ?$ z0 uinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;. B# q! ^0 s1 _( s
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
* {" j1 q8 X7 X/ |4 {: P% pinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;( j2 c9 o! l3 I8 D- @6 }- ^: e% I

: F+ G* Y' Y$ P: O6 g' e// 释放按键,这非常重要3 H; k) b8 Z9 d. W2 t
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
3 \8 X8 Z: |* }
# I, v; l+ }0 S" }6 o" l$ \4 ISendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
9 K  J9 V: R1 a0 d: R$ I  A/ Y7 R3 c<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
5 L6 o( v" d4 e( n</P>
+ L) g: L( O- _& O<>六、监视WinEvents
3 P# q2 ~7 |3 \' ?4 ^8 l# d1 N    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。6 Q! @1 w% q! d0 L6 T8 f
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
# j/ ^" d6 q/ Z1 K# e    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>$ L5 I( ]' q! s' o. I
<>0 w. w3 R; D2 B+ L) z
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>4 ?8 B2 _1 b+ ^9 U) ~# M

+ z( A0 H( P  q) Y9 G6 c) `<TR>; d- A/ W/ q' [
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
' k( |% Y# v; C0 c3 d{9 e8 L# U' w, q
hEventHook = SetWinEventHook(! F* e$ R, g1 C
EVENT_MIN, // eventMin ID
' ?; {( }! }7 M! v- b- GEVENT_MAX, // eventMax ID) J  l9 M/ W- P2 W: q$ Z' r
NULL, // always NULL for outprocess hook
, N: u) q* d( O7 yWinCreateNotifyProc, // call back function
1 f% r8 }. x% X. _( s! S0, // idProcess
2 k4 T& R- ]% G+ B0, // idThread
: m- L' [- F$ u1 n         // always the same for outproc hook
8 b% W$ M( N/ C5 P- pWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);4 w: L  \( p3 U) d; x
}  </FONT></TD></TR></TABLE></P>6 s1 u- b$ C7 C) `" e. F
" ]& f5 ]* m, g0 {" e4 M% F
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。8 I: U7 g% V. N/ X
下面是回调函数:</P>
0 N+ U9 W4 i- X) c" S; v( O% S& X7 O) c<>  q4 {' y) u/ q$ T$ u' q! V
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>( D( s; `1 h. x0 n0 }9 d, J

- o" r: E" N0 F, M' l; @2 _/ P<TR># O% ]6 @& d: |) s% v
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
& O4 n- V3 i1 o/ sHWINEVENTHOOK  hEvent,
  [1 O" f% Z" j  _DWORD   event,) [& x! v! W7 D5 h+ `5 l- `
HWND    hwndMsg,8 w' R7 o& x! ?" G& V
LONG    idObject,
/ w6 `4 \+ @1 Q2 hLONG    idChild,! I4 ?# I% q7 ^# x
DWORD   idThread,
: a4 v" X! K) BDWORD   dwmsEventTime
, q7 ^7 D- I' i)" B7 R) v# S- z, Z, @* f
{9 Y! |* \! V, ]( q! @
1 \4 t: b% Y. V9 D$ ?" D
if( event != EVENT_OBJECT_CREATE)& {  G8 Z& l/ U) ~
return;
, @" V: J5 ~$ P- |- L- t
. C7 Q6 N! x! e( Z' K; }char bufferName[256];
$ q' [+ {5 L% c8 p8 d8 LIAccessible *pacc=NULL;
4 v3 l. ^2 N8 D5 Q8 M( xVARIANT varChild;
* ^* k8 ^+ M, a    VariantInit(&amp;varChild);
! _8 S4 e6 y$ L  C7 N) U//得到触发事件的 UI 元素的 IAccessible 接口/子ID对, z0 e% h, a) |7 Y3 ^6 l6 O* S
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, 1 o7 u; P7 Q. \" j% N) h" Z0 _
                                      idObject, ( I, i- r% l+ j: W# @8 p
                                      idChild,
5 ^: \* M# B/ G/ [* `& u4 s                                      &amp;pacc, , z. |& u! \! l0 h
                                      &amp;varChild);
4 R* f) b7 e2 V# h! W
+ p* h9 K9 j) I1 ~( Z' ~* u2 q* s6 gif(!SUCCEEDED(hr))  R* I' g9 j- Z0 [8 c6 [0 \
{( J" n* P) F/ a6 O
VariantClear(&amp;varChild);, u3 o% n2 n6 S6 O: T1 j* u6 ~. X
return;
$ J9 d1 C3 j/ i+ G2 m) ^/ p}
8 u/ y# ]/ }! N& N6 |3 j//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。- C5 L: q. y( c, P
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));! A4 z3 x/ Q" D; @/ u
if(strstr(bufferName, szMainTitle))
* T* |  G. r( a& P. `* PPostThreadMessage(GetCurrentThreadId(), , c* I, D' H2 ]+ r: [8 H
                  WM_TARGET_WINDOW_FOUND, 0 z5 u( c' D1 [9 C, Q& D2 K% y
                  0, ' F1 {, G3 e( \6 \+ T. ?
                  0);# l/ P( o2 s% N2 z' e. u
3 Y' e5 Z4 D/ L  Y! T
return;; r( L! g$ ?( i2 s3 o. }
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
. j% I) {: W7 Q# }& p9 b0 k" I8 R$ e& z4 C7 O4 d
附录:
5 w% X- ~0 w9 d$ o, @/ O- }2 j' \6 V5 @( K+ f% \
关于IAccessible 接口/子ID对:1 G! ^' i, w; }) r- x" P4 Y
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。+ m$ u7 h6 _5 j, c9 |+ m
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
7 g" c0 F1 E* m4 E% @4 r" F" S    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。; }1 c( y4 S) B# M& s2 _8 F. n
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。, [; |! d9 g' x, e" D( l- Y4 r
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!/ t2 D8 Z4 W$ T% B

. Z) _- u; \' _: Q* Q5 a! k" R5 x注:
1 b, x8 _, [% H8 Q  Z2 }    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)) X1 @7 P. T; V" w% u) a5 t
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。, u; a& Z$ v4 p+ W1 ]! l/ K8 i8 \
. f+ U. ?7 a, t4 M
参考资料:
5 P- T0 w; _2 ]/ y9 l<UL>
1 }3 N& q1 {2 X* o% ]<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
' q% \# z: T, a<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




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