在线时间 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>
) N$ t+ K* c; L% h! k) \/ P7 ?
4 Q. R, |, n3 B! U1 X) b <TR>
9 q+ [+ l- H/ y& X7 t7 `% ~ <TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
- F8 j6 ]) J. b0 R/ n1 S4 f 原 作 者:崔传凯
7 }0 f$ s9 V. p 原 出 处:vckbase
B5 [$ B) o8 G |( F9 q. V9 u: Q# m 发 布 者:loose_went
% Z6 T; Z" ^& r. F) e6 X, p/ ` 发布类型:转载+ a8 h( e* ?0 y: m
发布日期:2004-11-12% P. C# R- J) A( Y/ U
今日浏览:6
$ R! L+ N) }4 {5 H; S 总 浏 览:234
/ Z* ?3 P5 t* h </TD>2 _5 V; W+ \+ H
<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>) Z6 }8 G& a1 q2 D0 _# O
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>9 [2 t) y5 v8 y7 n8 o: C
% X+ }4 q. M! j; x- R
<TR>
+ E$ i+ l8 {9 `# \ <TD vAlign=top align=left bgColor=#ffffff>
$ g6 Z, |7 j2 W6 M$ @: `
' ]# C( P) o6 T) z' ~ < >* m+ |! d9 E2 F% B2 i1 H K( q0 F
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>! p/ }1 @4 z( E/ _7 g+ z
5 c/ t- g- Q" _ [# p# d0 X
<TR>
* T0 g: g8 X5 N; n <TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">©</FONT> Active Accessibility 2.0 is a COM-based technology that improves the ; K$ B9 s. `5 `4 M' T- u4 Z9 Y! `
way accessibility aids work with applications running on Microsoft Windows?. It 9 P. O9 W Z0 N* z& V) v
provides dynamic-link libraries that are incorporated into the operating system : z8 c( A t) Q5 N# M; d
as well as a COM interface and application programming elements that provide * v* w5 x7 h" C( n
reliable methods for exposing information about user interface elements. </FONT></TD></TR></TABLE></P>4 C" Q2 B* m I5 L3 F5 L3 j! S2 ?+ k
< >一、基础
# z: |& E4 N1 H/ S9 n: m( U Microsoft<FONT face="Times New Roman">©</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。: Z6 x: i: }1 T3 s1 q' u# c
Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。: L, ?. }, ?/ `1 E' V! Y
每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>" x4 T3 q8 O: t9 c$ U" {- E! x( C
< >二、Active Accessibility 原理" d" t- u. y% G4 U
Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。: w3 ^. L- y9 B! U! [; p
当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。/ b# j& I1 F3 G" M1 i
内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。9 `) a1 \1 c2 B4 N
如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。( R$ e) _0 C( Q6 H
Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>6 G" Z0 Y/ _) [6 L
< >三、如何得到 IAccessible 接口指针1 y. o `7 f9 M* w8 ~3 ?
每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。3 [% O. g+ C/ r$ c+ e% @
有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
% W) {# l' x& n) y9 B* ?, e1 u IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
- Q" @- i6 [& \8 c4 c/ |0 _ Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。7 h! O4 E$ G9 {5 t- q* {
好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。7 z3 K" J4 C* t8 ~' T4 b
因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
) z# }" O, Y9 Z8 q; p 下面结合代码介绍一下它的用法。
: @: ^5 y/ c3 a& B 我们来得到下面运行窗口的 IAccessible 接口指针。: F6 D L8 x: L
& U. [, o* Q6 N6 \
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
+ Q/ H7 j. H) N2 v < >图一</P>) V* N. ]" _, y
< >3 x4 Q k2 C$ L# c& D
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0># ?- |8 Y! Z" }; u% M6 e
0 F. c! _' L' |; ~ <TR>: |% d$ [- Z; [# `, H4 Q
<TD><FONT color=#00309c>HWND hWndMainWindow;: W* P U1 F& s5 N$ O7 _
IAccessible *paccMainWindow = NULL;# N! y% x% T) T" |' y, g2 h0 n2 M
HRESULT hr;) `9 s0 j4 t( h# L/ H& e
//得到标题为"运行"的窗口的句柄" Y( C" w/ G0 U8 a1 J+ u; q1 e
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))& r4 r3 R3 `0 q
{
8 n) K$ z! w0 r A* a t MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
1 G* E4 G% u" ~5 ], @ }0 W+ M, U8 f" ~ z; e7 L
else
$ G* k8 A7 W" |. ~8 ~+ k. {! e9 @ {
0 Y/ N# {: c+ d2 n) z- D //通过窗口句柄得到窗口的 IAccessible 接口指针。
$ ^4 N4 \9 b; _4 a e7 C3 N) b if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
1 q* F2 }) A' K. n& _ OBJID_WINDOW,
* z% h& ^6 g: k4 D IID_IAccessible,
# h' u8 @. ~/ z( {( e (void**)&paccMainWindow))): I { g( L; ~6 h( r9 w! n
{ i# O6 p2 D8 h V/ B
//……我们可以通过这个指针paccMainWindow进行操作。
r: v U8 [ }% E4 r* r/ p) ~ paccMainWindow->Release();# S' e, W+ ?& u, e3 z2 M( ~4 v
}9 q0 d1 k9 [" M0 S' |1 F6 K9 p3 \
} </FONT></TD></TR></TABLE></P>
( L6 [7 P4 E- m8 v5 C; L( t0 ] < > 现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!2 \1 u0 `' W6 l" C
首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……* `! e: k. T2 e1 Q
然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>" j6 p& h; U9 [* ?. Z( w6 i
< ><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
) R2 _6 ~4 ?5 i( h2 e, ` < >图二</P>
' v4 j2 s+ c7 }$ R, k < >我们现在主要关注的信息是:Name、Role、Window className。</P>
% @3 w& D6 o* d% G" H1 k < >5 ?; u1 W5 Y8 z: e4 ~$ g3 m
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) y1 B2 S& K% \) \/ C3 K
+ M& O3 x9 m* u, @6 H
<TR>5 v, Q& L# F( h0 V
<TD><FONT color=#00309c>Name = "打开(O):"& e. v; w% w5 ^
Role = "可编辑文字"
8 B5 `; T9 { K) A7 d Window className = "Edit" </FONT></TD></TR></TABLE></P>
p- h' `; R9 u) k2 j0 Q- g5 c: i < > 当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。- x' W8 Q5 P8 Z% B
FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
: j/ A9 ^; Z& ]+ C) j 下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
( f4 D! H3 [9 j( d8 u$ }# z$ V < >/ m+ y0 m0 c( { S2 B8 _% z( `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; `2 v& l; y4 W# ?8 z# h" v
& k! H( C& E9 p4 m/ c- v
<TR>
, _9 t; W/ M# ~( N2 X+ r <TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
, c2 G) D; b- x1 ^/ I# q VARIANT varControl; //子ID。
9 v5 c+ l0 V6 D' t& }# o9 q3 s ' v3 b. `: p0 f5 F
FindChild( paccMainWindow,
- ^, ]- s) E4 i; X "打开(O):", ; R& p% J, s A) ?5 o; g. F
"可编辑文字",
s. d5 E0 X* \) ^! l* J+ C/ j "Edit",
9 A7 {1 g' w5 K7 }( h7 x. G8 z, E &paccControl,
: z, Y+ n6 Q o &varControl ) </FONT></TD></TR></TABLE></P>: d2 H, y! F5 u4 [2 c6 L! l
< >第一个参数是先前得到的窗口 IAccessible 接口指针。+ o8 S1 `3 t% u1 _) |' N. t. W
第二、三、四个参数分别是名字、角色、类。
; w! q& D9 R/ D1 H 后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
/ m2 v1 [% H) j$ ~' @ < >
9 g# q R. G, N# B, P/ B; j <TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 r& i# C- w$ _
1 }* b7 w8 R1 B s; {$ D
<TR>
6 e, P ]* E' C3 m) q$ ] <TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, + K: s M9 c( r
LPSTR szName, LPSTR szRole, / w' K% ? U; y! B4 `4 F6 E
LPSTR szClass, 2 Q- z; v% X6 |# O& ~5 ~
IAccessible** paccChild, + M) x; ^2 v; m5 X3 x
VARIANT* pvarChild): \ m0 n W4 p
{
# |% u7 D( J1 o' d% Q- a5 t HRESULT hr;1 W7 H4 D& l$ @# ]5 l
long numChildren; u7 B$ j+ u$ P) i- f+ P% n) D
unsigned long numFetched;$ h( p/ w6 m- Q7 }
VARIANT varChild;. D0 D' L! {! ?
int index;
* \% }. h1 E) d! |2 c" Q; H, w IAccessible* pCAcc = NULL;) g- o% U% k( D' _
IEnumVARIANT* pEnum = NULL;# j" t" G4 i$ X, ]: W! q+ z7 |
IDispatch* pDisp = NULL;
6 ?, ]0 K1 I4 ~# r9 c" f" t# u BOOL found = false;- N) |7 y5 W7 Y2 ]+ D
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
$ y, e( \1 U3 u x; @7 {
& C# e% g+ T! a4 b //得到父亲支持的IEnumVARIANT接口
Y& C5 y( U, @3 g. C4 n S hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);' Z7 p5 s+ y F
( R3 u1 k2 D; [" V: r9 ^
if(pEnum)! v, f6 f9 m2 x8 u! c' K& k: b
pEnum -> Reset();& q0 T# M) k; n9 J3 {& F; }: K
8 O" Q0 r W8 s/ u% H
//取得父亲拥有的可访问的子的数目2 S5 t; S* h& T0 I7 h/ Z# t
paccParent -> get_accChildCount(&numChildren);
. @3 ]& ]; z7 V- d7 P" A8 R! Y
" x& u9 n; R, z" \' b# r% o9 X //搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。% C6 Q4 Z$ f. ?& @" f6 b6 N W
for(index = 1; index <= numChildren && !found; index++)% b8 t4 c# d- x9 w% N. @$ x- G2 n
{
$ ^! K3 I3 l c9 @% E pCAcc = NULL;
! A! l3 t. n K5 g& R( N* z4 J- q* f$ T // 如果支持IEnumVARIANT接口,得到下一个子ID' ]0 ? ^0 a5 m, R' S
//以及其对应的 IDispatch 接口
( _9 l% t1 C) W, g* [4 E$ K/ V if (pEnum)
5 K, o, q3 G4 C3 @ hr = pEnum -> Next(1, &varChild, &numFetched); & ]" r; e8 A" o5 \$ J; l
else* r% _5 q! j9 y4 g8 x1 f: L5 q
{
9 w: t* I/ I/ q //如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
' _& m" ^' S$ g; u8 M P varChild.vt = VT_I4;6 Z/ d4 Q1 R5 \) e! n+ L
varChild.lVal = index;. V0 U( m* d* v; F) b) R: n+ O
}, h* H1 `: ~% o* n8 Q
$ v. o" i( ]: c- \3 k
// 找到此子ID对应的 IDispatch 接口3 g- z7 O6 T! O
if (varChild.vt == VT_I4)
/ j4 E2 ?' f+ y. W4 k9 b {0 U5 |* q, K: r# g
//通过子ID序号得到对应的 IDispatch 接口! R3 F* t# c' Q- c/ P. h
pDisp = NULL;
5 ~& z! N+ e- d+ i/ ^ hr = paccParent -> get_accChild(varChild, &pDisp);% A3 Q. x8 L+ n. U/ J+ I
}- ?; n4 `" S/ \- G; s5 v. B/ C7 h
else: B; W# L: m& F" `( p) L
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口. K7 h9 i0 x3 R
pDisp = varChild.pdispVal;% i' z% |1 g* J7 _- C/ G
! f0 M$ {5 E, f) L# F& B1 _1 Z2 W6 W
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
; T) c) U! `, ?. @6 G if (pDisp). x4 k7 s7 M, Z" Z ?5 r3 Y
{0 M A- X: w2 l8 z6 u* B" b
hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);
0 s3 M. S; W* [ hr = pDisp->Release();
% N; ^' e- u& B# _$ I# Z }$ z# H7 J2 j7 f
2 P8 d- j0 E* F: x* ?8 N8 F% U // Get information about the child% r& N. q" A' J! Y5 w3 m
if(pCAcc)7 {. U/ C2 i) x) x# T' h7 b( i J
{- {% i9 M9 U p) @
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF% I' ~! b3 e( \; d) F4 J
VariantInit(&varChild);
' }6 M# z/ |: |) _ h! v$ B varChild.vt = VT_I4;
3 w! g- y5 a: l- A9 J \# Z3 l# j varChild.lVal = CHILDID_SELF;% G, {9 E+ v6 ]: W6 {' h9 F! `1 `
* r7 r$ T3 t. c; H5 C
*paccChild = pCAcc;2 {2 Z4 n/ f' }1 O& x1 L5 d
}8 D# E$ @' }2 Z5 Z3 @
else: ?$ y6 @' S/ d4 T2 x
//如果子不支持IAccessible 接口
1 W* W; A8 ~9 r n+ Q J *paccChild = paccParent;
* e) B" W5 Y/ W$ f1 y
! h9 t. q- L* ~ }# T0 W //跳过了有不可访问状态的元素9 m- B' y6 n# h$ Y% z9 A# a2 U
GetObjectState(*paccChild,
8 C# j* j4 Q( j &varChild, 7 d+ Q/ G7 o) H# i' R5 d a/ `
szObjState,
" L B0 ~- r8 E% _) a* U sizeof(szObjState));
! N% r9 V! x. g* m) ^6 N if(NULL != strstr(szObjState, "unavailable"))) {+ [& `3 V5 q1 T
{$ M* ^. R0 C7 ~) R6 |0 W* O
if(pCAcc)2 b2 i% _$ J7 M, \
pCAcc->Release();
- z4 Z, ^5 c/ u8 `7 O- a+ c; p9 y2 \ continue;
+ V! f: |, X# ?5 ?) T }
% C* ~- L% p6 j1 @6 u //通过get_accName得到Name
" i1 i- U9 b' {. W4 h: S5 E GetObjectName(*paccChild, &varChild, szObjName, sizeof(szObjName));
2 a3 |4 R$ R3 X! a //通过get_accRole得到Role4 t, A7 Q6 y6 y. `2 d; m9 ]
GetObjectRole(*paccChild, &varChild, szObjRole, sizeof(szObjRole));4 k4 B5 ~1 H' L% V1 k* L
//通过WindowFromAccessibleObject和GetClassName得到Class. X' F% h# M1 }7 ~ [9 P% I7 L" U
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
( w" {7 s6 j8 `: A* y0 J/ E //以上实现代码比较简单,大家自己看代码吧。
! e, ? T9 [# _6 ?6 |2 [ 8 F; C- ^7 Z. L3 l, L, C* c5 ^
//如果这些参数与输入相符或输入为NULL
* _9 i* C3 ^* S! e2 I8 S$ U/ n if ((!szName || \( U4 ?2 s. Y0 Z( T
!strcmp(szName, szObjName)) && - U; Z' A" a; ~* U9 w N3 V) v I
(!szRole ||
6 Q' Z t" w% J/ S g !strcmp(szRole, szObjRole)) && , Q" G* A( j* e2 [/ ]
(!szClass ||
) _! N7 T9 D( I( w s! m !strcmp(szClass, szObjClass)))
+ Q; ~8 _7 k0 J; Q {
) ]1 g8 f0 `- Y: T, ] found = true;
- U% T# c4 ^ U0 r$ g *pvarChild = varChild;5 ?( {4 G5 [' ]
break;
6 V9 h0 `& a% U% P! F3 C3 L }
9 |1 _3 P4 h* `2 A4 S if(!found && pCAcc) G$ n9 | \- Y" H
{
" n4 x* L$ |8 W% a4 a // 以这次得到的子接口为父递归调用# R7 ?# a y: N0 x, Q r& l+ w
found = FindChild(pCAcc, 2 [7 G) X* C% C) l2 k1 \; n# p
szName,
# B' Q ~3 e( X5 y: I szRole, $ T7 q( `5 m' L( S c% m% w) }& |% t
szClass, 8 k- D8 j# V* L# X5 o2 e; I
paccChild,
/ f: Y- N: h1 h( t6 x" N pvarChild);! D6 ]" C! p! _) H$ `7 {$ m
if(*paccChild != pCAcc)
, L& ?) U0 W3 z+ E pCAcc->Release();1 k3 T A" d7 ~. r
}; ^% O6 T c' i* Q
}//End for! M5 W- R4 E3 d0 g- b
4 \+ W4 \: c0 W& b7 Y // Clean up( S- e$ [! `2 v0 \8 M
if(pEnum)
% P4 H# [7 @2 K8 b( Q pEnum -> Release();
' g1 j' H7 F+ U# q* ]7 Y% X9 W
/ N! O9 B8 y0 k7 u return found;; r& @5 J6 r. N9 D: l. R
}
- [% z" q3 x- |' {2 v
& O: F! f3 L9 m: ?. W // UI元素的状态也表示成整型形式。因为一个状态可以有多个值,. ^ Y! T! R6 ?3 j4 t/ ^- R8 y
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。/ B, \# v$ X* }
//将这些或数转换成相应的用逗号分割的状态字符串。
3 J# ]: U8 Q w0 c! s) T UINT GetObjectState(IAccessible* pacc, . z4 y/ \; n6 E* J
VARIANT* pvarChild,
8 D; S5 J, r" ^/ Z! f% U: j LPTSTR lpszState, # e. d* k( A% T2 n3 n/ E6 S2 ]' F
UINT cchState)5 a2 P' Y) _, k. i9 A0 T
{
! A, P% K% N4 O( q$ w. N: }: d6 {# F HRESULT hr;
5 ]8 Q/ _ d- J7 U VARIANT varRetVal;
6 x3 H( r6 n: F; y0 A( X6 F
: e' x1 n" T* {! |8 O- u *lpszState = 0;
; z/ s; c7 p8 }1 N, _, N/ |/ ]+ b0 a 9 n7 t# O/ \7 }% \0 c: `+ S
VariantInit(&varRetVal);
' x x6 D( L* p8 `- O7 v
; q- z9 K4 p9 A! Z. V3 v- H, M' |/ \ hr = pacc->get_accState(*pvarChild, &varRetVal);
% ^* K+ p- Y1 N, o' ?; G! X0 n+ D2 ^ / ]% v+ d0 c' i; k \: S
if (!SUCCEEDED(hr))
& j* R* {) r! Q& x; L; l" {3 l( n: D return(0);0 q; E9 ]' [" B, w7 U* N- O8 Z
+ S' n% S2 q4 f2 @- D$ f DWORD dwStateBit;
6 V# ^: n. s- q7 n9 w% _ int cChars = 0;
# V$ }4 W; d4 E if (varRetVal.vt == VT_I4)
! U7 y9 h) Z" i {4 p7 _+ A3 X5 [/ h( g% i
// 根据返回的状态值生成以逗号连接的字符串。: o+ e! i0 V$ p; T- x5 k4 |
for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
* e% l" X; M6 M: I dwStateBit < STATE_SYSTEM_ALERT_HIGH;
/ |7 r8 C- m. \ dwStateBit <<= 1)
6 b: j+ S* d' k! H+ t; D+ J- \ {, _' ]) \% j+ B$ ^6 v) ~; c9 O, N
if (varRetVal.lVal & dwStateBit)
) i2 L# l: i f# E6 _# K# ?* d {
0 ]* w# s. F) i cChars += GetStateText(dwStateBit,
4 ?% b( l' Q: C2 F% J) X lpszState + cChars, 2 T P3 `. e5 @1 G
cchState - cChars);3 E7 z" k( @" E; Z; T
*(lpszState + cChars++) = '','';
2 R& M: x4 r7 H' l/ [8 J$ H) { }
; r+ p/ a' I I7 ~. \# @, [" g }2 {- l6 m* a* o& z
if(cChars > 1)+ F2 j( Z/ y" t2 v$ b* ], S( }
*(lpszState + cChars - 1) = ''\0'';
- R# ^0 A' K4 u+ @6 u% L# h }
" p; P( |/ ?2 ^: J8 X, }, ~7 q else if (varRetVal.vt == VT_BSTR)% q, @' w% t6 h( [ h2 }
{
$ N; r: G* Z6 d' q5 [ WideCharToMultiByte(CP_ACP,
4 w( y; U: _7 H0 y 0,
! g8 O9 Y9 t/ Q4 U) N+ m7 ?! x varRetVal.bstrVal, 3 x+ V8 P& K9 I/ B5 l
-1,
2 V3 S V% R3 { lpszState,& ^& a1 H. B& l8 \! N: d
cchState, % a/ c# C/ o4 g4 V$ h5 h
NULL,
' v$ g" |- G1 q1 _; A NULL);/ m& I* h6 _; [! q: b$ Q* z: d
}4 m: G R! w6 \
! V" \- {% `0 }6 {* A3 E0 C
VariantClear(&varRetVal);1 B, A7 l: q1 u% H! A! T2 |# P
1 y- U b; X0 F8 o1 E
return(lstrlen(lpszState));9 X9 Y. E0 p* E) K# i7 V% @' v
}</FONT></TD></TR></TABLE></P>
% n+ O3 w7 c+ J$ } < >好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)) b5 T: Q% U9 ^6 ~3 a
) x' Q! c; z% [" t! a
四、在 IAccessible 接口上执行动作
9 e* @ E* k7 Q4 p+ f6 B5 o% D 有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。$ S+ [7 W! o5 J) f
最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。2 k$ `' `* q6 V
如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>% Z5 e$ n* J$ ~
< >4 f6 I1 Y, i% n. S
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
' k5 U, j, `; D. Q$ `/ J5 N0 g 2 e7 P9 Y4 q3 m* e3 w
<TR>
5 n B/ a1 [, B0 r; _ <TD><FONT color=#00309c>//在文本输入框输入"regedit"9 Y/ H' m0 p/ J3 [) ?% V
if(1 == FindChild (paccMainWindow, "打开(O):", / q! k9 T5 a3 ~7 O
"可编辑文字", + o0 o4 Q/ o$ H: ^ _' v7 B
"Edit",
+ h9 w. [0 k' ]/ O. Z( F; D1 r% I &paccControl,
! D" `9 l5 K+ Q &varControl))( f% P3 J9 b: [- e o d
{, u- D. b; l/ N& q4 u; k9 R
//在这里修改文本编辑框的值& F7 w! L( N& r8 @- `/ H. t& v& v, G
hr = paccControl->put_accValue(varControl, & o' X; n" Q7 B7 h! H
CComBSTR("regedit"));( e' W9 c4 A, ^8 q
paccControl->Release();9 S; ^! F- c) N/ E; e" I, O! w
VariantClear(&varControl);' L& z( Q V5 o9 z' X0 M! A' ]
}- R0 J; i3 @8 M
9 k( g& N( a. ~/ u0 E" O; N/ Y, D
// 找到确定按钮,并执行默认动作。
* U6 \! F# q. o! V" Y if(1 == FindChild (paccMainWindow, + S$ c$ S* a2 d) r! ?0 U" x
"确定", 2 U6 _6 i* w3 j& |- H; Z( e
"按下按钮", # U) R6 S9 q0 ~+ M+ B) \
"Button",
5 l; j, a8 |7 L. O &paccControl, 6 W j# J% Q9 J0 S4 x/ t" G3 T" \" }9 P
&varControl))5 ?) d% d9 a6 U# R
{! p, o4 d5 U# _- Q) B" @
//这里执行按钮的默认动作,即"按下这个按钮"/ [+ C' J& g6 G a2 ?1 r
hr = paccControl->accDoDefaultAction(varControl);: ^- K! L' j- q4 _: _
paccControl->Release();8 h% ^/ f: m! j% b
VariantClear(&varControl);- ]) v; J, M E4 s4 S) K
}</FONT></TD></TR></TABLE></P>9 h0 h1 l& J7 O% f& c. ]# p
< >现在,你会发现已经成功启动了注册表编辑器!!$ t- X% S3 t( g) L& f$ ~$ p- a
</P>
Q1 s0 Z/ {( E% r+ r < >五、模拟键盘和鼠标输入* i6 y8 s# [* T6 t; k. h: o
让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
( V3 `: Z9 q7 O) ? 使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。/ {- W8 t/ H [5 o+ H
SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
# m; p5 b% v9 q. F1 m6 I' H2 n( ^1 s 下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
9 G: h; h. d% I- H( v' z < >' d: \. A0 a$ D) d( H! r
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
9 V, t% t& V/ z
% h' K( L8 P( c <TR>* t3 H1 Y6 k% O6 T6 u( w" {$ o
<TD><FONT color=#00309c>INPUT input[4]; & a0 [) y7 a4 q0 |
memset(input, 0, sizeof(input));! Z% I" r o6 q% C8 P
& E! z( J5 G4 }4 ^ //设置模拟键盘输入
8 X0 e( \# I4 O input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
1 W1 u0 }: P. j' C* t- e1 ` ?) J; k input[0].ki.wVk = input[2].ki.wVk = VK_MENU;
9 B+ n" s; {' ^0 K( g6 w4 Y& G input[1].ki.wVk = input[3].ki.wVk = VK_F4;4 Y# V- o/ S, v1 o; T' }
* A- q& p$ T4 T' _% T1 l" P6 ?
// 释放按键,这非常重要
, B1 ]; j3 q1 l6 N; a% B, E input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;8 E e- [4 a9 w8 i: h# s
# }8 K5 i( B2 v$ B# R7 @ SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>- e# E7 f' I- D5 ~$ R, R
< >具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
6 Q9 I. t* R$ l& s4 M </P>) x5 [7 l/ K8 n% [( ? f, ?
< >六、监视WinEvents
' }- V; Q, f5 ]; g 监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
0 f6 V i. C' ?8 o- Y/ J" M1 b4 a7 D 监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
1 ]; [9 \/ y# ^7 A/ f9 E 好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>, ~: |6 r' e, e/ N
< >$ |+ w2 o, t+ o" ?5 G- o
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 L" B# x% a8 y; a4 E
* N8 w; S. V5 t, V* a, m <TR>6 R) G: i& c) q* Z1 D# B
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
6 ?$ ?) q7 g, f d3 f. | {, h+ @: V2 j" S- h- w# e3 h
hEventHook = SetWinEventHook(
+ ?9 N1 d8 i% G: l K' R' l, H EVENT_MIN, // eventMin ID% b5 Z, h6 N0 m2 z2 D
EVENT_MAX, // eventMax ID8 m1 A2 i3 z" U1 r6 @
NULL, // always NULL for outprocess hook
$ N' `0 O; C- L" y% y3 f# K WinCreateNotifyProc, // call back function {/ y4 W9 ~2 R
0, // idProcess6 ?2 w' f W H1 N
0, // idThread
) K- O' F& w- g" C! d // always the same for outproc hook
0 D7 G1 P' a- L WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
9 A# |& s+ q; Q2 R* Y' F& {* Z } </FONT></TD></TR></TABLE></P># u; f9 D! N" n9 ^. R" b4 c$ S
/ H: }8 a u+ l7 `; y" h% c- ]. L% a < > 第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。8 g4 b3 o3 S. j/ {
下面是回调函数:</P>, H- H! a" i T0 v
< >
9 _# T: g2 o# P# G# |) v2 F3 p) C+ \$ z <TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
' y. ?. t4 S6 {. {" s. d. ^ # `8 l f j( T0 X
<TR>
: H" n5 e% N, z5 @6 O <TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
) z, M! L' G+ P& r4 n5 C HWINEVENTHOOK hEvent,
9 a) s# l! U6 T3 j7 ~6 @7 R4 Z DWORD event,
/ {" H% p& Y8 |5 Q7 m+ H0 u0 i HWND hwndMsg,
# W7 \( l4 e) W2 W' T5 Q, [ LONG idObject,+ X) W" y% w& ~+ I6 M# G8 M+ w
LONG idChild,
4 }- [: x* v, E3 `2 Q: o' z DWORD idThread,
8 f& c5 Z5 U% z DWORD dwmsEventTime
2 I7 c- ?8 I# ~6 J )
' B! O( E/ X$ y+ } {
1 m4 I p, G% s$ J' d: r 5 v' O7 r( C. f7 Q% _8 k J
if( event != EVENT_OBJECT_CREATE)
4 X0 v5 c" [6 ]! S return;9 e5 {% k$ u- @$ n
( c$ B" X# g( g7 |7 Q$ ~! }
char bufferName[256];! Z/ |2 p- n2 C
IAccessible *pacc=NULL;% ~1 E9 _$ h: I+ T) {
VARIANT varChild;
+ E, H0 s( J6 o VariantInit(&varChild);0 `5 H7 {2 W+ j
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对6 y% w: Y6 s8 f9 D
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, W: b2 x1 t) t2 H! ?
idObject, + B! `* O, X2 U/ w; \& {1 ^; A H0 J! j
idChild, ( f) m* ~' G, U
&pacc, 9 M8 q5 a; ?5 Q1 `( p% b6 @7 N
&varChild);6 O% a3 V3 U. U& F# j2 _& {1 C; m
2 h' {$ D, l& }) e9 t, h3 r if(!SUCCEEDED(hr))
' d6 k" W0 G2 ~: g: n# R {
0 P, ` Z& b& ^* N VariantClear(&varChild);
; G. v7 b/ ?, o# F) f; f# C7 x return;
! Y) M# C- u5 C( X }5 s5 D! ~1 H3 R9 x O
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。9 x, G4 h1 y8 J6 ^
GetObjectName(pacc, &varChild, bufferName, sizeof(bufferName));
" h: l+ p1 p- L$ S if(strstr(bufferName, szMainTitle))
0 { O7 Y5 Z6 v PostThreadMessage(GetCurrentThreadId(),
' ]. }! a' h- T5 V- S: w# d WM_TARGET_WINDOW_FOUND, 4 X& }& }3 O% b( L5 N; W
0, # E- ]9 d5 X; k3 l
0);! z7 F' C4 q" F; l) M# g$ ?) k
! b/ Y J1 {# S) L4 D' i, r P, v return;
$ J3 S/ m, s" q4 u9 I% h, K } </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
+ y0 P' |6 U" {/ o' T; U7 x8 p8 h5 J8 w 8 W# z$ }4 i5 B; c ]9 N7 ^) w& `# L
附录:6 F9 h3 Y5 Y3 x2 S. F) n8 h
) z# P7 k. _ S+ @+ U- Z) ^ 关于IAccessible 接口/子ID对:
v4 r' ^4 ~; |/ K( ^3 G# f 让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
+ w7 ^: _% C, G% p! s 第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!- \0 c" E$ x% m) m2 X7 H4 j! q
这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
& _) h; P3 w; }- H* ^, f7 | 记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
% x `& I% V3 { 呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
" \5 @' X) K# E+ E+ X: B 9 N2 H/ V) v0 \4 \' V9 N1 u3 e' ~
注: H' L9 T7 I: V* ^
我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
8 Y. v* ~! L ]* H 还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。2 F0 @) ]' [! ~6 E1 @
3 M8 }7 a1 g s w1 v 参考资料:
% e, r9 z# G! X$ P- k; M& { <UL>
: p$ |3 v W. C, f' L <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
2 Y& U6 R$ B1 |, T# ?, P <LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
zan