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