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