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