数学建模社区-数学中国

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

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
7 \$ Z0 {6 ]" Z, ~3 J5 C4 r' L; K
4 e( W% T0 _' J+ |8 \6 U<TR>! v- Y8 h! k7 g! G/ }3 q; s
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>$ z) J/ \+ ~& g1 Q3 e
原 作 者:崔传凯
) D) U) R8 B: x, c9 s/ e$ G% ~原 出 处:vckbase) S. f# m& E4 i1 j# b  d; g2 O; i' @
发 布 者:loose_went
6 f  t8 O3 p$ C& a! u发布类型:转载/ R. ^2 C; |/ j* j) c
发布日期:2004-11-12" q# ^5 q: Z. m- n* x
今日浏览:6% j' ?- M% L2 Q  q% ~4 S
总 浏 览:234! i# t  F4 `* v
</TD>+ l% h6 A0 {* K$ @6 A
<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>1 ]2 G7 C4 {" S' E- g# @+ q
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
0 n( B% b" y2 i. Y3 n; C! n  f+ |0 R# e, V
<TR>+ c- r* t* V/ `5 @
<TD vAlign=top align=left bgColor=#ffffff>$ d' g7 l) ^" v1 I# P

( [% ~" H$ |1 o% y6 }<>
4 Y7 _$ R' r5 ^0 ^<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ \! K7 p0 c0 {8 w' S
5 p$ ]/ O6 t  z" F. y  A; Z& |5 A0 o
<TR>
% o2 ^1 ?( s& G+ [<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the ! T! \) K  J, p
way accessibility aids work with applications running on Microsoft Windows?. It
. K6 s5 j4 f- A7 j6 D! b/ Cprovides dynamic-link libraries that are incorporated into the operating system " H# R" g, ^! u7 @" U' D
as well as a COM interface and application programming elements that provide
; s0 }7 S- ]. Q7 ~; nreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>9 `8 F4 c. Q+ V4 C) ^
<>一、基础+ V' f* i) j: a. ?( r. u  ^
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。% B9 @1 {: y1 ~1 ]- x, [' l: x
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。' }1 U$ |. \8 F  O  ~
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
: R2 `, l9 N0 P  r' a) W<>二、Active Accessibility 原理
5 h" D3 {" V/ v& Z- D    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。( M# z& b. |7 T; P6 r; n! t
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。6 |3 C9 W# V3 n8 \# o
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
* Z) @& t( k. U    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。: w$ L) F! w! ]# f/ H3 J
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>3 g) ^3 d5 h5 t% P8 g0 ^: r! i
<>三、如何得到 IAccessible 接口指针
$ D& f* X+ N) J    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。- G; e) u( t2 D' }8 L/ |9 q7 C
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
. b9 V; C  t4 X$ T! ?( }/ Z  x: [    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
1 F* A" T: H$ X. m2 L& Z# v3 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 开始。
/ D3 W; X; S9 J, \4 o3 T( p" w    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
+ l3 ^8 e+ `: h8 Z* t( j    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。8 o* B$ y8 `. v, I) M% y
下面结合代码介绍一下它的用法。: X3 }* ~: N: ]) l7 Z
我们来得到下面运行窗口的 IAccessible 接口指针。* L: @1 v* k. E& P" y& |1 _* y0 u
/ [& c) N" m3 T9 c8 k& I0 M
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P># m4 x4 x) Y/ E2 R( Y; h  k% h, v" l
<>图一</P>2 h, R1 W$ O  e: l0 R: e. x
<>( l7 P' x; Y) ^; _0 I2 \( j& }/ U: w
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>2 r/ ^: B/ ^% @3 |; }8 D- m
: l6 |6 V3 ]6 f* H! M
<TR>' R3 o& {/ Y- V# Z- [$ i) l6 n
<TD><FONT color=#00309c>HWND hWndMainWindow;
+ s) Z; W+ Q/ Z4 VIAccessible *paccMainWindow = NULL;
! h/ Y! h+ q2 o1 WHRESULT hr;
% [; p- l+ ?! P9 R//得到标题为"运行"的窗口的句柄; w  L" J8 d8 y; ~, f
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))( I8 J9 m& [$ C4 r( g1 S, @
{" Q0 ]% k. x9 u' b
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
4 A4 U  _; B; J  V8 P. U}
9 ?* N# F) r. _; b: H2 S: @else
1 M# b- U+ i4 e- |+ E1 ]{" r" \% U6 M6 z  J% H4 W* M
//通过窗口句柄得到窗口的 IAccessible 接口指针。8 I; C: W6 f, f2 x# L
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, 7 D) U# |1 w6 I) G- ]$ T
                                            OBJID_WINDOW, & j, C4 F2 q9 m6 M
                                            IID_IAccessible,
7 [* Z% p8 a1 F1 R) x                                            (void**)&amp;paccMainWindow)))1 g3 p* i2 V* B: M7 C
{# {& y7 @+ y* e$ W+ Y
//……我们可以通过这个指针paccMainWindow进行操作。( d7 a8 Z" x: c: f' y
paccMainWindow-&gt;Release();
' u8 ?2 c. g0 G0 ^        }
! @! j; v- I9 z3 {1 R}    </FONT></TD></TR></TABLE></P>
! v$ W" ~8 z5 i9 S: X1 l4 V) ~<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
8 v/ W0 j6 g8 A0 y    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……. Y. R4 e5 W: A5 X* I9 V* F
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>: F9 Z' `7 H' \
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>* H- I7 \( A2 ?$ d8 d
<>图二</P>
7 ^% M9 e2 t6 y, ~& c<>我们现在主要关注的信息是:Name、Role、Window className。</P>
5 q" w" e2 [: Z, _9 I) L<>, D$ E$ X0 o) k% I3 |# z
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
5 k, W1 b3 \% ^( q2 {
. M8 M2 l) ]* `<TR>
2 X4 d) b/ p; d) Z# W) W<TD><FONT color=#00309c>Name = "打开(O):"# w9 k, M) G7 U9 h7 _- ]
Role = "可编辑文字"3 s9 f0 c6 v" b
Window className = "Edit"  </FONT></TD></TR></TABLE></P>8 _" U& a- g+ ], K. {. \
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
3 ^' [1 ~# |" R  J: V5 `0 j    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。) C3 g2 b' m. k) M* e+ A, _; _6 o
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
6 Q4 h! W) T  W! p<>
  }% }2 v- ~- Y6 z9 k<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
