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