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