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