QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5771|回复: 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>1 P  b. q' w6 `! i
" j5 o* j: z0 m0 F( I' W, K
<TR>5 m  i2 ]8 e0 Y4 p; r
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
. M) x, N* v$ K9 s; u' e2 Z原 作 者:崔传凯8 L' C2 s8 j4 b
原 出 处:vckbase  z- m" C% P7 B% Q5 B+ H9 C  \
发 布 者:loose_went
5 e9 m1 T* X* f# K发布类型:转载
" a% ^% w; X" A$ D! }4 d发布日期:2004-11-12
# n! U+ A% Y; d: o4 l今日浏览:6
! D5 l7 O% a% G. [  F6 q总 浏 览:234+ s( f! X9 L3 f6 z9 s2 L& ?; k
</TD>1 I. D& Q7 Z4 _2 _/ f& ?
<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' `* Y* y+ k1 x' o6 }7 s' o9 O5 r
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
# @! f0 K  |4 b% V# k  Z. `. b/ u& S
<TR>
4 p3 t. w* Q+ [( u) X$ l<TD vAlign=top align=left bgColor=#ffffff>
/ g( W8 f, v4 p2 V$ _  J) h) o
; o! I/ G7 C8 n' v<>
0 W1 Z* s: J+ v6 o( `<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# K# ^( X8 v4 B# w" @, y. Y7 b3 q/ D# b  c+ |' S
<TR>7 c8 t1 M6 \1 u( D* u8 v+ t3 l6 L
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the % y& m0 @: A$ P' _! E& i
way accessibility aids work with applications running on Microsoft Windows?. It
$ ?9 ~8 f$ G" f  q3 q2 uprovides dynamic-link libraries that are incorporated into the operating system / P' |% n% ]4 v7 ^# t
as well as a COM interface and application programming elements that provide
! y' @1 N$ g" h% A' Treliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>% C' a  ~8 C+ r
<>一、基础  P$ h1 o3 o1 X5 X
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
, U# L- {7 a- u1 O: j/ Q$ G    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
) m* n  B: G2 @, \4 l. [    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>' n: `7 Q% n3 l7 q
<>二、Active Accessibility 原理7 T8 t  E% l9 a- o4 {% w( i! Z
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。9 r0 O- J; x2 C9 F: K" B4 N
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
5 J# D" s5 c: e    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
; ^) ]* y6 f% H9 R  o8 Z    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。, S$ {! {- p- m$ a" r$ |
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
0 P, U0 m8 P( L6 a* J<>三、如何得到 IAccessible 接口指针$ W8 W) A4 p8 h% s2 s; \
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
# R9 T' a  A/ ]2 H3 v. y    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
- x4 _9 Y: o7 ^2 q' X- j, W7 ~    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。! ?- _; Q5 Z) P* r; g. d
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。# p; _. C! @! c, ^' N3 A3 d$ Z  D3 I
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
$ z' f- T* j. ?, C, Z    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。" j7 [7 G: k) {) W. H
下面结合代码介绍一下它的用法。
  W' F4 @$ J: l: O/ ?9 ?8 K我们来得到下面运行窗口的 IAccessible 接口指针。+ h' U# |* P( z! p/ D3 L$ f

: X& j' Z5 f1 |* r<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>: `% @. S6 B7 ?' x
<>图一</P>+ W1 f1 j5 S2 `/ a' y
<>( [. S; W1 {: {1 V8 @' G8 @8 r# o
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
, j' ^8 z5 k9 M, y* d0 P5 \5 D
4 D9 {4 S0 k' ]- T# q2 x) o<TR>2 w( k* ?( e" |* m. x6 _% C
<TD><FONT color=#00309c>HWND hWndMainWindow;
$ B& Z, U# _+ u2 ^, V: G# ]4 B' Z, y6 JIAccessible *paccMainWindow = NULL;
0 w- J, z# }2 F% k, P+ m/ \HRESULT hr;
( l7 K+ `- Y9 c8 W7 }8 n//得到标题为"运行"的窗口的句柄
: i8 g; P  D+ O. l1 \if(NULL == (hWndMainWindow = FindWindow(NULL, "运行"))); \8 `7 e* [+ Z! M) F$ o
{$ g+ I, ]5 T- N* l- V. B0 e' {
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);: p) v* s- i$ K
}
. ^" F; w, Z( `else5 X/ M; }. J, ^2 }
{
8 H2 m$ `2 ?4 r- C/ n//通过窗口句柄得到窗口的 IAccessible 接口指针。
' X: P0 E' H7 j. e  D8 l# e+ C- y! jif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
6 R* l% Y) \. H4 e                                            OBJID_WINDOW, ; z! u5 h+ S$ G
                                            IID_IAccessible,. z2 L  g- t2 D
                                            (void**)&amp;paccMainWindow)))
* r1 L0 Q# Z$ ~* V& B{
! W1 [4 a! X, r- g3 I6 ?: q//……我们可以通过这个指针paccMainWindow进行操作。
& J) `; D( f. rpaccMainWindow-&gt;Release();3 D# X3 t6 X7 W6 V% x$ o' w; c
        }0 J4 o: j! m0 W* E
}    </FONT></TD></TR></TABLE></P>9 u! U9 Q8 w  l+ r& |8 ?
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
5 W! T1 b8 b  p7 e0 G" x% A    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
6 o; m, d9 R8 b8 a1 ~  J    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>4 O7 r* V! \# u( X7 S! \/ B8 Q
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
% ?' o% \$ O' v9 x<>图二</P>
! L: ?( `- \  N4 ^  d  {<>我们现在主要关注的信息是:Name、Role、Window className。</P>
3 y1 _6 f# b/ u6 W* A1 @$ A9 x<>; ]/ j5 B4 [- w; _
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# O( t: m! I6 Y2 |, x5 ]6 s% Z/ ?  Q
3 E% b" k$ A6 D8 q8 s& p$ P: u6 c<TR># J( F7 @- l0 X! q6 y0 T; a
<TD><FONT color=#00309c>Name = "打开(O):"& E1 c" d' U( Z; D6 u. k3 p) L4 J! c2 p# \
Role = "可编辑文字"+ [- K# C, p1 x2 b
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
& e% O( a4 j+ P3 u/ l<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
) s& d/ g4 B& T+ @    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
; g" g9 f- o& b5 s: Y下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
+ Y/ e2 ?; n1 t+ u0 b) F<>
" ^/ C2 F0 t/ F+ d<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>1 H, {9 a; {: d, K* b9 {: Q! q
4 ?% I1 O% a6 k( B3 g+ O& R) ^, `9 h
<TR>
3 ^" i7 R9 A4 M& h, d; L<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
( @+ M, ]" Z! ?5 b" R  ?VARIANT varControl;    //子ID。
2 g: D& x. Z# ^6 K2 h+ w, Z2 M. q7 v
: G6 f8 g( B" l5 KFindChild( paccMainWindow, ( e3 t5 [' n6 R* T
           "打开(O):",
& r  A- O& I' f           "可编辑文字", 0 G( `( K. p! S
           "Edit",
% a; j7 u! ^* n3 z, ?% t/ J# V7 Y4 O           &amp;paccControl, 9 X* G# _2 q+ ], e( S. ]
           &amp;varControl ) </FONT></TD></TR></TABLE></P>  P' D9 [( e0 G( H8 b- C5 |6 j- l
<>第一个参数是先前得到的窗口 IAccessible 接口指针。; I0 E2 E  `7 i, V
第二、三、四个参数分别是名字、角色、类。
0 O* }! h. l6 w, w8 ]0 L' f+ o: Q后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
8 N0 m! ^3 G, c6 E<>
/ ]3 J, \. g% \/ i- h9 t<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 t' `* {1 D5 t
5 X* e" V7 o  C6 c0 w8 x- a<TR>
$ Z! P: G: Q6 H2 h6 j2 m<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
" v1 p7 c  B4 Z9 s  W; V                         LPSTR szName, LPSTR szRole, 9 h6 `  b1 [3 R, }: @
                         LPSTR szClass,
8 B: Z- x" l& o                         IAccessible** paccChild, 6 ?- B: O' O5 K1 ~3 C( t
                         VARIANT* pvarChild)% H- B& s+ N% X9 t' i
{
# O; I( p# o2 ?: _4 Z8 fHRESULT hr;! h/ z" M  m8 S8 o+ @
long numChildren;) M1 q: r4 l$ |. t; s
unsigned long numFetched;
. a/ o1 L: ^( j" MVARIANT varChild;7 _# X4 i9 J+ {: i
int index;8 }& W# h0 E" _' o% B* A
IAccessible* pCAcc = NULL;
" L6 t( O) S  a9 a8 \, r& l, GIEnumVARIANT* pEnum = NULL;
; u9 Z. N" j; e9 H9 d1 |) gIDispatch* pDisp = NULL;
' {$ d) ~4 _+ O) ZBOOL found = false;1 @" w& k% u' X" v$ }( C) h
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];( x. |1 V8 ?( c: [

/ }9 ~2 e% @% }' J& w7 D//得到父亲支持的IEnumVARIANT接口
: X& L, P& B5 m: M$ Xhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
4 t2 j1 X9 t; u; [$ e/ C% S. S
- B2 |* K4 z  l1 z- K' h) h9 oif(pEnum)
' y9 M; @  L. GpEnum -&gt; Reset();
; }! c- l/ w* a
1 y# t# P" z* P) D$ c- @. M; D//取得父亲拥有的可访问的子的数目
0 V. G* i/ W% W* ApaccParent -&gt; get_accChildCount(&amp;numChildren);& n0 ^7 o: h" e7 D; X( Z0 F* q
- {$ o( m* r  g0 a5 q3 P9 ]
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。+ T5 f  s0 Y; _* B
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
: k8 {$ R" D/ Q/ _. t% |; m$ C  z{  ^. Z. A+ N$ O" T. Q, M
pCAcc = NULL; ; h5 ]" U0 w3 M8 `
// 如果支持IEnumVARIANT接口,得到下一个子ID
/ u& H4 Z/ r/ A  o3 P" Z# x$ q//以及其对应的 IDispatch 接口  P# o& [2 v. f' g6 t# r' c  K0 H
if (pEnum)5 J$ R' g9 R3 H* Q. J9 e7 U( l- w
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
! N2 j8 Z# W' C+ Ielse: h3 ~  P. F3 f0 I4 Y. {, e) e
{6 a$ l6 Z/ B% h1 T. w3 t* ?
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
7 \4 f3 [9 J4 m" N' nvarChild.vt = VT_I4;- L0 y& H  x5 O
varChild.lVal = index;: R! K! S) H" F6 O* _( @& e* L
}
/ A# ^9 O1 K9 `7 C' E4 q9 ]2 u% P1 K  d8 a. n
// 找到此子ID对应的 IDispatch 接口
) o7 u- ~9 ]2 W9 X! Eif (varChild.vt == VT_I4)6 O, |7 \; V, R' C7 D" h  U
{
1 E3 U) V$ k5 V7 G) N  D. ?//通过子ID序号得到对应的 IDispatch 接口1 B3 r; h5 X. l) z$ ~& c8 [  M
pDisp = NULL;; r6 t6 F4 W. M) ?5 Q4 w; W
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
, [3 h  S. y# G! _% M}
! y# ~; i/ n" H7 ?  {) v; Qelse7 x# ~3 u- t" R. h# E
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口5 |# ]1 O3 u" _: r7 ]1 Y2 X
pDisp = varChild.pdispVal;
) a3 S, s% ~2 Y9 L. C( n( n+ @" S  c% Q5 D
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
, `1 K: T* H" ~4 L' J3 U4 \if (pDisp)
/ `' P! q+ x0 O# V6 O1 X2 `; l% t{
$ r) N$ T* {4 F% P9 Shr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);; S, C) K% }6 a2 b7 Y7 m
hr = pDisp-&gt;Release();+ M# z( l$ C; r8 F
}5 d. |$ C: z1 S' `7 U9 {  c4 r- n

