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