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