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