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