QQ登录

只需要一步,快速开始

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

MS Active Accessibility 接口技术编程尝试

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 15:21 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>4 ?0 R. ~# M, r

' ^  |! _/ d+ I! c<TR>  V% N5 O# J0 t/ F/ ^9 c& X
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>- Q2 G& @9 ^. v2 u0 t! J8 f0 P
原 作 者:崔传凯
) o" s9 ]5 ~/ A1 @$ g6 r原 出 处:vckbase
3 t" Y: H) C. W+ d& D发 布 者:loose_went
0 p- k, J2 D9 V) z# |. i: T  Q发布类型:转载& l! r$ w7 ?+ ~% p+ h) I. M2 E
发布日期:2004-11-12
! H; g! n1 A! H/ H, F1 O% g. z今日浏览:6
* K# g; j7 F% W8 {; }4 m: ~- {4 v. l总 浏 览:2347 k& f$ c+ `' g  N: s
</TD>
% N2 r4 V* e- x  S# D<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>
  V3 M; R# V/ y1 z: i. t<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>7 }" I. q2 N9 U' m  f
+ B' s1 E- `" X1 J  l/ n: D; v
<TR>4 D1 c  |& t4 W+ @& h+ S6 G! V  ~
<TD vAlign=top align=left bgColor=#ffffff>5 y* u( t' U3 `* W4 g) T& i( D  m
/ g( E5 X, C. }2 E$ H2 }. c, c4 X! l& o
<>( W9 o/ T3 s7 Z/ v/ a. t9 f* ]2 Q+ p
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) Y& f# f2 N/ i# [4 x
( k, t" M  T6 d, L% @
<TR>
- Z% |& J) V5 R5 K8 D0 i<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
( B3 [( k+ D5 j, E- Eway accessibility aids work with applications running on Microsoft Windows?. It
, \% \/ t0 T, c1 h# S+ |provides dynamic-link libraries that are incorporated into the operating system / ~& n  {* a9 J& |
as well as a COM interface and application programming elements that provide
) ^8 K! T7 U' F  V: d/ Freliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
8 O3 N& A) r1 c<>一、基础- R' k9 d: S8 L
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。" b$ z+ V0 u* a$ {
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。; [8 c' I9 U8 F! D* ~% R+ w
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>' `0 P! \/ `0 e" `8 t
<>二、Active Accessibility 原理0 @$ Y& w$ x' f
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。; f2 G& z$ O! @, ?; F% A, S9 l
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。6 [! q8 ~- V# p8 e2 p
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。! K3 m: i; \9 k% t  D$ Z: ^. G& V
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。/ E; Y' |: T0 y2 R2 x0 v
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
. j, S# O# {" k4 M1 W3 P- p  S8 N  f3 w<>三、如何得到 IAccessible 接口指针
/ G4 b1 S( ~% f9 @    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
6 W/ W8 F5 x' h* A# b    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
9 A4 b( U1 N% r0 r' O& D! _    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。6 O1 E& D2 X: x6 Z+ l( G  K
    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 b5 |& y% K" l% w
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。+ e. ]3 Z! X+ r6 C7 J# [/ @
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
6 B# P% s/ P( C: _9 s下面结合代码介绍一下它的用法。3 F! j- w+ x4 ~$ R: P
我们来得到下面运行窗口的 IAccessible 接口指针。
+ t: B7 @; G+ n9 G2 `' ?0 t
( N, `9 _3 h, m6 J2 C' P5 p% r<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>" y7 q; A3 U7 X' n
<>图一</P>9 Y) f3 N2 H, H3 S: C) _; ^' z
<>; e0 m6 w  L, M. i( d
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>  S0 R/ L5 T4 L( ]/ k
3 F% `& t7 _# \) c0 h
<TR>
  V9 F6 E5 l, p: m<TD><FONT color=#00309c>HWND hWndMainWindow;: B6 \8 m4 c9 h! }) A
IAccessible *paccMainWindow = NULL;
& e( g* |- ]4 F4 l0 Q* yHRESULT hr;9 N8 e$ K* b6 t( c$ w; K! q- J
//得到标题为"运行"的窗口的句柄( ]  q! J; e( e
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))" L8 |; h1 ?3 b
{
' `: S9 E4 w  ?& m: N5 gMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);' g6 c. w  h( U* a
}6 P1 ~& M  [, Q
else
# X& U( `, D- `% Z0 d{/ H, B) }+ d4 Z6 v* M
//通过窗口句柄得到窗口的 IAccessible 接口指针。
! \3 u6 w" x# }if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, 6 P# u) _+ h! N/ R
                                            OBJID_WINDOW, 8 t3 v! D9 S( z+ J5 `) l5 `
                                            IID_IAccessible,
6 b1 C9 d- Y4 y# e                                            (void**)&amp;paccMainWindow)))& K; V1 c# l  Y+ r, \  D
{+ `) o( V7 b% }/ P; h) E
//……我们可以通过这个指针paccMainWindow进行操作。
0 z2 g$ X8 o- FpaccMainWindow-&gt;Release();
4 Y: Y9 x: S' I! o# d1 ?' W        }) \: ^* a( F6 M7 Q
}    </FONT></TD></TR></TABLE></P>4 U3 L  P. c& \, l9 ]; S1 C# ^
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
6 t! X0 m6 p. ]. d6 v; S    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……" c5 u; L# Q; b1 t
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
0 ~- {# @8 b& e/ f+ Q1 |<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
" D* n  d' u2 [; R2 M<>图二</P>- `  u* B; l0 V7 ]( {  I( X- L
<>我们现在主要关注的信息是:Name、Role、Window className。</P>- @, e4 v& U" c1 m2 [+ ?$ q* u
<>
! x* c; \1 L; V" c/ L) ^1 \<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
. T( M+ g6 k  ]8 e7 N! X' y& a% A- f! g5 T) E9 ]
<TR>: ~8 r/ c' @7 I6 N8 T
<TD><FONT color=#00309c>Name = "打开(O):"
3 f% A- d: M5 d' i& y. p# L9 XRole = "可编辑文字"
3 e+ O# y7 R* ~3 F7 ?$ R4 J# [Window className = "Edit"  </FONT></TD></TR></TABLE></P>8 g# \" H$ _9 z. ~
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
, Q4 K* R; [* A: Q) l0 r5 b    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。' O$ n: x# I* w3 E3 H) ^" C
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
( s4 J$ I0 b2 n7 j<>
4 \5 w& ^$ m; t3 n1 v  u- u' D<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>. F2 M( d: L- S8 z
1 _9 F" u3 {5 O: h( a
<TR>
8 I9 A( g8 [7 y0 O4 z+ A9 H<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
/ t2 L1 N, A6 e4 \% R! |& k$ mVARIANT varControl;    //子ID。3 f; R' O% X" ?' P0 C
. Q/ S7 C+ w& @- R6 `
FindChild( paccMainWindow,
: `* w& v0 R" g/ g! c8 M; C           "打开(O):",
6 Y# ~1 ^* ^9 t3 }           "可编辑文字",
. H1 A& E% b+ w5 e. s           "Edit",
& y0 d& z1 p/ R/ Y/ y' E           &amp;paccControl, $ {; N$ @' Q5 g1 x" }5 J+ }! \
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
$ M! b  t  @5 O  f$ j1 x2 f. w<>第一个参数是先前得到的窗口 IAccessible 接口指针。9 g% X( T& _4 _6 G3 c5 ?
第二、三、四个参数分别是名字、角色、类。- b  k+ G* V4 y) ^2 P; \+ e: W, c
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>- I* I9 O& T- B6 J2 x
<>
) I' {: B2 V7 t4 C( o<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>. E$ i: z( E* n& p% V* ]
# ^8 j* p* @9 O4 ^) E8 }) D3 V
<TR>
$ g+ H( a% I" t8 e<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
) H* o7 q$ p1 f1 C- T) ^                         LPSTR szName, LPSTR szRole,
5 u! [) e+ m2 I  h                         LPSTR szClass, # i  |  E  w5 [* M6 _
                         IAccessible** paccChild,
: K# j, }% C& l5 n! e. F                         VARIANT* pvarChild)
9 i% k" F+ E0 _9 C- i* C+ U3 O# H{
7 c: N5 f& f2 {3 D7 wHRESULT hr;
7 a: F; h, @0 b6 [  ylong numChildren;
, P, S, ?0 S: U, ^. ]unsigned long numFetched;6 c% v. D6 K( x5 R
VARIANT varChild;
$ Z- Q. H- ]0 ]int index;% l2 N, o" g! x2 e% p2 e# k6 j
IAccessible* pCAcc = NULL;
% \2 B) A' k+ n9 i. a! RIEnumVARIANT* pEnum = NULL;
' W, L: r7 n7 K, rIDispatch* pDisp = NULL;
& [9 k0 t5 P6 Z' k" c. uBOOL found = false;
- Y, r) p& k1 P2 q& R& V5 ychar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
: k  R# i& q/ @$ t, W9 D* g
- r2 X* v3 _  R0 {' y//得到父亲支持的IEnumVARIANT接口
' ]/ \; m1 U  V8 o* Phr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
% G1 |8 d- w) t( r9 q0 T& p7 F
6 h5 a: _7 P" Iif(pEnum)
0 B) E( I7 I- c) W6 k( SpEnum -&gt; Reset();$ E1 R/ M1 x* ^$ E: k/ m8 c

3 _9 |! ?' h. v* {/ d7 @- ^  ?//取得父亲拥有的可访问的子的数目3 E5 E& X# J+ f! A. ]/ R3 W
paccParent -&gt; get_accChildCount(&amp;numChildren);' k( F3 _9 {) w0 \! \, J
* i, u9 y* d: B
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。% A+ N' i& z$ g( W
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)5 a7 F4 Y5 a$ i2 [" N
{- b- j! `% P7 `5 D6 N" h0 z
pCAcc = NULL; . Y& V% d; l5 k% I/ t
// 如果支持IEnumVARIANT接口,得到下一个子ID+ Y& y- N* F9 r. i& S6 F8 r: b- v
//以及其对应的 IDispatch 接口
- g+ \" B! a  U/ O- h# ]if (pEnum)9 f( Q4 Z% _6 J6 J2 q+ l
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
2 q9 u& I9 s. X( F/ D$ G# Lelse2 f# _9 f/ K$ C, \8 U' c; ?# l9 p. o
{+ S" Q9 ]3 t% k0 B1 n" r
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
3 X7 [% Q6 S7 L8 Q5 _  Z, E: BvarChild.vt = VT_I4;
( O, a! b3 H( n  avarChild.lVal = index;
2 \) o5 L, r+ R, }" ~: p' p! K}- {+ G+ S0 M2 y

. f8 U: s& J, m5 g// 找到此子ID对应的 IDispatch 接口
& O% _/ a  Q- r. \$ i/ v8 mif (varChild.vt == VT_I4)7 P" H% c5 e9 _3 Y7 a9 G
{
. K3 k# y/ y0 m. ?# L5 e7 W8 [/ z//通过子ID序号得到对应的 IDispatch 接口! c' d% R& p  L3 M  P! a
pDisp = NULL;
9 ?5 E3 m0 b0 v( \3 L, Mhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);9 }% E" K: P1 R6 E; G
}
! o* q; r8 g! H8 Y0 k& ielse
( h% S1 c" Z# ~' r' c9 x8 f//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口3 _0 ~* A7 n6 p+ ]2 S+ i
pDisp = varChild.pdispVal;
/ h8 S8 ]5 b1 u. V7 ^. k+ ^; `7 Z9 o) }9 u3 V/ A6 S
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc: }0 P+ C. ]' z
if (pDisp)
# e1 k0 `3 C9 X" a5 P. z5 ?6 R: W{9 Q1 q+ f( V4 U2 c/ u
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
* B8 t. e- j  ]- F3 Ahr = pDisp-&gt;Release();) @8 \# J8 L# w' n& k
}
0 S- g* c6 w' W+ Q4 K' W7 ?, ~8 p% @, Q( k# h6 C5 k: ?2 B
// Get information about the child3 c1 H8 ]: E& l& W; ^  Y9 t6 F
if(pCAcc)
0 ]$ }+ n* ~3 j7 m{: y" L' }  z" @% |6 n4 p8 Z
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF& ?) r9 p; k8 I3 z  b$ x
VariantInit(&amp;varChild);
5 o( v" |) C0 J  `* _7 _% l: N) E( tvarChild.vt = VT_I4;
4 f4 J; n3 ]- Q; z% {0 VvarChild.lVal = CHILDID_SELF;# F1 _. Y( y* m' H
' U& d) I' v5 t5 V
*paccChild = pCAcc;
6 l# _( p6 n7 {. k( d' K9 H}
& R% c) h6 R6 I1 I; j* oelse- j" j' |- n- o+ `
//如果子不支持IAccessible 接口
5 r0 I5 V: D# [/ [% I( f*paccChild = paccParent;8 v( `; p/ T( w, ^# x. o/ F

  N+ h* h8 k& L* w9 ?  f//跳过了有不可访问状态的元素
  p7 A# ^" p1 u( Q: m' vGetObjectState(*paccChild,   A, q( W4 S  b7 v; K  D
               &amp;varChild,
( X7 H! |, w7 K& R3 U; o+ ?               szObjState,
' D7 p! d6 T* n! s" K/ @               sizeof(szObjState));
. [; E- M# ^: R' @3 H8 {* Lif(NULL != strstr(szObjState, "unavailable"))
+ f6 ^& K$ \% }* H: G{
0 i) l5 x( T7 m7 `# qif(pCAcc)  G1 W* ?  B6 K# e
pCAcc-&gt;Release();
2 U* r# x5 g- B9 ycontinue;0 j, c9 ^5 [* ^
}
5 I: }# U3 s0 B2 Y//通过get_accName得到Name  b+ N1 {8 B- M* a$ s; F- U
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
. l0 I+ D3 [9 H& D  E//通过get_accRole得到Role
5 I7 D1 `  }4 FGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));* ^3 q# ]1 P+ v, h% D* `5 f1 [
//通过WindowFromAccessibleObject和GetClassName得到Class
2 S& `4 _& a) N* B1 b, d. M' cGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));9 ]9 o3 |/ b( ^0 _$ K. F2 e# ]
//以上实现代码比较简单,大家自己看代码吧。1 f3 O5 ~. C8 \; Z/ s# d0 X" I

3 h$ D: {  n1 O' x! M//如果这些参数与输入相符或输入为NULL3 u$ `4 `8 E) Q6 w5 q9 G
if ((!szName || 1 b8 U# l: K% B
     !strcmp(szName, szObjName)) &amp;&amp;
: n" h) o9 L+ v) z" n7 Y8 Q     (!szRole || , d8 T' s: v1 x: {! v# o* C
      !strcmp(szRole, szObjRole)) &amp;&amp;
+ I0 B; P+ V5 a8 d* h. I/ [; L1 y     (!szClass ||   B0 S& Z# p! d" g# `
      !strcmp(szClass, szObjClass)))" }" F# a6 m( \8 R5 q+ o
{
2 I- _1 z8 b0 f  j3 ]2 E( \found = true;0 b# X' U& B. e6 e) j2 P
*pvarChild = varChild;
, f0 r4 s5 q0 I( L- abreak;
3 l8 }$ B# w$ M1 E6 s1 j}: T% v$ u, a- E0 L
if(!found &amp;&amp; pCAcc)  e+ m. y6 }; w# M: J
{
# c: l* h' Z& ^+ X8 S- ]// 以这次得到的子接口为父递归调用4 T" A5 E* k) {9 ]" [* }
found = FindChild(pCAcc, * o2 V! ~2 z3 \5 A' D6 |8 I
                  szName,
1 r) I5 ^' F' |* x                  szRole,
; E) E. W0 o$ a: K3 a* ]; ^, q                  szClass, ' W" B4 z9 o, D: h
                  paccChild, * [; A7 O, l8 W' V/ W0 ~; F
                  pvarChild);
  c, O9 p+ M6 W* Sif(*paccChild != pCAcc)
" w* g3 C3 d/ Z0 kpCAcc-&gt;Release();
' n% s; F6 l1 p# L( L- g  z}
  D6 o5 ^8 i/ ]- `}//End for
8 x, v7 q) r0 K0 N) B' g& f+ ?' \2 E4 j1 D6 R& {
// Clean up7 j2 |, l$ j1 b5 [+ @2 {  z
if(pEnum)
' `- e! D) d6 W/ p: dpEnum -&gt; Release();
1 R1 q: b5 Y6 C1 O* `5 s
6 f8 ^  n# v; _' ?, Vreturn found;7 S+ C/ Z8 x9 T) ~* H: `
}+ s2 P. y4 Y9 C2 @& V
- `. l2 B# m4 k. T+ i
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
3 s3 D) i  z/ O4 Q1 d//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。7 E. W) |! E3 F- ?
//将这些或数转换成相应的用逗号分割的状态字符串。4 N$ w* h% o$ Q9 y! I
UINT GetObjectState(IAccessible* pacc,
  ^' `  i5 B7 C. k                    VARIANT* pvarChild,
