- 在线时间
- 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>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">©</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">©</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**)&paccMainWindow)))& K; V1 c# l Y+ r, \ D
{+ `) o( V7 b% }/ P; h) E
//……我们可以通过这个指针paccMainWindow进行操作。
0 z2 g$ X8 o- FpaccMainWindow->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 &paccControl, $ {; N$ @' Q5 g1 x" }5 J+ }! \
&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 -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & 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 -> Reset();$ E1 R/ M1 x* ^$ E: k/ m8 c
3 _9 |! ?' h. v* {/ d7 @- ^ ?//取得父亲拥有的可访问的子的数目3 E5 E& X# J+ f! A. ]/ R3 W
paccParent -> get_accChildCount(&numChildren);' k( F3 _9 {) w0 \! \, J
* i, u9 y* d: B
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。% A+ N' i& z$ g( W
for(index = 1; index <= numChildren && !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 -> Next(1, &varChild, &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 -> get_accChild(varChild, &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->QueryInterface(IID_IAccessible, (void**)&pCAcc);
* B8 t. e- j ]- F3 Ahr = pDisp->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(&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
&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->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, &varChild, szObjName, sizeof(szObjName));
. l0 I+ D3 [9 H& D E//通过get_accRole得到Role
5 I7 D1 ` }4 FGetObjectRole(*paccChild, &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)) &&
: n" h) o9 L+ v) z" n7 Y8 Q (!szRole || , d8 T' s: v1 x: {! v# o* C
!strcmp(szRole, szObjRole)) &&
+ 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 && 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->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 -> 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(&varRetVal);
4 p5 h! G0 W, p/ _9 y& k8 D
O& X; K; M* D- d1 A1 V0 n% y hr = pacc->get_accState(*pvarChild, &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 < STATE_SYSTEM_ALERT_HIGH;
. S8 d) O5 }9 W% ~' l% V9 Q dwStateBit <<= 1)
, G, G8 Y3 n2 l$ | {, g6 {1 G4 Q# V5 h8 F' y
if (varRetVal.lVal & 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 > 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(&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
&paccControl,
# H2 c; O8 t& ]- r7 m2 p. ~% b &varControl))
& i0 E2 }6 o F9 W{
6 K. U O* C, t% t6 k' h, ]//在这里修改文本编辑框的值
2 b* F, v5 Y5 D5 ]& phr = paccControl->put_accValue(varControl, % G+ F: v" ^6 }4 `: y3 t
CComBSTR("regedit"));& K: \4 W, a4 ]! A7 v
paccControl->Release();) R& ^; ^ z; v7 p. g
VariantClear(&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
&paccControl, 8 N8 {- i5 z; V/ Z; S9 r+ y. o1 ?
&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->accDoDefaultAction(varControl);! d H+ `& s3 B# Q$ k8 t
paccControl->Release();% n# f& m; M: c$ p3 q
VariantClear(&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(&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
&pacc,
1 x& t3 M: t' W* q, M) M% G$ t) L0 g &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(&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, &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
|