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