$ s( s" I1 Y: N3 r5 e1 m                    LPTSTR lpszState,
1 M  ^9 f# J; E2 e1 d- S                    UINT cchState)5 q1 S/ e& b* w9 ]8 _
{
* S( i: n4 `# @3 r, B. m    HRESULT hr;( c6 C. I7 c) @
    VARIANT varRetVal;
! p! D- w, [2 V$ x  H! r8 y% t) q9 N, c2 H0 }0 Z
    *lpszState = 0;
4 E  B* M$ K, `" u4 Z1 _1 ~6 L9 n
    VariantInit(&amp;varRetVal);
4 p5 h! G0 W, p/ _9 y& k8 D
  O& X; K; M* D- d1 A1 V0 n% y    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);/ F( C3 x) Q' ^$ t
+ T( |# h6 W1 H4 Z3 M
if (!SUCCEEDED(hr))
7 H! l) ]# q' |$ b( `+ u1 i2 |  O        return(0);
% B- N$ N' B4 r+ p2 K
7 i% e* a# y; J- n' X1 H  @DWORD dwStateBit;
1 I4 O9 [+ i7 e8 @, Fint cChars = 0;
5 O" c5 z% }' r$ i    if (varRetVal.vt == VT_I4)
* p( V1 A# e$ H" b{! V( o0 m8 ]% F1 m' [5 y
// 根据返回的状态值生成以逗号连接的字符串。
$ S$ H, J! I) I. H        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;   g. W! L$ H+ Y$ O( i4 }7 i
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
. S8 d) O5 }9 W% ~' l% V9 Q               dwStateBit &lt;&lt;= 1)
, G, G8 Y3 n2 l$ |        {, g6 {1 G4 Q# V5 h8 F' y
            if (varRetVal.lVal &amp; dwStateBit)) ^* C  q4 h% k1 n2 ]* ]
            {
+ w+ P" `# V- p, Q' q                cChars += GetStateText(dwStateBit,
, Q1 Z, H3 m9 k+ ]1 x; J) y                                       lpszState + cChars, 6 I$ |# f" m) o; n
                                       cchState - cChars);9 b2 c1 F0 c# b( K$ S- g
