在线时间 0 小时 最后登录 2007-9-23 注册时间 2004-9-10 听众数 3 收听数 0 能力 0 分 体力 9975 点 威望 7 点 阅读权限 150 积分 4048 相册 0 日志 0 记录 0 帖子 1893 主题 823 精华 2 分享 0 好友 0
我的地盘我做主
该用户从未签到
<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 b 3 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">©</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 u provides 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' T reliable 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">©</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 J IAccessible *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! j if(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**)&paccMainWindow)))
* r1 L0 Q# Z$ ~* V& B {
! W1 [4 a! X, r- g3 I6 ?: q //……我们可以通过这个指针paccMainWindow进行操作。
& J) `; D( f. r paccMainWindow->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 K FindChild( 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 &paccControl, 9 X* G# _2 q+ ], e( S. ]
&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 f HRESULT hr;! h/ z" M m8 S8 o+ @
long numChildren;) M1 q: r4 l$ |. t; s
unsigned long numFetched;
. a/ o1 L: ^( j" M VARIANT 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, G IEnumVARIANT* pEnum = NULL;
; u9 Z. N" j; e9 H9 d1 |) g IDispatch* pDisp = NULL;
' {$ d) ~4 _+ O) Z BOOL 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$ X hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);
4 t2 j1 X9 t; u; [$ e/ C% S. S
- B2 |* K4 z l1 z- K' h) h9 o if(pEnum)
' y9 M; @ L. G pEnum -> Reset();
; }! c- l/ w* a
1 y# t# P" z* P) D$ c- @. M; D //取得父亲拥有的可访问的子的数目
0 V. G* i/ W% W* A paccParent -> get_accChildCount(&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 <= numChildren && !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 -> Next(1, &varChild, &numFetched);
! N2 j8 Z# W' C+ I else: 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' n varChild.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! E if (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 -> get_accChild(varChild, &pDisp);
, [3 h S. y# G! _% M }
! y# ~; i/ n" H7 ? {) v; Q else7 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 S hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);; S, C) K% }6 a2 b7 Y7 m
hr = pDisp->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(&varChild);7 ^* t. J7 v* v3 c$ G+ M1 `8 E
varChild.vt = VT_I4;
4 z" R Q" m7 M! @; L3 Y7 a6 g varChild.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 `( [% d GetObjectState(*paccChild,
& u7 Q* _' @ ?; \ &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 D if(NULL != strstr(szObjState, "unavailable")). B9 ?% k) T' N, c
{
9 o$ ~' P# o6 }; c$ c if(pCAcc)" V$ C$ X$ S( j4 V, C. F
pCAcc->Release();
: N" J9 n: p* c; V5 s: u continue;
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, &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, &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! B if ((!szName ||
/ o, e! ]! d5 N6 M7 U: x1 c !strcmp(szName, szObjName)) &&
" y0 w3 h" ~; N (!szRole || 9 \$ A0 u0 o) K6 F) u, L
!strcmp(szRole, szObjRole)) && * @! 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) U if(!found && 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: w if(*paccChild != pCAcc)
/ G+ Y9 M9 U& p( a1 J5 C pCAcc->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! F pEnum -> Release();
2 r) y1 v% Q! u1 V* W L 1 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(&varRetVal);; j) W. F! c+ P$ t* W
4 J! w" Z7 {- d7 C5 o! V6 e
hr = pacc->get_accState(*pvarChild, &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 < STATE_SYSTEM_ALERT_HIGH;
( j, P) k* k9 y! J) E$ a. ~5 H dwStateBit <<= 1)
+ N) N6 T2 r0 m {
: {: F' r w2 d; | if (varRetVal.lVal & 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! z if(cChars > 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(&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 n if(1 == FindChild (paccMainWindow, "打开(O):", 0 X5 x+ ?: j0 r4 F" J
"可编辑文字",
! ^* t1 Q1 y: p: e "Edit",
1 F0 y0 x" \' [ ?: J6 m &paccControl,
( R, l, j1 U9 x7 r* n& o &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->put_accValue(varControl,
: [' D4 A% G) U7 U9 P* n CComBSTR("regedit"));
+ n5 k( s8 t2 Q6 q! ?. ?( z3 ~ paccControl->Release();
; s' s+ u n1 C3 ^1 g VariantClear(&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( |; L if(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
&paccControl,
+ B5 F' E2 u f9 e" X G5 {( H &varControl))' K+ q! x: |' s3 F" L6 p. P
{
2 {# W9 u0 ]1 R, ~7 A$ C //这里执行按钮的默认动作,即"按下这个按钮"
& o6 H& c! K* _; E hr = paccControl->accDoDefaultAction(varControl);
. O0 M( Z! @: {! c1 G: ]7 ~3 r paccControl->Release();* ?8 V9 E* L. D+ d
VariantClear(&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) z input[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& R SendInput(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 \# \% N hEventHook = SetWinEventHook(
/ _& ?* [) w+ D" Y EVENT_MIN, // eventMin ID
' u0 V; Q1 ~* K3 `+ c EVENT_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/ @" @$ t 0, // 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: e WINEVENT_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# P DWORD event,
3 p$ |4 ~8 |- g( ^ v HWND hwndMsg,
- x- g7 { p4 b LONG idObject,9 ]8 e& v. M( O$ l' T! u
LONG idChild,
& ]) ]: w, a+ X" v6 t" y, C DWORD 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- `* D if( 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; i IAccessible *pacc=NULL;
, c) e0 J9 H8 z* m( i VARIANT varChild;
2 o1 i6 P! N" x* o0 n- k* o+ U3 X VariantInit(&varChild);
% n1 t( O/ ^! l: F% W; g //得到触发事件的 UI 元素的 IAccessible 接口/子ID对
- K& `7 A& u! m( c HRESULT 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+ ^ &pacc,
& O6 P) Z6 N& h+ ~ &varChild);1 D8 s* L$ L3 c. ~ V, f
9 w6 V. ^9 ]5 [6 L* B if(!SUCCEEDED(hr)): l1 _; Q( ]$ Z Y
{. A! @0 g! K* R
VariantClear(&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, &varChild, bufferName, sizeof(bufferName));! R6 h2 E% K( n0 \
if(strstr(bufferName, szMainTitle))
. k+ H+ R3 V1 {; N; u PostThreadMessage(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! {( B 2 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