数学建模社区-数学中国

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

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>+ I$ ^' x. T5 j9 d0 Q- W: d
6 G/ i; z3 V7 E- K: T
<TR>4 D* _& C0 J; y0 [; a3 }0 E
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
- m9 ~6 n6 |# A原 作 者:崔传凯
* D  P- F9 I- r$ {& ~7 t$ m; `原 出 处:vckbase
7 m8 h* q0 v0 @+ C5 v! V2 w% E发 布 者:loose_went
, ^9 B  c4 v9 N$ ~  |发布类型:转载4 E1 Z7 `; Q, ]8 J3 ?7 @
发布日期:2004-11-12& z( j0 o2 c; Y
今日浏览:6
# Y& m+ ^$ V. \  ~, }0 M总 浏 览:234
. O1 p) _; |5 D</TD>
: s- J& b0 @+ z0 W( {<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>
9 Z* K& u6 u+ r<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
4 F% I+ C. E! Q7 T9 ]; E" f8 {2 @! B% m0 l- u% E% Z
<TR>( I3 g, H, T3 n: ?
<TD vAlign=top align=left bgColor=#ffffff>
3 A1 i1 ?4 [% n6 w
7 N( E+ V9 Y, t- A2 K<>
" G, b; `1 T3 k4 |! L( t( P<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
* G7 C8 J& X  R1 {7 _1 F3 C1 C, ^& b/ }) s7 E. ]
<TR>
4 t( g2 A6 t! J; b4 y2 W3 r, `<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 `7 i  o( e/ ~
way accessibility aids work with applications running on Microsoft Windows?. It
2 g$ h" b# v0 _provides dynamic-link libraries that are incorporated into the operating system
! {  u/ A+ n" q# M( O+ Fas well as a COM interface and application programming elements that provide % s; R$ s" p1 s" P7 C; {" c, t
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
3 y+ k9 z4 ~: e- m( C0 V$ r! C<>一、基础
# N; k; {# U; q( Q1 f* j: I$ \    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。- [7 @# _& X: Q, n) H4 O4 ~1 Q
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。5 Z; s; e) F* S; Z, ?  D9 d
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
, N* n  L- e8 Q5 `<>二、Active Accessibility 原理
  W( O0 e3 Z$ G8 c+ x( K, W( c# y    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。1 h5 k6 B6 L0 F3 Q- d  e; F
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
  r9 D: g3 N# n, H    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
) A/ s7 p+ s# C. T    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
/ p0 e2 g0 y; ]/ O    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>" }0 G+ B0 Z$ q" Z
<>三、如何得到 IAccessible 接口指针0 r; _) J. `  w3 ^' E: t
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。" \7 y3 Y( ^& I4 ]& X
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
( Q7 c6 O- P+ L* D. E8 z, c+ ]    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
3 h/ n2 E. e( [; r    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
8 l$ L# |- i3 l4 d4 k2 g    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
; v' R) q7 Z' T9 j. w    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。+ X4 ]4 [( a, Y$ @/ ]
下面结合代码介绍一下它的用法。% K- @& \1 U7 B
我们来得到下面运行窗口的 IAccessible 接口指针。. `* c' Q$ O! }6 {* H. d( ]! P
/ q4 S  I4 O* y* v8 _( u# Y
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>( U7 Z- j3 z$ Q6 G
<>图一</P>
/ }5 Z! a/ O0 D7 _2 W<>8 \" w' `* E& I6 U7 _  X) W
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>( F' n$ ~& g" y4 q8 E
% F9 r+ u. {+ v$ a& k
<TR>
. ~6 |) C! C( ^1 J" l2 W! d<TD><FONT color=#00309c>HWND hWndMainWindow;: d/ [# a  i; U2 z
IAccessible *paccMainWindow = NULL;
5 {4 C% x$ S6 i- B, g. L7 oHRESULT hr;# e& n& l0 v  [" h  r& B
//得到标题为"运行"的窗口的句柄
2 z5 }: P, v/ P. s, B8 }if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
% z4 {! m( c# w8 V8 m{- ~! z& V4 ^$ E1 l0 a* ?
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
8 X* ~- }4 x( t1 W5 x* B6 o. m+ f}
6 k" ]% L: X9 @' q: a6 p! D) [else
/ Z& q* s9 c$ V- T8 M{
0 l6 Z- O, l' z- e( f- D//通过窗口句柄得到窗口的 IAccessible 接口指针。& M1 m. O: G; n( F
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
! B9 w& j- t8 A/ A                                            OBJID_WINDOW,
7 z: U7 i  r2 Q; ^1 Q                                            IID_IAccessible,
9 f0 l* ~# ?- L7 [8 G) I                                            (void**)&amp;paccMainWindow))); f" o. Y5 S: R+ S) M9 \, i" j9 Q' B
{
% k- q* V9 L  P/ o( j, L//……我们可以通过这个指针paccMainWindow进行操作。
' m' F8 j, h" G8 j0 B0 h4 LpaccMainWindow-&gt;Release();6 L* ], P' @* @1 W; c" M
        }
7 ^  q$ f' K' D2 Z7 B' c# c}    </FONT></TD></TR></TABLE></P>; }* c9 m( L3 B7 D4 c; V2 M/ O
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
: Z$ ]. Q$ N5 v" ?    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……% j* l6 Q* P) \' L3 N% A
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
% ~$ Y) c  h) N/ }/ E5 p) r<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
& u* c2 e; y6 \$ K. `& \<>图二</P>
) d9 Y1 X( o! o5 O. C, w7 Z7 G<>我们现在主要关注的信息是:Name、Role、Window className。</P>$ @8 }  F" v1 }) Q2 }; W
<>
( A; l% d4 w4 P. X8 T/ L1 M<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
* J1 u- a7 ]4 a0 F3 F: j9 Y8 `" [2 q
<TR>- ]$ S" R# T3 H  I2 c. T
<TD><FONT color=#00309c>Name = "打开(O):"
3 W  r* y( ^/ l' M% xRole = "可编辑文字"
4 k$ ~9 E6 |+ p+ `/ x9 TWindow className = "Edit"  </FONT></TD></TR></TABLE></P>+ @6 j2 U( s! h6 S" h8 A
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。5 o# W. L. S2 O& m
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
2 O, O8 n/ Z+ {3 `- u下面我们开始取文本输入框的 IAccessible 接口指针。 </P>* R. J' }3 Z: I: P. H8 R
<>: \9 ^4 C1 f% }  d7 W: G( J
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>4 X6 i% d( H' p# s& i

7 l5 I5 A: p! f, v<TR>
5 d) U0 K; E9 l/ A- [' z<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口7 p$ G# p$ J1 B2 O2 o  P
VARIANT varControl;    //子ID。
- A1 o& e, k) \( H* {1 v  I, W! s; \3 t% s1 z
FindChild( paccMainWindow,
* \. c6 M3 u* z9 Q& [           "打开(O):", ! r  {" {% H9 w4 d6 N
           "可编辑文字", ! A/ _( m$ v( M4 T$ b
           "Edit",
5 Q: Y6 e. s4 J) \           &amp;paccControl,
9 `' J, Q1 D  W1 o: \* r: y. e           &amp;varControl ) </FONT></TD></TR></TABLE></P>
* n$ f7 R+ f# Y) `+ x3 K% p<>第一个参数是先前得到的窗口 IAccessible 接口指针。0 P' G5 Y# p; x4 _) f0 R* p( n
第二、三、四个参数分别是名字、角色、类。; F- `4 r$ z  ~) U# Z) t. D" I
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
- U1 }4 S0 `5 u5 j6 K2 k<>2 [& E/ d; F1 O# I3 }2 M/ S' R
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
6 V) B1 w8 J3 k4 _7 F2 r
9 t, d$ t; G/ ^; X<TR>! ~+ M' b( ]. p- B
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, 9 W% a- d5 z0 V7 Q  \+ }* |
                         LPSTR szName, LPSTR szRole, ; X4 P, D( z9 O6 v' |# q
                         LPSTR szClass, : t. U6 v! b/ l6 ~1 @, q
                         IAccessible** paccChild, 4 t  p6 e' r  |/ ~( g1 {
                         VARIANT* pvarChild)
2 X3 d2 K# Y& E* Y4 [4 D{2 K9 t& `" l% j  o) ?
HRESULT hr;$ J7 G6 j  s2 u& \
long numChildren;
; m! b; Q2 b. i" A  _unsigned long numFetched;
0 \4 ?6 `9 r. Y/ J& xVARIANT varChild;5 l8 ~) E9 M3 b
int index;: i7 ^+ V# @$ z$ u8 R( d
IAccessible* pCAcc = NULL;
2 z9 E( O$ M: y& B: S; I7 S: g1 \IEnumVARIANT* pEnum = NULL;
$ V: L0 v0 |- p+ }IDispatch* pDisp = NULL;6 L9 |+ U  X) ^' I
BOOL found = false;
, c0 `2 U' ]; X* `0 f5 Hchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
2 u% O2 n4 X1 ^2 P! j2 H
: @' `9 o% H  I  Y, @//得到父亲支持的IEnumVARIANT接口1 W% O7 l6 w( M' _% Y( _
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);/ A7 P/ s& w- D# `4 |7 c

9 @6 P: m8 ~; d- j! i+ hif(pEnum)9 H0 O5 L; P5 S& a
pEnum -&gt; Reset();
$ L, I' ]/ d* G+ H2 k# j, V! G) J- q7 H8 E/ F2 c$ U) |
//取得父亲拥有的可访问的子的数目7 v" m* C4 @1 M/ s3 P- K( G0 _
paccParent -&gt; get_accChildCount(&amp;numChildren);+ S, N, `! ~+ r

+ p  i2 `& k8 u: N$ N+ u; x//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
/ N6 \5 ~4 W5 F2 n0 tfor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
& n* w1 N. [: t$ @' n{
  j/ U2 `8 Y& }5 g. rpCAcc = NULL; 0 }7 B4 m, J6 v1 Q5 q( n; P
// 如果支持IEnumVARIANT接口,得到下一个子ID
" ~" ?. G" u9 r7 V# u, y) r//以及其对应的 IDispatch 接口
# d' K3 g) p6 H  M5 lif (pEnum); l9 p4 B( ]& t
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); ! ?. g5 O3 c- j) p# u( q
else
9 L- L- w2 T, U{
& f- F) S% j) t5 G# }' Z//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号) P1 L/ d) p2 s2 d; ]! ?
varChild.vt = VT_I4;
" ]. m9 t3 h) X: CvarChild.lVal = index;
: U5 G  L+ x1 H- [& R4 e3 y) U  p}: w# @$ u& y, J3 {
" f! [) ?6 ~; O' f9 h
// 找到此子ID对应的 IDispatch 接口. j8 n3 S/ R! _, j
if (varChild.vt == VT_I4)
1 U; g0 o" d: D( V{
( h! o6 y, k* y1 }- A//通过子ID序号得到对应的 IDispatch 接口  W; U  x$ L: u0 H% `  f- d7 s
pDisp = NULL;
+ H- {. F1 f) }4 W- rhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);6 T4 {% [: V  ]
}& f4 N; G7 I. U: `5 l: w
else& z$ I; |+ N5 Q# b# q2 d. C
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口! q0 G" ?" \( \$ h$ r
pDisp = varChild.pdispVal;
% `3 p  d  ^2 a4 ?% Q. r/ c" z" g
* |, X6 c  H' f( k' x7 T// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
9 t* x8 B0 {5 m) }: Z/ e7 Aif (pDisp)
  a7 o, T. c, ?, T; c& E8 r{! f! T2 I, g0 S# o8 W, s
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
  q' J9 |5 z8 _: `9 H; whr = pDisp-&gt;Release();; l3 t2 @( [% S0 F+ w
}8 X) G7 V; c8 `$ q) T
' U4 r1 }6 h, X/ u* E/ i9 x
// Get information about the child
0 T( M  O( e7 C- z" Uif(pCAcc), P$ Q, g9 R0 ]. C# ~6 Z) x
{
* G. {$ j. }2 c/ r, |0 y//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF# P$ K6 t  }) w8 z4 K  H) p! f
VariantInit(&amp;varChild);8 S0 [  f8 W, x; L- ^2 t1 n/ {: L
varChild.vt = VT_I4;% f) h5 u8 f, a+ L
varChild.lVal = CHILDID_SELF;: D4 d  x4 `5 {+ r! Q- m" m

: y, K/ J- A( g- b5 B% l. f*paccChild = pCAcc;$ [2 D9 H8 V0 V/ s+ x, s
}, t$ [& d8 v8 l3 W
else
7 E3 Z$ }9 y4 J5 v) A% U( _//如果子不支持IAccessible 接口- x. _/ z# }! Q, |% g
*paccChild = paccParent;
; s8 x8 G, k6 W+ |# g; y  F+ ^" t2 m; N. V4 v
//跳过了有不可访问状态的元素6 Z* D& H( V( u5 g4 q
GetObjectState(*paccChild, # A. V2 M# Y, h0 D
               &amp;varChild, 9 [7 n9 I* Q2 t* @) W
               szObjState, $ x9 {$ O6 J8 d
               sizeof(szObjState));6 S2 D5 I1 k4 j9 k7 ^: l
if(NULL != strstr(szObjState, "unavailable"))
) I5 ^: T; Y6 U3 u{  S6 y, i% z- }" P0 P
if(pCAcc)! q5 M4 z( r4 P; s
pCAcc-&gt;Release();4 d9 H# [' y7 K$ Z) \
continue;
2 g6 j4 Z- Z$ s0 Y4 X: H- V" |3 T+ E* p}
1 }, A0 b1 X, i5 P. v0 T9 W8 g//通过get_accName得到Name. M* I% d, Y# y8 a" [" j3 J
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
+ L3 |! A' x: `) }# y1 ?7 ]//通过get_accRole得到Role
: r0 n6 g8 G7 A) p; r( Q1 e5 UGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));/ t: t8 J1 j" m- D
//通过WindowFromAccessibleObject和GetClassName得到Class( q' u% b0 M6 g0 @' m) v
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
; q: ?2 e1 i! O( K% I7 |& |3 L//以上实现代码比较简单,大家自己看代码吧。: O5 z0 X+ c) Z/ w7 r  A# u
. c1 s: U; ?+ ?1 Q* c/ I4 r
//如果这些参数与输入相符或输入为NULL
# l4 f! V, Q' Z# G# Gif ((!szName || + O7 Q5 g+ b  ]8 o1 Y% }
     !strcmp(szName, szObjName)) &amp;&amp; & f. y1 T- r( X: y) {, J
     (!szRole || $ G) a3 m6 H  E- e
      !strcmp(szRole, szObjRole)) &amp;&amp; : L+ _: u' t% E2 |# {
     (!szClass ||
; c7 l$ c+ ^1 p9 `  h+ F      !strcmp(szClass, szObjClass)))
. T2 U& ^$ z& B2 Q{
5 ~( \, X  O9 m4 {4 a2 B6 k* vfound = true;
# M1 G5 ^- m; n7 D. X- E*pvarChild = varChild;- W$ H$ O0 I3 ]$ z! l+ a
break;
% w. l2 }/ u1 u}
3 V! x! c7 ~6 b7 J& h' T/ vif(!found &amp;&amp; pCAcc)5 T& b0 h, V+ M0 e! G
{7 `; z, e1 F1 }8 W  b5 H6 O
// 以这次得到的子接口为父递归调用7 E5 P9 q" l7 L7 h8 u2 T( E
found = FindChild(pCAcc,
, W. T0 h. T) m/ B7 s9 x! u. [1 n" Q) N) T                  szName, % g4 W- U( s/ q: M
                  szRole,
8 w' D/ ~  @$ c4 N, H                  szClass, # j$ I- T; _* P9 B4 r- ~
                  paccChild,
1 c& S  X6 y2 W& C+ h/ h                  pvarChild);
8 Y! B1 G* q. f) q1 g" ~if(*paccChild != pCAcc)
' p5 z, W$ l6 ]/ k" R. K( j& `, P5 tpCAcc-&gt;Release();
  o; t7 f9 x, R}
. P& z' E' D; ~+ C' G}//End for
! \7 J# x- q( i8 P& q. k
% h9 T0 T* ]; N" \$ U/ L" P4 n// Clean up
) K, T, V2 ~# f+ j6 Lif(pEnum)! t% `) R" h' t
pEnum -&gt; Release();
* t7 G0 k7 q+ {" o- {) H/ W9 Q# C) |" ?; o; @$ \5 O  o# h
return found;
! j& G9 L+ a$ G/ r4 a8 F' q) v}
  A. p" L) v, Z. m, k, u& {7 A4 }* a( m  n
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
8 b+ Y& |' _& j) L0 j) A//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
; j. z# J7 |# U3 m/ ?//将这些或数转换成相应的用逗号分割的状态字符串。
! Z8 u2 T2 U$ v) w3 W3 dUINT GetObjectState(IAccessible* pacc,
1 X, [* ^  n# h8 k6 v, _4 ~                    VARIANT* pvarChild,
* q2 F) Q8 a+ |1 L8 @                    LPTSTR lpszState, 2 Q9 ]8 K# [! S8 P+ h
                    UINT cchState)
7 ^: `, F: S5 p8 u0 u{! Y8 \5 y. x. Y( n4 _* ^
    HRESULT hr;
0 L2 X9 s. k- ~    VARIANT varRetVal;
' S2 e& Q: f3 |% \1 g4 b; u/ s9 h( c' k+ n
    *lpszState = 0;
, u9 i5 K4 |$ \# m0 B: q( I5 y/ R
* f- ~2 ~) v  p5 N, g& P    VariantInit(&amp;varRetVal);
/ d- \& h- w  Q4 i- t9 b8 k& C' R" A  n. m2 n
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
* G4 |$ u. r3 v3 k- B
/ Y$ T0 p$ H+ ?/ K, Lif (!SUCCEEDED(hr))$ U: R* w1 x( t6 X, ?: L
        return(0);4 Q; t" h6 c+ A( L/ W3 k
" M6 ?- A/ s- a* s
DWORD dwStateBit;" F- M* ^& l; Q/ b  d
int cChars = 0;
7 Q- W1 |) k1 U3 Y" ]# G    if (varRetVal.vt == VT_I4)5 Q) w$ O! c$ C" U$ B
{
: U: r# X: u/ l// 根据返回的状态值生成以逗号连接的字符串。4 t/ _) N! ~- L/ q; `
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
0 v( d% U2 y. S& \- _( `; |: M               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; 3 d1 |, S3 T) g; ?  v( N+ R. x# _& Z
               dwStateBit &lt;&lt;= 1)
1 r$ H- u  Y) ^        {
: m8 g" h! X. V) P2 T            if (varRetVal.lVal &amp; dwStateBit)' c: q. T; q+ m4 u' j: J% x# N! B
            {
3 q5 g- i9 q) m& _% C4 l                cChars += GetStateText(dwStateBit, : e5 G$ G0 i9 e, _
                                       lpszState + cChars, / l7 k; B: x) q  R: I# s
                                       cchState - cChars);
$ C3 j! q) x$ h" x9 u, U3 j' k*(lpszState + cChars++) = '','';* E: ^6 A: u" b! h% K
            }
: r4 v6 @- r: P9 X        }' c. u( ?3 y9 R: |% Z2 S5 @, v
if(cChars &gt; 1)8 |/ E; ^( a" l! ~/ K/ h/ C
*(lpszState + cChars - 1) = ''\0'';$ _) j8 O7 ~( u0 M( P; \/ h
    }6 T8 H/ F: P5 _! O3 {/ B( v
    else if (varRetVal.vt == VT_BSTR)  z- K! i" B* [
    {
: b! _9 Q% w% k& z3 O  n; z( O        WideCharToMultiByte(CP_ACP,
/ Y3 U- m8 v: b2 d) ^3 V                            0,
3 a8 M0 W8 o/ E# c8 R                            varRetVal.bstrVal,
4 O% f1 E  x7 e2 R# f& j) K# A                            -1, 5 s  _7 {2 P! f! [# y! F9 D5 ]
                            lpszState,
. i0 @- J: X, C: o* _; W8 @                            cchState,
# P* x1 E5 \* J8 H9 N) a$ B7 f/ q; y                            NULL,   F; b1 A& t  `  ?* o
                            NULL);# Y5 S5 M. k1 u' ^
    }  G! M  L3 z, k6 G8 |9 ~( {

& d& u. m: M4 H6 ?( r( u    VariantClear(&amp;varRetVal);$ u0 }; u! \  l. j- r% x* e5 g
5 d! B- H/ \$ W9 ?& P
    return(lstrlen(lpszState));
, b# ~6 c9 q5 n  ^: j# h+ r}</FONT></TD></TR></TABLE></P>. m+ r! O6 V. F. D  Y: p2 C" t+ r1 Z
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)6 R0 ]' H' ^% d& {: D

' K( Z/ T. u, Y4 j  n( _% \四、在 IAccessible 接口上执行动作9 o+ S1 X" W6 ]* U: E: ~
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。1 U  ?) `; V/ v; I& B; ]$ E) v
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。9 w" s+ g+ e- \" u/ [3 g' q
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>0 X; H* @$ B  C$ G% Z
<>, z" e- S- d* m: T7 ^" ?& N" [. I: s
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
2 W2 j6 R6 O4 H1 Q2 l
2 |) K2 c) s$ ], O9 {+ \<TR>: U0 d- s/ Q/ Z6 A5 B0 A
<TD><FONT color=#00309c>//在文本输入框输入"regedit"- ], |1 A5 S  @# W( e, k$ I
if(1 == FindChild (paccMainWindow, "打开(O):", ) u! G, x1 e0 K) }! ]* l: D/ C
                   "可编辑文字",
1 L' p3 G. Y$ w6 H  ?. i                   "Edit", * H) ]- s5 Q4 t+ Y. x
                   &amp;paccControl,
  T$ Z" q/ s" v. f; r- S                   &amp;varControl))
9 s! q0 m1 A- k* e; U{5 _. k: h, {2 ~0 s5 t. n% D; C
//在这里修改文本编辑框的值
4 K$ K) U: b, [, z# z% C# n6 hhr = paccControl-&gt;put_accValue(varControl, % r! q+ v( ~, t9 k; o
                                  CComBSTR("regedit"));7 L3 R- S( U# O# }0 c% O# n
paccControl-&gt;Release();
$ V0 x: {, ~8 Q$ V' w* {) AVariantClear(&amp;varControl);5 b' T; F' N: B/ N  I
}
4 M, ~# x) @$ m
  B! u, ?/ _0 C$ `9 b* l// 找到确定按钮,并执行默认动作。$ I/ ?  A+ a6 r% c' N
if(1 == FindChild (paccMainWindow, . ~9 u; Z" u/ j# k( I
                   "确定",
/ F) m# v6 O# T% E                   "按下按钮", 0 L* x9 S3 ~' O: u0 E( n$ d
                   "Button", 6 H7 c7 h- ]" Z, N2 F
                   &amp;paccControl, / G& \, P: Q' |' V# @, P
                   &amp;varControl))
) {4 R5 H! O- c{
# V. r  U. B5 I) ?- \6 c% M//这里执行按钮的默认动作,即"按下这个按钮"% s3 o. Z# s4 \. C
hr = paccControl-&gt;accDoDefaultAction(varControl);: E  v' q4 m5 _+ v1 C
paccControl-&gt;Release();
2 e' W# o* e( n$ j4 ZVariantClear(&amp;varControl);
7 }+ \! D, F6 G6 Z% A" Z5 S}</FONT></TD></TR></TABLE></P>
1 l3 D7 u/ _* t2 F<>现在,你会发现已经成功启动了注册表编辑器!!
5 E" H$ C& _- {</P>
; d3 s: g6 [6 A: _<>五、模拟键盘和鼠标输入" V* ]' x0 v" u$ @$ ]
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
5 h- ]2 B5 f5 C/ ^2 b) O    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
3 ?% x+ I6 ^$ b( ?    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。8 ~) L/ J7 J' p8 H! D* E
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
' d  }1 M; X+ R7 }( s<>+ R; [7 C. E" S) V' s
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
6 g, ?, ~( B& M3 p% M3 {
- E/ J! {' u; A4 }* u<TR>  g. ~" i" \  a9 z2 M) a
<TD><FONT color=#00309c>INPUT input[4];
. x2 O6 n; N5 g% Cmemset(input, 0, sizeof(input));1 v* E. p( E' r8 E9 w6 I

7 Q5 N- D. I$ h) d& _//设置模拟键盘输入/ j$ ^/ u. A2 A4 T( A
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
9 i: }8 e3 F' H! T# jinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;/ C" z  N, b: P
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;" o0 ]; I! _+ x" @4 T6 I& n
  H! P$ _  [4 I4 q. s4 ~& V
// 释放按键,这非常重要
# [5 F6 @2 D' ]4 N8 iinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;2 {8 k  z% o, k9 j2 p1 u1 c; o

! s+ Z8 ~0 E0 ^8 iSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
; o' T3 A1 S" L. i, ?  |4 ~! S" S<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)- B# m) x& I. f2 T2 w
</P>2 _) G' G9 f! X1 e" e; A7 X# Q
<>六、监视WinEvents2 M/ f7 M, V& i1 u% K
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。* c$ I; H' V& {, v
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。. }; D7 V7 i+ I* y7 B
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>, W* R& \  x; v0 x5 [. E. X
<>
" n# \; q& c2 A) f3 |( X<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ P. w4 ]  {% @$ P# J

8 ?) l( f1 p6 Z<TR>
$ b  L" i4 @: [1 |2 z<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
1 |" S  g0 }: M2 z{2 K- q* z1 _8 ?5 {
hEventHook = SetWinEventHook(& h: i% Y+ D- s3 a
EVENT_MIN, // eventMin ID- {% o1 \/ r4 d5 r4 ]& N
EVENT_MAX, // eventMax ID
! Q6 h; `+ `9 y$ ^NULL, // always NULL for outprocess hook/ Y* N. E+ ?) U& U7 N& S9 ~
WinCreateNotifyProc, // call back function
  z7 u$ ]- i3 A, C0, // idProcess
6 k, T3 @* r  ?. q0, // idThread
( H0 N8 N3 A: l9 D/ |         // always the same for outproc hook) K* h# G  x9 I! V/ `  Q
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);$ o4 I! J6 K& f2 B* z4 ?
}  </FONT></TD></TR></TABLE></P>
. ~8 s5 l0 D# j3 K+ l( ?- r* f( Q' i& h9 g3 J! A$ y1 j& v' u: t
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
; `: a, {2 L) D2 a6 ?# e下面是回调函数:</P>2 }5 H8 R; d1 c* `
<>: L) ?6 q7 G: l& `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>" T8 W/ [  J" ^% \0 ~0 ]8 [

# j+ X" C- d: ?+ P$ q& X6 M<TR>% I% J3 g& r2 n5 x) Z9 ^- S% [0 ~# @
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(4 r1 ?  V* {& `4 U8 A' j8 n' S
HWINEVENTHOOK  hEvent,
2 D$ D, s2 n9 o3 ^9 P5 Z( o: \DWORD   event,
6 I+ U5 I7 y: m; b$ X! Y3 jHWND    hwndMsg,& g+ k! M( N0 Q# {7 m+ T
LONG    idObject,
0 U8 d, J+ [! O( rLONG    idChild,
3 \0 h. J9 A* U' b  l% uDWORD   idThread,
+ h: I& u+ S- I& a  S1 L4 fDWORD   dwmsEventTime2 T! d# T3 B6 r4 u2 }* a" C6 S) w
)
0 c8 g+ Z" w5 ]( K9 m" X{
8 ^5 J) H2 G- G: V/ N/ l0 r/ D  w3 ?" J- F
- q' {3 e: t$ e* `3 V+ F6 i2 [if( event != EVENT_OBJECT_CREATE)# J3 d9 _& O' U0 F0 c6 |0 F
return;6 U' U2 |& i; r

/ T8 \: T+ P0 V+ \5 R9 ~char bufferName[256];- z7 U  G+ w# Y5 {
IAccessible *pacc=NULL;) d% ^4 @- F* |$ K+ \( F/ S) z
VARIANT varChild;5 R. B* u' j$ ]: N% Z4 P8 q
    VariantInit(&amp;varChild);
& T7 Q% a) V6 w& h6 y9 Q1 @//得到触发事件的 UI 元素的 IAccessible 接口/子ID对. f% E9 W. M0 m( P, k6 w" r) f
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,   f8 m" V0 B% C& J4 b( s
                                      idObject,
- Y  ]+ ^! g9 f5 }2 T$ ~7 V                                      idChild,
. Z* q0 f; J8 L% X& G) c2 |                                      &amp;pacc, + `% J" E5 E9 {
                                      &amp;varChild);, i) y( W  j5 e% I( R
9 K6 i* {" ~! A  n0 i% |3 e
if(!SUCCEEDED(hr))
1 m  {2 ^. a" M{- K7 s" i7 R! h* E( m2 U$ J# n. ]
VariantClear(&amp;varChild);
$ J$ m! d9 _& k3 Z5 Q' K, @: p& C$ ~return;
& @4 R" @, b8 P+ K. A% n9 u}
1 A6 ~* M. y  [- F' t/ z! f//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
) K! x* ~" h/ z7 {' V0 mGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
; B4 ^9 H  t3 w$ \) j/ N7 k, R7 k* Vif(strstr(bufferName, szMainTitle))0 {# X0 k& r) G, ^
PostThreadMessage(GetCurrentThreadId(), - N$ L. s' K, [5 G8 `# a6 }' n
                  WM_TARGET_WINDOW_FOUND, 3 [  W2 T8 s( \4 K4 p8 c
                  0, + }3 i! g, k& d( r
                  0);
" Z  l& Z$ _, E- Q/ Q; E# F$ A& [2 u. |$ i  K$ m
return;' V6 j; X( V/ K& g  G
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
- O8 w, c, L% L3 k, E) c7 M' h, n1 M
附录:) a4 e1 {- @) ^8 E2 c# p/ C

: A2 t( d- H) b4 j( M0 o& D关于IAccessible 接口/子ID对:
* y0 K6 R% z9 j3 w; Q    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
' A( l' B4 r3 Y$ K    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!4 H9 o6 p3 m* |9 X9 r' x
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
' ^2 p+ h/ k  c4 S1 [+ C    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
' h$ g4 C% a) L$ _    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!2 k( b4 r/ x- b8 @. D

* o) ~2 }5 q7 p8 N注:# v( v6 b% W9 t+ e
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)1 I+ ^8 o% w$ e- M% h
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
0 |* y# V# M% ~' M+ _: |
! J1 u8 F1 G$ {; i参考资料: ! D, o) S6 d1 L$ \" i  w3 {3 o
<UL>
, N& u# f. U& R4 `' d/ U1 q8 }9 y<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
. u2 X0 e  z6 C/ t  [6 ~1 \<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




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