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