*(lpszState + cChars++) = '','';
" |! Y" u( t/ ]( R+ T            }
' V$ {- e1 \8 A: {        }
5 V/ N$ u3 _4 B. Nif(cChars &gt; 1)5 b' R& h+ `2 w
*(lpszState + cChars - 1) = ''\0'';
* K) y4 G; j2 L9 `7 P0 v    }: Y( _! Q; K. o- b3 T
    else if (varRetVal.vt == VT_BSTR)
% Z/ L/ j( R, A$ |, w7 F    {6 K5 o+ e% K2 B) i7 S) m
        WideCharToMultiByte(CP_ACP,
: s3 k& A8 N' @% `; O                            0, 4 n# y9 a$ c8 T" Y
                            varRetVal.bstrVal, # Q: h( i% o3 q1 ]
                            -1,
* X; a5 Y) a  S4 v) ]                            lpszState,) M; |$ g  k0 I+ w$ q/ t6 O" b8 U
                            cchState,
4 ?8 v0 o, k, t+ B5 W3 G* h                            NULL,
0 t3 g( A0 x" o                            NULL);
0 C4 ^  G( [' N- `. \    }
0 C* |$ G) R4 U4 k9 N# O5 r
' n. O. i* Y+ ?8 N! a0 G+ c    VariantClear(&amp;varRetVal);
2 p- D. l3 B8 C& t: K* [+ q1 h8 h
    return(lstrlen(lpszState));
1 S, b) n3 n1 H$ c}</FONT></TD></TR></TABLE></P>  L7 r8 k7 w1 O8 L* p8 d8 j
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
9 k! A4 Q9 I( Y0 m& J
+ {6 n# d, b+ H% c四、在 IAccessible 接口上执行动作8 ?/ @6 {3 @' S5 z7 u6 f4 h
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
: x3 A% C6 Q$ y0 q( u2 O    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
6 ]; X9 E; ?7 g$ y7 W    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
/ H$ G% b$ H: q7 K% V4 y<>% j* g* V- h1 @1 F- l7 G. {
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' `7 l+ ?' ]8 ], u2 ^9 M9 \0 K

* z% m# t8 ~  _# J( r, A2 L7 h; C9 p<TR>& H/ j% O- i1 D7 B
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
* Y2 F  Y+ p& Cif(1 == FindChild (paccMainWindow, "打开(O):", 6 O  M2 Z& T3 Y
                   "可编辑文字",
' q' }' Y& o: y$ ~* K' R                   "Edit", $ A0 O& p5 D$ e+ `- V
                   &amp;paccControl,
# H2 c; O8 t& ]- r7 m2 p. ~% b                   &amp;varControl))
& i0 E2 }6 o  F9 W{
6 K. U  O* C, t% t6 k' h, ]//在这里修改文本编辑框的值
2 b* F, v5 Y5 D5 ]& phr = paccControl-&gt;put_accValue(varControl, % G+ F: v" ^6 }4 `: y3 t
                                  CComBSTR("regedit"));& K: \4 W, a4 ]! A7 v
paccControl-&gt;Release();) R& ^; ^  z; v7 p. g
VariantClear(&amp;varControl);/ h6 B& a% K5 \$ g; Y( H. u
}) R3 I4 r: B: A, G0 F
8 m  [8 b- J0 h" i0 s
// 找到确定按钮,并执行默认动作。
5 }% \' F$ r) G+ nif(1 == FindChild (paccMainWindow, & d) l  z% C7 g0 s) @5 {
                   "确定", 3 M" |. a& K) c2 ^  ?
                   "按下按钮", 5 N% w6 e! s& G  w* N% }% E
                   "Button", % `: d* M6 F  P5 W) E: e
                   &amp;paccControl, 8 N8 {- i5 z; V/ Z; S9 r+ y. o1 ?
                   &amp;varControl))
. a5 s' X4 s2 K4 R( o  L$ C{
  i" u1 o* {3 t//这里执行按钮的默认动作,即"按下这个按钮"8 o: x$ z# Z0 ~4 l+ Y: r+ c  G
hr = paccControl-&gt;accDoDefaultAction(varControl);! d  H+ `& s3 B# Q$ k8 t
paccControl-&gt;Release();% n# f& m; M: c$ p3 q
VariantClear(&amp;varControl);% O3 G0 q2 I2 X8 G0 T- H/ L" b! Z
}</FONT></TD></TR></TABLE></P>- p3 w. p/ t8 d
<>现在,你会发现已经成功启动了注册表编辑器!!' c, l; E+ Z3 u2 x  e; g
</P>
) u: H& X  j% K/ X$ ?<>五、模拟键盘和鼠标输入! ?; |5 ^5 N- m* X1 R2 w
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。' i8 g; |' \! W4 g$ P
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
4 d# m- d% I( \" z1 c  L' M8 L    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
) @3 [9 B4 A' D6 _; P  O! K下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
4 w0 w1 J* [+ Q# E, |9 |<>1 N7 Q+ p3 r/ o1 m3 F5 }% E9 i9 _
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>9 E$ ^/ h* p* C% Z/ t
3 U; {9 h& H& L" C) U- N* A1 [. l
<TR>6 b2 |& ^: ^* s# w
<TD><FONT color=#00309c>INPUT input[4];
# S* _) d: x0 w. v( @, Vmemset(input, 0, sizeof(input));
! o5 B* n2 ^6 X( h5 j1 U
) h! A5 V' S0 u9 [& }# }//设置模拟键盘输入: d0 D' G3 v  w7 f
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;# {- A1 D9 R  \% @; I7 U
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
/ h8 z2 b1 S: k- [input[1].ki.wVk  = input[3].ki.wVk = VK_F4;
2 A: ~: ~  F: C, ~  S$ G+ z
6 }! U, g5 h$ E6 ^4 f// 释放按键,这非常重要' U9 ]: c7 K; O& m* ^* C3 \7 [) L6 F
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;" k: F6 t8 q! c* C! U1 M0 E1 x
1 V0 {% O0 p6 a
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>: t( W6 p$ T: M( z3 n$ T. w
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)* ?0 L& x! {$ ?) P  s
</P>
( l1 H% X, j7 v<>六、监视WinEvents$ X4 W+ J/ c. o8 J
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
; u1 O$ _; O& j3 S& X( J    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。4 ?7 i# p3 N5 j9 |& e- T$ V
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
0 s& l1 R2 n" {0 l3 o<>
9 [3 T1 j  k# \4 Q: i6 Q<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ e3 }; `  u& j, w6 t

# i9 ^# H( z3 p2 S<TR>
+ v, d' ?7 Y8 y1 `7 J<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))2 i3 @8 v" G" c# o6 p# e
{
9 O! h3 b+ H8 e* G" h* f- V) s3 VhEventHook = SetWinEventHook(5 ]; Z! M1 O( K8 H+ q
EVENT_MIN, // eventMin ID' ]/ S# p% ?0 r  j/ o9 |
EVENT_MAX, // eventMax ID
( r+ q" h+ T: a1 C( I1 Q4 m6 |3 MNULL, // always NULL for outprocess hook
* ~8 D4 d; j* p& d4 w# O. Q2 s4 j1 ^WinCreateNotifyProc, // call back function
5 U# O! K; L1 E% D9 H+ j/ n" B$ g0, // idProcess
. ~0 H* ^( z" B$ _; t7 N& s0, // idThread : b; p4 h$ W* r2 k. x0 Q
         // always the same for outproc hook& C8 U- s9 k4 F, |% U
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);! {9 ~( H1 V$ |! p6 j
}  </FONT></TD></TR></TABLE></P>% y! S3 {) i& @& i' e) |4 U

+ W# |0 D/ t2 t<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。! `1 a9 K/ g2 D- G: w0 r- _
下面是回调函数:</P>
3 Q% a5 V3 B7 }/ z; \1 \1 m, J<>) y6 \" m) u6 X6 f% m5 d
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>2 s& w' p! {% G3 u+ k3 f

$ ~! C8 G) T/ b/ j/ G<TR>% W  L; A' {, s! {. d
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
% a0 V) F& K& P) @# y6 c- {9 d$ NHWINEVENTHOOK  hEvent,
/ f/ W% A1 s/ ]9 f* I8 Q% u' W4 ~DWORD   event," [# _% M' y3 Z
HWND    hwndMsg,
- i7 I" i5 f" c1 |: Z8 _LONG    idObject,1 H0 |& c; g. ?) }# d
LONG    idChild,
) K9 J* _( M1 z) T  B+ m, KDWORD   idThread,
! W6 b3 q! r  T: `$ nDWORD   dwmsEventTime8 f/ E" C* j  A% D7 {
)3 W- c$ Q: S% J- B
{% `4 g7 d6 |; E0 J: z8 C% s8 t
4 _3 l: m, K4 V5 M
if( event != EVENT_OBJECT_CREATE)0 N4 x* K$ {& P, u/ S
return;
( {* f! @# X( \/ e
$ W% U9 l* g$ ^( A5 F9 _+ rchar bufferName[256];; {7 l$ H" Z% L
IAccessible *pacc=NULL;- {9 a- z8 o, R; M. R
VARIANT varChild;
1 E9 z3 U$ O, V# e  h  C" X    VariantInit(&amp;varChild);
. J& }" W! q( R* J+ m- X7 e//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
) n1 T4 h* r3 k8 E: c" l" Q: T1 I3 |HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
5 p% s' g" h( Y. s* ^, V) _& Z$ I! G                                      idObject,
2 D7 r( m* q+ p4 j) {4 F! o) r, l, E: c                                      idChild, 5 F- m" m  [# x4 e. \& B5 t
                                      &amp;pacc,
1 x& t3 M: t' W* q, M) M% G$ t) L0 g                                      &amp;varChild);  |; W* `0 Z3 H5 S9 l3 V( y" a

( ?: f2 N5 O7 c* Vif(!SUCCEEDED(hr))! E% a. F% C9 z. _' `8 z- X2 i7 U
{
% M3 Y) D5 ~) O  J; m% N8 S8 @" ]$ lVariantClear(&amp;varChild);, L; _2 M2 U% ^# r- g* @
return;
/ }0 D# w+ M4 K% y7 }1 s( I}2 b2 k/ C6 a( X
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。( N% Z/ t9 x( r' Y
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));; V7 W: i/ J: V: Q( z
if(strstr(bufferName, szMainTitle))$ v' H, L3 p0 q! U; ~3 i
PostThreadMessage(GetCurrentThreadId(), " z# V! l8 b6 K5 ?: T# D" G; I* j
                  WM_TARGET_WINDOW_FOUND,
! d0 g2 Q+ ~! F                  0,
2 {5 t$ m, _, R5 l! E, Z" @                  0);
+ k2 T8 Y5 p$ u$ V5 h8 E: i1 b; f2 S" H# k
return;- m* B; p) n* m, l( r4 ]& ?/ `' T
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。8 t/ g2 ~& S% B6 g, f0 W4 S- A

; i% O9 V/ h8 V( l! T附录:
$ g/ v# A+ _) U' @0 p2 q; T5 j1 r5 o$ r; B$ L! w. g4 b* t, h+ m
关于IAccessible 接口/子ID对:! w" \1 O/ ?: O' x: K9 d/ D
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
; s7 N4 j! b  Q& t    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
0 T% q# l! s$ W% H# g' a    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
+ f; [# t$ E) \" J0 N6 _9 Y; N    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
& t. ]/ c( I# V1 q- o; A    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
$ v& S9 f5 R+ [+ {
: h- j1 Z" A; S* u/ L注:) Q4 L0 j7 R# O# N, B
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
# _( X2 ^+ M0 w    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
) W* E/ C- G3 Q4 Y- e0 O& g
& p6 N2 E6 V- `1 d$ d& j9 N3 p9 H参考资料: 8 ]0 Q- Q7 S/ x7 m$ X
<UL>
/ x. [0 X, [/ y7 q" m$ V8 X  S; 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 0 s: t: ?- I5 z, b# M. o/ n
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
xShandow        

43

主题

1

听众

385

积分

升级  28.33%

该用户从未签到

国际赛参赛者

新人进步奖

回复

使用道具 举报

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

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

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

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

蒙公网安备 15010502000194号

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

GMT+8, 2026-4-10 20:58 , Processed in 0.860299 second(s), 58 queries .

回顶部