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