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