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