! S- ?* c: B* p# u( R9 M% w// Get information about the child5 P- N, z% X7 }# ?$ d* c
if(pCAcc)
8 V/ C4 e7 Q1 _, x{( u8 X3 q/ m* x' \" O9 |
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF( V' E# D5 W! v+ o( b. I5 Z
VariantInit(&amp;varChild);7 ^* t. J7 v* v3 c$ G+ M1 `8 E
varChild.vt = VT_I4;
4 z" R  Q" m7 M! @; L3 Y7 a6 gvarChild.lVal = CHILDID_SELF;3 d3 q# @; t! L

1 ], N9 `# ~2 v9 s0 W- m, r*paccChild = pCAcc;0 i2 z* w- t2 E1 D, j% F! K$ w  ]
}. v; M+ D. b* [' \1 O3 r1 ^6 ]
else
# h$ |( {: T( S4 |5 K8 e//如果子不支持IAccessible 接口4 j( X/ x! P% |
*paccChild = paccParent;
" W2 ]% A2 r, ^8 @3 E5 g
% {' \" D% }/ L) V5 n1 G5 v9 O//跳过了有不可访问状态的元素
+ l% h% g. m# j, A5 X% U2 `( [% dGetObjectState(*paccChild,
& u7 Q* _' @  ?; \               &amp;varChild, ; t6 t4 @( }" o/ N' V0 ~
               szObjState,
' w6 t5 t1 C0 k; W3 V               sizeof(szObjState));
) w2 |6 F; l( i* p) a8 O0 ^) g8 Dif(NULL != strstr(szObjState, "unavailable")). B9 ?% k) T' N, c
{
9 o$ ~' P# o6 }; c$ cif(pCAcc)" V$ C$ X$ S( j4 V, C. F
pCAcc-&gt;Release();
: N" J9 n: p* c; V5 s: ucontinue;
7 r3 u7 s2 D+ M& S8 @}7 i1 U# @( [' j9 e  e% n3 @
//通过get_accName得到Name* n) \2 ~! h. V; g% V
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));5 O$ S9 F( a( T: h6 v9 E
//通过get_accRole得到Role
# D* ^# x" a/ W# q, m& }2 L' E6 ~GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
  U$ v3 H4 s4 ]( h. s) g7 d( O+ H//通过WindowFromAccessibleObject和GetClassName得到Class& \4 d& J; {5 G6 o" F! k) H  A  H+ ?5 K1 q
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
0 h( C$ K/ o% n/ ]$ T) _//以上实现代码比较简单,大家自己看代码吧。) D6 Q8 D- d" J7 r2 Z7 v4 \4 u

" k  R; J+ d* d7 i2 j7 s//如果这些参数与输入相符或输入为NULL
: t, W2 T8 z% B! Bif ((!szName ||
/ o, e! ]! d5 N6 M7 U: x1 c     !strcmp(szName, szObjName)) &amp;&amp;
" y0 w3 h" ~; N     (!szRole || 9 \$ A0 u0 o) K6 F) u, L
      !strcmp(szRole, szObjRole)) &amp;&amp; * @! e6 U/ q* L7 W9 V) a
     (!szClass ||
6 l. H+ x2 J& d! h$ m7 ~2 ~      !strcmp(szClass, szObjClass))); g$ B+ r& c- i% O
{2 R( {9 N# |7 D+ z
found = true;
) Z/ p  }: b+ G% ]*pvarChild = varChild;3 e. M" w7 ?! _8 |* a. G3 l
break;
4 l. Q- A7 B! K" G/ e}
9 J  U8 X. W% F/ b) Uif(!found &amp;&amp; pCAcc)
/ Y, n2 a0 ?" I{% @' y! r! ~( |: C4 w
// 以这次得到的子接口为父递归调用1 l. ?% D+ O4 H0 z. X7 p
found = FindChild(pCAcc,
" J" c: F; X2 a3 \, d( l                  szName,
  `7 p- j* {7 t  s" g6 n! S                  szRole,
" H, \# j! q1 b( R0 \7 W8 |* s4 p                  szClass,
5 E) }3 _8 c* F, D3 k                  paccChild, / H5 N3 o1 i3 g
                  pvarChild);
' E: ?3 ?7 V1 S: wif(*paccChild != pCAcc)
/ G+ Y9 M9 U& p( a1 J5 CpCAcc-&gt;Release();# W7 w- T- ]( f/ }
}3 I5 Q- J+ o, }! W+ M
}//End for; q8 v# V  H" O( i5 c+ w* g

$ S' s: g- p# Y% ^  ~. _// Clean up4 b$ D; h. S) S4 K
if(pEnum)
4 L0 M1 q5 J' w" _3 ?3 `; Q! FpEnum -&gt; Release();
2 r) y1 v% Q! u1 V* W  L1 t7 N1 A8 O6 g* ^/ b# E
return found;
6 T( P4 {; r) X/ S; }6 `" ~% O7 Z2 @, i- O}" U- D5 Z. U  C( b
( T0 U9 \9 O4 g( f
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,) W9 m; e) u, g# Z3 Y
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。2 `$ ^# b' I. d5 l
//将这些或数转换成相应的用逗号分割的状态字符串。  G% h# g8 h- J6 t% e$ `6 k; o
UINT GetObjectState(IAccessible* pacc, - R: P% |0 K8 _$ v( i
                    VARIANT* pvarChild,
) m+ J/ i3 {: H' e' o1 C                    LPTSTR lpszState,
6 |; t" i5 }4 [; B7 `/ J                    UINT cchState)
3 ^! q' N- G; U0 q{3 z; Z5 V+ C) i/ g. x0 p, `* R
    HRESULT hr;2 a/ P% v% X2 T# s% `7 }
    VARIANT varRetVal;( [* ?/ A% e) S) I+ @0 p- b3 K

8 V6 T( `8 {. E/ d) k0 o6 s2 F1 `    *lpszState = 0;
# Y3 i) k! C  P  h' g  N" K/ q# V
6 a# Z5 `! w9 a" {9 I5 {    VariantInit(&amp;varRetVal);; j) W. F! c+ P$ t* W
4 J! w" Z7 {- d7 C5 o! V6 e
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);) a9 V: h2 J, q  V$ P( z/ Z! h
3 s& z4 N. Z3 @# I. x
if (!SUCCEEDED(hr))
6 e2 j* a6 g) F; |/ D0 H        return(0);; w& ]& I  \+ T
4 D% @, p& w* r, ~9 z8 C9 K, f
DWORD dwStateBit;  }- B( t/ ~9 {$ h. Z1 J
int cChars = 0;
# x$ v, `1 z) _    if (varRetVal.vt == VT_I4)+ U1 R2 z) V+ t: u! Z7 f
{
' G+ I9 c4 p; k0 D! o$ z7 A' ?// 根据返回的状态值生成以逗号连接的字符串。$ b( D6 \/ a" A6 O3 l! i
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; 8 f* W* k5 `+ v+ \6 Z
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
( j, P) k* k9 y! J) E$ a. ~5 H               dwStateBit &lt;&lt;= 1)
+ N) N6 T2 r0 m        {
: {: F' r  w2 d; |            if (varRetVal.lVal &amp; dwStateBit)3 n4 d8 q1 H, U9 p1 N
            {
7 ?6 d. n# I) U% Z                cChars += GetStateText(dwStateBit,
( J7 D2 \( e7 J- L4 M3 L: V                                       lpszState + cChars,
' y1 j3 s' s3 T0 R; H/ S                                       cchState - cChars);
* j' h3 Z5 K/ p/ O8 N*(lpszState + cChars++) = '','';0 B7 |) ~6 I) d# q1 K, j  ]) y; L
            }0 c; s* t6 [# r. Q8 F
        }
* c$ T$ J6 V& z/ M& m" A! zif(cChars &gt; 1)
" S/ h* N5 k; k" [7 s4 `*(lpszState + cChars - 1) = ''\0'';
( E; o' i2 J9 `8 |4 z. |) u    }
/ V, t/ q: y7 T: G    else if (varRetVal.vt == VT_BSTR)0 ~  e' Q8 ?, t" ?1 R3 d/ n
    {
: U1 K- O' S/ L! X        WideCharToMultiByte(CP_ACP, ' A6 H; m5 q: [5 U4 k  e
                            0, 9 q# {, a6 U' W; ~$ B0 D( l
                            varRetVal.bstrVal, : r/ q6 L0 o0 w- P7 F0 b
                            -1, - P0 ]$ v8 m! l) h: [
                            lpszState,& `- a1 s- Z$ k2 d
                            cchState, 4 A& D  p! b3 a% q0 M/ n* A! k
                            NULL, # I9 p' [1 e4 g. h, C- \
                            NULL);
- v7 g# g% r) F8 N: @% V    }
6 j4 s, h1 v. b- f8 u: ^' V& j: v. k1 @- x2 L3 s
    VariantClear(&amp;varRetVal);! M7 r( Q% {/ B+ E
2 p  J9 p% x2 W
    return(lstrlen(lpszState));" e( N! u9 x3 _9 t1 N
}</FONT></TD></TR></TABLE></P>: M: E& K/ z2 d
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
6 O" d! j" o* p# Q; ]% Z8 A, E
四、在 IAccessible 接口上执行动作
* B+ F0 `. D* h5 M: g1 F2 Z: G& I    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。# j4 u4 M4 e2 w) G1 d3 _4 X# R
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
3 ~# R. h7 W/ t( b% S8 X* {    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
' V; M( r* @6 {$ x4 a<>
3 g5 [5 r3 M! \<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
: \4 a+ X9 o5 z9 a  |2 k9 o" Y% n0 P1 G, k
<TR>9 |5 ]9 p' w. B# x* z4 t! E- f9 A2 m
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
9 x3 m; c! w# e+ m1 J; N2 nif(1 == FindChild (paccMainWindow, "打开(O):", 0 X5 x+ ?: j0 r4 F" J
                   "可编辑文字",
! ^* t1 Q1 y: p: e                   "Edit",
1 F0 y0 x" \' [  ?: J6 m                   &amp;paccControl,
( R, l, j1 U9 x7 r* n& o                   &amp;varControl))
: o1 M% ]' Q1 t# j  o8 K{3 {1 L1 m! h4 |$ E+ h: N; o, O( i
//在这里修改文本编辑框的值: b. L- n+ b6 ~+ ]4 ?
hr = paccControl-&gt;put_accValue(varControl,
: [' D4 A% G) U7 U9 P* n                                  CComBSTR("regedit"));
+ n5 k( s8 t2 Q6 q! ?. ?( z3 ~paccControl-&gt;Release();
; s' s+ u  n1 C3 ^1 gVariantClear(&amp;varControl);! s7 I. h0 a  t; E- y- z. _
}
) ~' Z9 V# k. I) d" f! m& u- Q( e8 i7 h4 P7 j7 h) u4 F
// 找到确定按钮,并执行默认动作。
4 Q1 W1 d/ i( |; Lif(1 == FindChild (paccMainWindow, 6 ~0 V3 v& m9 K. L+ m' x' s
                   "确定", ; A4 m7 G, e1 _) M9 N
                   "按下按钮", 6 V; c9 }" O$ t5 J7 w9 A
                   "Button", : m7 s. w* K- [7 B7 Q
                   &amp;paccControl,
+ B5 F' E2 u  f9 e" X  G5 {( H                   &amp;varControl))' K+ q! x: |' s3 F" L6 p. P
{
2 {# W9 u0 ]1 R, ~7 A$ C//这里执行按钮的默认动作,即"按下这个按钮"
& o6 H& c! K* _; Ehr = paccControl-&gt;accDoDefaultAction(varControl);
. O0 M( Z! @: {! c1 G: ]7 ~3 rpaccControl-&gt;Release();* ?8 V9 E* L. D+ d
VariantClear(&amp;varControl);
2 |! l8 R5 A2 w}</FONT></TD></TR></TABLE></P>* K- k1 d1 \; Y6 K3 q
<>现在,你会发现已经成功启动了注册表编辑器!!9 _5 [" N: e+ u* o4 T5 q
</P>
, @* k& @. l7 Z  ^. f6 b& B1 J$ o<>五、模拟键盘和鼠标输入
# Z/ R: X0 S( i    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
. W1 E/ M' B1 U% v" _* L    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。, G& S2 o6 c6 |( Z& P$ q: @
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
9 ^8 z% X' D$ w4 q2 w下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>- q' X" m( w' I
<>, H; g! s0 N( p1 b8 t
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 c/ }" m) U) w
$ N. E' s& c" E) M' }: K<TR>0 y8 E; P( y& ?' [6 G) W, T
<TD><FONT color=#00309c>INPUT input[4];
, n2 J9 l1 E& ~8 e! `# d  Q7 \memset(input, 0, sizeof(input));: A+ F, z4 @* A

! q6 c4 y$ B8 [3 }5 ^  g//设置模拟键盘输入
9 k" d3 P. x% u) zinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;% b4 |' P" C- e! b+ y8 K( J
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;( ^& O5 m9 W3 W2 b2 P
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;% N8 g% ]; `8 I! N

4 ?  @0 J6 |  }: V/ v, Q// 释放按键,这非常重要
! `" c! E% L0 {, |input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;% i+ Y. G, |; S6 u. f$ m* K

1 F" Q3 K; B! E2 o& RSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>, H$ [! `* L4 e/ a
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)0 G3 n  P" Q& K% D! ~. w  l
</P>
6 }1 _! v$ K% u% h6 Q<>六、监视WinEvents
# t% o. ?5 {, E2 ?0 A( l& F    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
$ r1 ~3 t' e, Q  L( z! D    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。- Y- p; y8 U7 b; h
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
+ y6 X2 Y% |9 t# {<>
& z$ c% H- }& B" j<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
2 c) u8 k7 X& p5 l" m( L
4 s; L+ H% j8 B: Z/ T! k<TR>
. e8 R3 j9 R  _& C<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
" A- {9 F7 p/ I7 t* e; E( N{
+ p( w6 q: ]" E9 \# \% NhEventHook = SetWinEventHook(
/ _& ?* [) w+ D" YEVENT_MIN, // eventMin ID
' u0 V; Q1 ~* K3 `+ cEVENT_MAX, // eventMax ID9 p: [" k$ [! k+ v$ Z+ j, t% c5 J
NULL, // always NULL for outprocess hook; t. u5 m( x7 [6 s, K  v4 i
WinCreateNotifyProc, // call back function
/ d4 ~8 T7 B/ @" @$ t0, // idProcess
7 I6 c( e* o( @" a1 P, [0, // idThread # S% h( W4 \4 w9 Y6 J1 l( u
         // always the same for outproc hook
8 D1 t" w8 I' m9 E3 N" c: eWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);" F7 }+ f0 J9 [7 f' ?
}  </FONT></TD></TR></TABLE></P>
) ^+ s1 K* E. |5 v9 }% t  m4 p
. V' L' G' H0 w<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
* T3 ~% ^$ @2 o- z6 k下面是回调函数:</P>
! V2 z; c9 W$ q) c# }. l<>; ^4 n) v7 X0 T
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>6 B8 P1 w$ `& F0 `: E

1 e1 o; M* v7 R/ H( s<TR>
( j/ F" W6 \  }( r" ?9 x6 K/ [<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(7 ~8 r! `0 a2 N& m- S: }/ R
HWINEVENTHOOK  hEvent,
1 s2 Q1 R6 q/ t, a# PDWORD   event,
3 p$ |4 ~8 |- g( ^  vHWND    hwndMsg,
- x- g7 {  p4 bLONG    idObject,9 ]8 e& v. M( O$ l' T! u
LONG    idChild,
& ]) ]: w, a+ X" v6 t" y, CDWORD   idThread,+ M) k' b( F+ U4 I
DWORD   dwmsEventTime0 s% g0 d9 G) `1 @1 J4 B
)
& B  A4 |9 \4 }, R{9 w3 [! t& A& A! Z# E! J; S( s

8 i: y  J8 j+ a3 i- `* Dif( event != EVENT_OBJECT_CREATE)* O  `7 t% W7 i; r3 n
return;
: W3 S2 M5 t0 {" w5 ?: \* @  Q; _. R& B3 \. o
char bufferName[256];
/ l0 ^0 h$ Z7 t2 t; iIAccessible *pacc=NULL;
, c) e0 J9 H8 z* m( iVARIANT varChild;
2 o1 i6 P! N" x* o0 n- k* o+ U3 X    VariantInit(&amp;varChild);
% n1 t( O/ ^! l: F% W; g//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
- K& `7 A& u! m( cHRESULT hr= AccessibleObjectFromEvent(hwndMsg, 2 d; p0 S: B! T  M- W
                                      idObject, 0 g& ]8 w5 q" x) A
                                      idChild,
* ~  J; c2 y/ z. E+ ^                                      &amp;pacc,
& O6 P) Z6 N& h+ ~                                      &amp;varChild);1 D8 s* L$ L3 c. ~  V, f

9 w6 V. ^9 ]5 [6 L* Bif(!SUCCEEDED(hr)): l1 _; Q( ]$ Z  Y
{. A! @0 g! K* R
VariantClear(&amp;varChild);# x4 o% \/ c$ l4 Y
return;% j! S3 f( K3 o/ b4 d
}
, x# n8 @0 N5 T( z* ^//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。  n0 n: r( {3 K& }
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));! R6 h2 E% K( n0 \
if(strstr(bufferName, szMainTitle))
. k+ H+ R3 V1 {; N; uPostThreadMessage(GetCurrentThreadId(),
& z, o4 @& I- p/ k5 q* h( Q- A                  WM_TARGET_WINDOW_FOUND,
2 X- v- z- \7 {1 h1 \2 y# m4 ]2 g- |3 z                  0, - p$ @7 |4 L# Y7 q5 r
                  0);
( G+ @' J! e' B1 P9 f0 n% L$ A0 q' N( [7 B$ D" [9 J: ~; i& X
return;* Z$ i, N1 J4 u* O' X* ^& f
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
# q4 `: m0 v% y: E! {( B2 i2 m7 [/ R- H4 P6 P
附录:8 Z% W! G9 G1 C+ h4 O+ p% Q

! R+ z* y  B( I' `# f+ Z5 `& [/ l关于IAccessible 接口/子ID对:  ]) m/ {% `. b( o) d; r$ M
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
' d) [6 m: q3 Q( M( C2 t6 E    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
" _1 N  j# j) z' s    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。% \0 W! ^/ k/ w, P
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
, d. E; E9 v4 g, d: o    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!7 M4 ~. k. e2 H9 z

9 T8 Y) |+ O: S1 l注:
3 ~* m* |: ^; a    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)* L  T5 m0 }6 |2 ~2 a5 y# M' S
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。3 ~5 \8 z+ K9 m4 v' R/ G

/ M! r  Q4 s! `参考资料:
5 v# O/ m1 b  ?/ f" j5 z/ M$ L<UL>: R/ F+ n, F, k
<LI>1、 Dmitri Klementiev写的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》及其源程序。http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx
6 H1 S8 k+ b( U% ?/ R' C. q- D<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-10-9 09:21 , Processed in 0.688102 second(s), 57 queries .

回顶部