$ J5 |, j2 k) u& @1 b' n2 S0 s/ X; D* ?' z2 [
<TR>, z( K5 X8 X5 k) G4 x
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
/ N; C: p; }: [- M8 V; x: Z1 M) oVARIANT varControl;    //子ID。) [4 T* C# K8 I6 F. @9 O# g
8 J' ^& ^% t9 t0 A
FindChild( paccMainWindow,
& d3 j/ M- t# q3 @9 K( C" O) j1 G           "打开(O):", 4 \# ~, C" h; G/ B* ]0 b0 \* s
           "可编辑文字",
/ W0 Y( r; a# l% K* m* f& v           "Edit", " u9 ~2 E( \3 }, H
           &amp;paccControl, 0 w8 K2 o+ }/ o( Z) A
           &amp;varControl ) </FONT></TD></TR></TABLE></P>0 [& o2 J/ T  b1 B0 W" T) l
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
  ^! s' \8 w4 m$ l, i( b第二、三、四个参数分别是名字、角色、类。) J0 M! f& I3 {: q6 ], \6 N3 Z
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>% o# n- G! R. G  n0 b
<>2 ?- D  P8 s; i% L% A
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) s: Y: x( w, p" ]' G' i6 L( E

# ]% R$ t7 H0 u9 S4 V<TR>1 |$ C% c$ \9 j) }1 [0 ]
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
& g9 y2 S! S, x                         LPSTR szName, LPSTR szRole,
: X, ^, b, @* c                         LPSTR szClass,
7 y2 w. H4 {1 T( k4 L$ P                         IAccessible** paccChild,
7 c4 R, D8 K' l- ?9 a                         VARIANT* pvarChild)
5 @) R  j2 E9 E5 |7 Y{% Z( c, ]* {" Q& s) X2 C. P% o; Q
HRESULT hr;
9 e- {- I) E1 {+ A! [; X$ glong numChildren;+ C7 n# H$ a) g7 j: Q: F
unsigned long numFetched;1 X1 h0 Z: k/ t) {
VARIANT varChild;
/ |: _! R3 S8 z: n( x0 O2 j! C! Dint index;
- H# A7 U- J& S- s$ T3 |9 {" l# ~IAccessible* pCAcc = NULL;: A8 B5 {" y1 [( |- _3 B
IEnumVARIANT* pEnum = NULL;
) r* s/ a9 d9 I2 ~IDispatch* pDisp = NULL;. W' {6 O$ B1 m: i1 Z0 s
BOOL found = false;+ c# p7 W. g, S( E/ d/ b
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];( K# B$ O$ B3 \7 T) C6 C4 }) W

$ e8 Y" I/ p& S+ h, O//得到父亲支持的IEnumVARIANT接口. U+ N2 p) _& n
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);- Q7 s5 n5 m0 H- F, I4 k

0 p) |5 K' L! A$ x) i3 Lif(pEnum)
5 f6 D; y! H! u8 T0 CpEnum -&gt; Reset();
" ?6 l9 s* M6 b  q) ^5 m
: m$ m) K& R+ T$ u//取得父亲拥有的可访问的子的数目* J" [5 [) R+ X2 ^: N, p% i( [2 N
paccParent -&gt; get_accChildCount(&amp;numChildren);9 W2 I1 y6 s" l  Q
% H$ E  Z- U& D* [( h
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。; G& f7 Q( h3 P* _& ~, w
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)2 {( `  O4 U0 `
{
9 A9 W" z0 r  Z; X8 C6 W% IpCAcc = NULL;
8 C+ K8 c4 m: n4 C: F// 如果支持IEnumVARIANT接口,得到下一个子ID
6 @0 {9 E4 J+ k# m# c5 d. w  v7 V8 h0 i//以及其对应的 IDispatch 接口
' E# @# N2 a$ b" D7 z) iif (pEnum)7 t  k* c4 @- l. i% J. j" ^5 r
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
) o5 @. k" r. f6 y5 e4 u% Y+ V7 ~6 }else
' z+ z/ ]: W4 v4 V{' w. [7 I/ X4 [
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号' v! k; Q1 `# r
varChild.vt = VT_I4;
, P" `& N& @0 Z2 z6 g5 ]varChild.lVal = index;
- j& Y# v/ v: x/ x2 L5 j9 H  L+ ?}
. l4 m* h5 w+ f  a5 K/ @, _0 N8 B4 ~. `* Z4 s! f6 [
// 找到此子ID对应的 IDispatch 接口  N: ]4 Z1 W: D. e2 W
if (varChild.vt == VT_I4)
& a  l  @+ A0 t$ h{
- Z  U5 d( g4 ^, g//通过子ID序号得到对应的 IDispatch 接口
+ [$ t/ E  i1 epDisp = NULL;" u+ ^2 ~% G2 ^4 U
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
8 t8 F% d9 u) X# j}3 V1 k/ B2 P( t0 p
else
4 _5 G, `) u7 w//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
, R7 O1 C' C. spDisp = varChild.pdispVal;$ i. A9 s' X8 O: p  S$ H

" ]1 A, X1 ]/ Q0 W) |// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc& q7 w* o6 E+ P
if (pDisp)
% V, T7 g! ?! }{4 @, A$ _- Y7 O. `  j8 H
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);9 j0 `6 X. n7 e2 b
hr = pDisp-&gt;Release();7 L6 u0 h3 o4 a, }: ?( t, }, E
}) b+ b  ]" Y- \- C& }) @

$ Q9 C' Z! l- J7 \8 }" l& ?// Get information about the child$ D+ M1 T# p. t' N
if(pCAcc)  }1 d+ L% f" \5 T' ?+ b6 E
{
* C0 _7 J. f) W6 a2 z# _" [8 t. N//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF3 w) V6 X# v+ j
VariantInit(&amp;varChild);
% m6 w6 i$ f, w) K( N& SvarChild.vt = VT_I4;0 b( {# z4 ]" C/ U  T! l
varChild.lVal = CHILDID_SELF;
' K: A2 d: X3 i/ W/ P. e9 P. }
4 I. J' N6 x' t# i, l- D) c( K# q* _*paccChild = pCAcc;$ G% ], }2 [1 u+ ?
}; ?! p' f- V. |" z8 k% C
else
" l: F3 N2 S$ k//如果子不支持IAccessible 接口
; Y. U( p. b- s  ?5 N4 q% m*paccChild = paccParent;
. [$ s+ X% @1 n9 c" L( u$ `& R
7 l7 E5 u% x7 W8 f( h//跳过了有不可访问状态的元素& P% k; D' f7 }
GetObjectState(*paccChild, + d) J& A, r. T2 D5 U1 Y
               &amp;varChild,
$ @+ \8 G  C5 I  B               szObjState, + K. o; q( O% W1 u2 {8 ^& G, _
               sizeof(szObjState));. M9 g; n1 r4 Q) E4 U- t- U
if(NULL != strstr(szObjState, "unavailable"))
7 |/ I- y' L; P* G4 x4 u{) B$ W  `2 B& ~- y1 I3 n5 H( [- S+ P& ]
if(pCAcc)
% c# v9 S' R& |9 r/ ]! B( J- fpCAcc-&gt;Release();
0 O- }) F0 r# k4 M' J3 G0 ycontinue;5 H7 t  L: N( n
}4 d- S2 u: J0 q/ Q$ I9 C7 p* ~: }
//通过get_accName得到Name* Z1 s! @* d" c- G, Q: Q, S/ s- |0 R
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));$ h7 D7 A* H2 I$ e4 s6 V
//通过get_accRole得到Role
4 ]2 z+ X, _! l# QGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
2 E( w2 w' d" @) q* z' j2 T! @//通过WindowFromAccessibleObject和GetClassName得到Class8 Z2 F# o# U1 {- _! }' E# r
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));' n  i( P8 Y+ b  \4 t6 s6 D$ l
//以上实现代码比较简单,大家自己看代码吧。
+ y% e% k% _9 v: Z! s
3 y* c) `% {. m0 V. m//如果这些参数与输入相符或输入为NULL
3 }5 V' o: w/ [$ Z" Z, eif ((!szName || - l/ w1 P; E: J! A' @
     !strcmp(szName, szObjName)) &amp;&amp;
6 l: e/ _# A) p4 Q     (!szRole || , \! R/ C7 I/ u% k; N
      !strcmp(szRole, szObjRole)) &amp;&amp; - a' ^/ e, j( B& g
     (!szClass || + T- _" @! J' z% D
      !strcmp(szClass, szObjClass)))" [+ q, n/ L5 D$ C- D4 u& ]1 N
{8 f# J. F7 k* J" r
found = true;
- I( s" q. u7 z$ d# ~# @. R5 V: }*pvarChild = varChild;
6 O. b4 N9 |8 D' ibreak;4 [" V! j6 i  }% P- @9 J
}5 Q' o% [( v8 c1 j( J
if(!found &amp;&amp; pCAcc)
3 b6 k# o2 i. ^2 L$ r9 R$ D{4 ~' c$ d" a( @& q. G$ D
// 以这次得到的子接口为父递归调用! @0 F2 S7 j( u
found = FindChild(pCAcc, , j7 E. w3 X6 @' I; v0 x
                  szName,
; v4 \; y! c* s                  szRole, $ J4 [& Q1 R- u9 U
                  szClass,
% |+ Q7 v" e. K% A2 _  p0 c/ R! B                  paccChild,
& m0 v$ j, r$ O, M1 R                  pvarChild);' B4 x; W5 Y# _! t" K% z$ k
if(*paccChild != pCAcc)) H$ s( v# K2 `, f) S
pCAcc-&gt;Release();$ [0 T) ?0 t2 M
}
4 K6 t$ o& b* c' `  `}//End for
) ~# B4 q& k/ Q; L2 z  H1 r
0 V1 T" E/ F; G3 q// Clean up
8 a4 j2 ]9 J% r: eif(pEnum)7 r7 ?% J8 G+ x: l  s( Y! z
pEnum -&gt; Release();
8 N4 i- \: G4 Z0 ?
/ v7 O2 S; a/ N2 P' t( B/ {, greturn found;1 v  e8 B+ T; s0 P
}
3 B. q6 ?5 P+ m: ^2 e( n/ v* A& r5 o# D4 Y( u3 y( \% N
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值," w4 H5 [" t7 _! Y& I
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。' ~1 `- A6 D7 ]
//将这些或数转换成相应的用逗号分割的状态字符串。0 Q9 }& l) q* E8 ]! W
UINT GetObjectState(IAccessible* pacc, ; O' P6 e* G1 l- S3 H% l
                    VARIANT* pvarChild, ; F3 d9 H0 _- H) t  y
                    LPTSTR lpszState,
0 O: H+ X0 k1 V! E( Z! X                    UINT cchState)
; s+ e' R; ~; D! w. ^- c{
* U3 h" k. q/ o" Z6 E    HRESULT hr;
( d& S( ]/ X& i# d1 K9 r    VARIANT varRetVal;
' P9 i' x: X; r; ~" t8 J
0 T, d# b' a2 a- B4 H; s    *lpszState = 0;
, m% D1 P% T, J& L5 M
9 M8 ]* `  ?4 Z    VariantInit(&amp;varRetVal);; `0 v: U8 G6 t  ]
2 ?+ v: L: U4 k& f" `) a/ W
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);4 t  h' H: I3 B1 P2 u* z/ w
5 u( \* p& t  y" D( q
if (!SUCCEEDED(hr))
. |5 Q! F2 J+ u" Y        return(0);. f" Q+ u' Q9 I7 Y( D' K% I
+ c" Q; O* ?0 i& M9 e: z- C: c
DWORD dwStateBit;- l, b' H5 C5 H* E7 ~* z, }
int cChars = 0;" ~0 W% u. \. ~! |! s" a# I1 l+ V
    if (varRetVal.vt == VT_I4)7 Q2 Q7 L& U! I' c" e4 r; K
{
7 E8 k5 f# _. k) J/ y  O// 根据返回的状态值生成以逗号连接的字符串。
. B# O( I9 }1 y. m1 i        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; ( q1 E5 Z6 @0 ]& s' U, E
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
" n. W- z+ i! T# ?; Z: ]               dwStateBit &lt;&lt;= 1)5 A/ N: k! r2 h
        {
3 m3 c7 B$ `  P5 @% g: ~            if (varRetVal.lVal &amp; dwStateBit)
7 D+ B7 j) T3 p) w) p            {
6 T. U1 {3 z2 Q  Y3 v                cChars += GetStateText(dwStateBit,
! \7 }; [- t. S1 H& L6 ^$ {+ T                                       lpszState + cChars,
; V+ c% R( o5 _: r6 L3 \: U                                       cchState - cChars);
5 Q( h" R9 q" O9 ?*(lpszState + cChars++) = '','';3 b6 w/ h/ R0 u! ?* {
            }
) Y& O6 G" }+ f1 d" @- b8 G2 x        }
$ B( T' r. Q7 ?) f4 eif(cChars &gt; 1)  G* _. n) @" V: a2 t+ S
*(lpszState + cChars - 1) = ''\0'';7 ?# C7 ]+ s1 [, `/ P: N
    }
$ p  X# I( ]9 J+ q    else if (varRetVal.vt == VT_BSTR); b, S1 x7 Q1 z. h& h
    {/ ^5 }0 q" g: u$ v6 V4 a
        WideCharToMultiByte(CP_ACP,
' R4 H! A0 g/ x# F  ~                            0,
2 a: n9 F! J$ d) @1 X* l                            varRetVal.bstrVal, 1 w: N" S4 ?9 j% q1 m8 K* _
                            -1,
6 T/ [0 f- c# o1 @8 O& A9 e4 L                            lpszState,
% M/ T. C4 Z8 f" c% ^% t                            cchState, 6 E$ G9 a* q' y. I5 }
                            NULL,   m5 ]' ]6 E: T0 ?6 E
                            NULL);
+ U. g& P' v4 e6 ?    }' p" R) u: {5 J0 X5 G4 O

+ s" @7 s: V: k1 ^# \* |, E    VariantClear(&amp;varRetVal);
7 T9 I  P& @. X- W  X$ W- [
0 ~5 D' r8 ^8 F# {! w! Y* |+ A$ v    return(lstrlen(lpszState));
% n1 J8 m5 s7 C8 Z) ^2 D}</FONT></TD></TR></TABLE></P>7 @+ t$ ~# ]: k1 V1 V% n, Z& {/ F# @
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)( k% n' Y( a. i6 Y/ Q

0 w% y7 J; x- S/ x& B, a% j3 @四、在 IAccessible 接口上执行动作
# a, F# f2 Q! D. k' _    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。+ ?: K9 H# Q: P% E2 ?$ y5 f+ O
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。/ L% w" c. p5 x* l
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
* K1 S, L% H* D<>7 P* {2 h# C) l' g" X' S. }7 @$ H* ~
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& [& j0 e: [' y" @; o

5 }! C3 u: X+ ]- m/ U) Z5 |<TR>4 b  P9 q5 X7 K% }$ }
<TD><FONT color=#00309c>//在文本输入框输入"regedit"0 W3 y. x* X' l0 Y. |7 A
if(1 == FindChild (paccMainWindow, "打开(O):",
7 S: k  X; U/ W7 n$ v. l                   "可编辑文字",
; Q" r, g) O7 z2 K8 ~& N                   "Edit", 8 c: @! H8 D  A! q) ~: G+ \
                   &amp;paccControl,
2 @% F- f5 }; P( r                   &amp;varControl))7 B. P% \# G5 Z6 a5 T% \
{
5 L8 b; T6 J3 p2 v' Q//在这里修改文本编辑框的值
& S2 v1 ]  {+ fhr = paccControl-&gt;put_accValue(varControl,
+ i4 O4 Q6 Q  n9 I9 d2 l                                  CComBSTR("regedit"));
' W: P! s0 }2 [9 u# mpaccControl-&gt;Release();
% }. c3 J# t8 w- G9 N$ W9 CVariantClear(&amp;varControl);
% u- v$ f% X" }6 T8 b! s0 `}# v9 Y) E' Y9 A3 d2 Q! y
- g# @+ l( h$ Y  p- m3 ?/ A3 }
// 找到确定按钮,并执行默认动作。
+ U4 a6 w. b2 o) H/ f- V4 H4 ?if(1 == FindChild (paccMainWindow,
. I' Z! C1 J7 S2 v" [% `/ P- ?9 r                   "确定",
" q, l1 P& d+ _4 a                   "按下按钮",
+ {+ c& n5 u. f1 M" s                   "Button",
9 ^- ~) P+ G0 I# m0 b0 Q                   &amp;paccControl,
( T: Q2 M" U% q, p% @: g  x                   &amp;varControl))/ Q9 v: t- k8 }% @3 r' u
{
8 |" a& c% L" {  S6 Y//这里执行按钮的默认动作,即"按下这个按钮"
& ?4 Z2 S: g- J7 x/ D( Bhr = paccControl-&gt;accDoDefaultAction(varControl);" w# [; d- w* m) _: B8 R) P3 y
paccControl-&gt;Release();
! z, X. l2 o6 F( V. E5 e8 QVariantClear(&amp;varControl);
9 y' Z- A, Z2 a. c) W" s}</FONT></TD></TR></TABLE></P>2 M, P5 x! R& s6 ?' \
<>现在,你会发现已经成功启动了注册表编辑器!!( Q% C, L  k. x0 W  c
</P>, d. ]# ?& e# T1 d
<>五、模拟键盘和鼠标输入* U5 I- r! `( d% J# c
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。  y$ R/ [$ a4 ~9 Y% H
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。$ J/ w4 l1 v5 f
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。# a3 p$ e/ f0 |& ]
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
& x: {( c3 K/ u5 d9 k<>" ~0 ]7 N* \8 {
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ l! J! V# g, R( c! k8 z' [9 U

) P' |, u5 e9 B; K: j. X2 _: b<TR>9 k. c3 d/ V' ~) w: B4 a- f
<TD><FONT color=#00309c>INPUT input[4];
/ l. X- U" b9 M3 M) fmemset(input, 0, sizeof(input));$ ~5 u7 E( f# g: T

1 u; [  s0 |6 R8 W: ]6 ]//设置模拟键盘输入5 j  n- A6 j( H, F1 ]& |
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;* K- a  v# v+ x2 w- ?
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;  W/ }6 d0 l: Y# j# h
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;  r$ d+ y1 a6 T' n6 l9 o( M( s
) ]6 Y% {# F: l; Z" D: z) @+ y
// 释放按键,这非常重要
9 E4 ?3 U! k. o3 R8 ?* k1 B  Qinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
3 a8 q* y) W/ k3 {6 T( v( A: s
$ b8 ?3 l$ u4 c8 wSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
( n% i, y0 v+ W# F6 `' U<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
* R" {" @! x$ e  U" y% v' K8 A* e</P>  i8 U( ^+ T( g8 k3 n
<>六、监视WinEvents9 f( U# G9 r5 ?
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。' q, ^( |. Z; S$ M3 p4 \1 M6 ?: F
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
& f$ S+ h- o# c1 {; _) {    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
* M+ a0 e9 y1 c" t6 F8 E<>
3 _  N3 Y/ s& |. \( M& l  @% R+ ~<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
/ S1 n: f( D. }- `) O
" w; I3 d  L7 N9 W* k, M<TR>
7 j" A" ?/ E0 m9 d9 L0 D<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
4 c- `: [! h* }) O{! q' G" r2 M. |* Z/ f6 h
hEventHook = SetWinEventHook(* N6 x0 [# i/ c  W9 e0 i
EVENT_MIN, // eventMin ID& H! A5 P& _% J* o
EVENT_MAX, // eventMax ID3 x" k% d4 L7 f3 @
NULL, // always NULL for outprocess hook
, W, \6 k6 s4 ?0 ZWinCreateNotifyProc, // call back function
* i3 O2 V; i1 P8 s) i0, // idProcess
7 M4 w4 U7 Q9 W0 }% A0, // idThread 9 n- ?1 M) R# Q# M% F5 A, k: z+ a) F
         // always the same for outproc hook# ]( \; G. I9 W+ A7 r/ O
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);9 s3 C3 r& \. M( g7 e- N
}  </FONT></TD></TR></TABLE></P>
: D* ]4 B5 i- U* G* C/ r+ _& v* R8 h, q
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。: {% S/ X) Z+ B  g4 x
下面是回调函数:</P>
% h3 l5 P  U0 V7 P<>
% b; ~) d, v! I8 v( a<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 z! p$ T- ?0 P3 [: H" H
* d6 B" @% Y8 `1 c/ h: S0 ^
<TR>4 z0 E; O+ N. F, m2 ~4 l, X; ?
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(! r! w6 C9 S+ N, U
HWINEVENTHOOK  hEvent,
: |  ^6 P7 V0 ^, X4 ]  X- w' HDWORD   event,9 s" Z1 `- [8 w( m7 W6 x
HWND    hwndMsg,
2 _, F0 c) g/ d( t! D' A9 ?$ {: wLONG    idObject,4 t+ f) }, c5 M4 m, F4 z" o
LONG    idChild,
. [: ?. j, z3 b+ pDWORD   idThread,
- b9 f9 Y6 D7 wDWORD   dwmsEventTime0 t4 ~$ @6 X" X  ?9 B+ e
)
9 a/ s3 V2 I9 x# L7 e, M8 J{1 d6 z# J' Y/ e% S* o

* E2 n, h5 |9 B4 u0 L5 L2 F6 Aif( event != EVENT_OBJECT_CREATE)
+ |$ T" m& k6 C- [return;
9 s4 h6 `+ x, V2 L: E1 P# X; ?. [' }3 q
char bufferName[256];
6 C& b: `9 Y( g, _8 A  P2 gIAccessible *pacc=NULL;
9 I: x+ [! ]/ G: y5 l" gVARIANT varChild;3 W& V2 o8 j3 `) I
    VariantInit(&amp;varChild);) P1 s, q& Z# n, g; m
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
1 B) F  e" K1 k5 U: e. P) E" RHRESULT hr= AccessibleObjectFromEvent(hwndMsg, & j: f" |4 Z4 e- l( A
                                      idObject, - t, {6 B5 o4 d
                                      idChild, . ?/ ]: b) H; ~- Z
                                      &amp;pacc, 7 Z  ^; y& K; \8 l/ f" |
                                      &amp;varChild);
- f8 J* h) m( v/ t+ j* w& V
8 O0 |5 x# n3 I2 {if(!SUCCEEDED(hr))
3 R; x# M2 Z+ }# K9 K- u{
% k/ F8 c5 z$ p3 iVariantClear(&amp;varChild);4 h, B3 D+ H$ P$ X" m
return;8 k9 X+ R2 o* h2 C" \
}
: R  z: d; H+ _/ f- P/ M//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。8 E# }/ y4 L2 Y) J
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
  j6 f1 Z9 P% M5 y2 ?; Bif(strstr(bufferName, szMainTitle))
& R( x& @1 z: |PostThreadMessage(GetCurrentThreadId(), - k+ q; A* P  U1 _
                  WM_TARGET_WINDOW_FOUND, 9 g4 y; _3 V! y0 ]- x  o
                  0, & P/ S, z5 x, W, C4 _. ?  ]4 u! \* x
                  0);. u% }* o9 c9 d( v

6 q% B* A5 }5 K8 H1 k) ~) `return;" P" B) M4 K. Q3 K9 S6 q
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
! T6 c6 U: ]8 @$ w6 V" r4 i& y9 p8 N; j# ^& E0 F& M) E
附录:
5 o; v* x: B# B! u9 P+ K
- S4 h+ q. a' r) p# [' f" |' Y6 A$ p关于IAccessible 接口/子ID对:$ M. ?* W& \/ w* N1 _
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
/ m8 V7 @4 p7 b    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
7 Q+ p; t% K/ `# Y4 q( y* o    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。& r' k3 Y3 F. c* f
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
+ J% h! i( @% B8 h: I9 a1 W& z    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
1 j) u, g5 S# Q/ p% M2 |
# g4 n$ I/ Z2 a2 D2 f+ `注:
0 q( w& b' y, ]) {& {; u% P    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
: o, ~; A" y1 W, v6 s7 O- ?1 n    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。4 Q6 c1 U2 m/ z& d0 J5 R" S' R# n7 Q) T
6 z" u- c- j" e
参考资料:
: I  _# U/ @( x& X5 p. N<UL>3 t% Z  V& u! R0 v
<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 1 U- L$ W% ?4 J$ M( L
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




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