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