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