QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5669|回复: 1
打印 上一主题 下一主题

MS Active Accessibility 接口技术编程尝试

[复制链接]
字体大小: 正常 放大
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2004-11-21 15:21 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0># k( w: e/ F4 h8 I& |2 a7 j5 I

  @9 w, W. P* |2 n) \4 g<TR>' {# M7 E* U* O8 |8 \
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>4 r  Y& N* \$ S0 ^7 A
原 作 者:崔传凯# i  C7 u" e2 \  n
原 出 处:vckbase' Y1 Q/ q, |! \5 j
发 布 者:loose_went
' Y% `/ j% L/ G" V# e4 Z发布类型:转载0 Y6 V4 Y0 r* M- v7 }5 D+ K2 w
发布日期:2004-11-12
6 e* X' {8 M1 B- f8 q5 g* q今日浏览:6
7 R% x- X7 U5 g" _  G总 浏 览:234
. A9 K0 J- X& K' Y+ \# I. a</TD>
2 O4 e& W0 p4 X4 b9 R1 @9 {% W  v0 I<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>& D! G6 j6 b/ i* ^7 I
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
! D& Y6 |) d- R4 ~$ }9 ]2 [, y( H; j# P" w
<TR>* S5 R: Z2 M$ D. E# Z' r
<TD vAlign=top align=left bgColor=#ffffff># c+ l0 Z/ C. l- R
6 y& g4 e7 \0 j$ }' ]# N  u) p
<>6 F3 u. b6 q# w6 `& E
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 X% l; ]& Q2 [; r$ f1 v) F3 b8 x
) J6 P1 \; O7 B& {2 M! b' u/ m
<TR>& z7 ^$ k: Y0 ?
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 6 ]. d: [; I, Y2 y6 a1 W; B8 O: k
way accessibility aids work with applications running on Microsoft Windows?. It
' C  }. X' _4 \- R3 Kprovides dynamic-link libraries that are incorporated into the operating system - @3 P$ ]$ \5 m+ e2 M2 k' F7 j
as well as a COM interface and application programming elements that provide
' N& O) Y/ l5 I/ o: c7 Treliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>1 l8 h* K+ ]- i) j
<>一、基础
; b9 Z0 B  g# x  o    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。/ [5 J- n5 p8 ~+ k) x
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
; }1 @; v" p# C7 S" |    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
; p1 F+ ?# `' J<>二、Active Accessibility 原理6 P5 h) H3 k. b
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
! j. O* {1 d) Q$ m4 ^    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。9 a# u3 Y2 I; A! r! i
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
! U" g; P/ I$ A6 R( f% S/ X3 M    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。1 L! G6 x+ }$ w( V# E! S
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
3 p1 H" ]* z3 R4 G/ V! }1 a6 B<>三、如何得到 IAccessible 接口指针( S/ E( p/ }% f3 i
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。9 s+ }- J* q4 Y7 _) j4 ~* T
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。( K* {. q* L. H
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
4 C' _4 X$ V4 ]7 t    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
7 _) z# Y& c6 H, E/ j    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。8 @  }: ]- x8 y' l
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。! t9 o* ?% t0 t1 t2 p
下面结合代码介绍一下它的用法。6 d+ `" ~6 @0 @# I2 x# T
我们来得到下面运行窗口的 IAccessible 接口指针。: E/ g# Q1 Q1 ?4 P

& o# f$ ~* N9 e5 X( O' t<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
% C& x- _5 f2 u3 o7 n* m* }<>图一</P>
7 `( G6 W* n, H6 x! |<>
" I7 r7 |( c8 ~9 u( D! O<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ k. u3 e, f; \9 c4 R) z) [

( K1 r! `" ~! e$ r' D3 X' w<TR>2 m' t8 m2 e; `- `
<TD><FONT color=#00309c>HWND hWndMainWindow;, L1 D. r' d% @
IAccessible *paccMainWindow = NULL;
$ [0 R$ {( L, a: m5 I1 ?: D( qHRESULT hr;
- c7 T* A6 q; G+ c4 T//得到标题为"运行"的窗口的句柄
* Y6 ?8 L8 g, \- ^$ V# V7 cif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
! y' s4 B6 X$ ], W" C. k1 m{2 J3 n# c* p0 f4 h  J  W7 v, O! K  q3 Z. U
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
( n+ V3 P7 q2 n1 y}. a4 l6 @4 ?* b$ T! _
else0 J' p. e1 ~; M) U, a" k
{: O6 y* I/ O3 F/ p
//通过窗口句柄得到窗口的 IAccessible 接口指针。: n. y+ P" Z' i% D0 R' n
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, + B. {2 l& E1 ~5 T$ s6 G$ J* y6 o
                                            OBJID_WINDOW,
* F. F. |& U& j7 z' b                                            IID_IAccessible,
; I) Z# P! s) T0 L                                            (void**)&amp;paccMainWindow)))
/ {, w  Y4 h3 j" l8 b3 F3 V) ?{% _  G& j0 m8 X/ b6 |1 }
//……我们可以通过这个指针paccMainWindow进行操作。0 L" i2 H) X. c
paccMainWindow-&gt;Release();
: `* n0 O! t& N) z" ]        }4 s8 a2 J& N- {' d7 |
}    </FONT></TD></TR></TABLE></P>
/ |" |5 [: t6 z, g6 U* O6 A<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
1 z6 Z2 c* a8 C' H* w# C    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
1 N/ H: ^7 y7 c+ t    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>! x' H. A0 @" |" Y
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
9 R& \2 F9 `( T: p) @, {<>图二</P>
: |+ t  G; ^2 W) h& Q9 S) `4 ^<>我们现在主要关注的信息是:Name、Role、Window className。</P>! e3 L& u2 `- z* p' Z* F
<>& N$ b, e% M3 A" ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
) t4 P' V5 L) v  H- n* a6 b: Q$ d2 s- P+ G; |8 G
<TR>
/ f/ j5 F$ @# o" X8 J" `' x$ V<TD><FONT color=#00309c>Name = "打开(O):"* q* ~  J6 d# F" S
Role = "可编辑文字"7 N) ]8 n* |& \$ q  j% z# K5 w8 @+ V
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
9 [5 `/ @9 k5 K& U. ]<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
# S, x7 T" A+ v    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。3 r0 V2 M5 W2 Z: j/ u% k3 C$ U
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>& y" y- K" T: a* a# S- O% ]% j( d7 j
<>
# i3 }  p: `( ?& n; G<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
: ~: f2 g8 p$ e% h" Z
! |! e9 C; p9 p: ?<TR>  W$ P- ^# \) P% J  ]# A
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口. ^6 T. _+ z- }7 w
VARIANT varControl;    //子ID。( Q: ]* h: ]! B

: b3 t. ?: B8 l; f" ]FindChild( paccMainWindow,
8 T1 `# s. F3 Z( b           "打开(O):",
- u% E) D5 N& c$ O. b5 l! V           "可编辑文字",
9 p7 n2 @# m( q- Z           "Edit", ! x7 R7 A& ^7 A" K) U
           &amp;paccControl, * U: t4 U0 Y2 a: h: K% e8 B# n
           &amp;varControl ) </FONT></TD></TR></TABLE></P>, r6 d* c6 Y6 {$ V$ t' `
<>第一个参数是先前得到的窗口 IAccessible 接口指针。2 T4 Y% j3 u. C1 ?% w
第二、三、四个参数分别是名字、角色、类。
  X7 ?- |4 K( N后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
3 R  a0 Y6 B0 H8 q/ k! a1 z' ^<>
5 B+ F. z! v; _( y4 E<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
, Z+ p. M( r8 J$ D3 Q# W0 v
) u# Z( a4 f6 A. d6 P<TR>. r7 x$ `: q5 u5 K$ X1 ~+ n2 i
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, 1 o# D) H9 l) J( v, e* L8 X
                         LPSTR szName, LPSTR szRole,
. E% Z# z' A' F; c                         LPSTR szClass, 6 M0 N/ V& l, g7 v$ l% C2 T; ?
                         IAccessible** paccChild, # P" d" L4 T; ~: ^
                         VARIANT* pvarChild)$ o9 ~7 z4 H9 Z$ i9 u; l
{8 ]# w) T7 w  m$ z1 ]4 T( u1 G
HRESULT hr;
% P( N0 [4 E1 x0 Nlong numChildren;
. S" \2 ^3 |8 n# v' j" g0 l% L; Tunsigned long numFetched;
4 K; v4 i6 J3 P8 N5 J8 B; y: FVARIANT varChild;, A0 A" D5 [- V0 u# c- i/ E
int index;% q* \6 b4 I; L( I# a  D
IAccessible* pCAcc = NULL;
5 y$ U; h  k8 d9 }9 P4 N7 DIEnumVARIANT* pEnum = NULL;
- a* m: Y' m4 }8 a+ QIDispatch* pDisp = NULL;" r, }5 X( m, b- F& [0 ?: N2 b
BOOL found = false;  Q( R5 @) D8 r+ v0 i
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];# b$ V+ }: f  m

1 m0 Z# K* T* {' i//得到父亲支持的IEnumVARIANT接口
& ]# K$ l9 ?- h# j) q; `" Ahr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);4 O3 b; H4 D- V3 u" T. W! W- @
, B- f: K+ Y4 `. k
if(pEnum)( G  w/ p. B" u
pEnum -&gt; Reset();& c# {/ F# d1 N. A3 G- C

( I' _# K# x$ @6 g//取得父亲拥有的可访问的子的数目7 O1 f3 ^7 Q% O% D2 C8 w
paccParent -&gt; get_accChildCount(&amp;numChildren);
( U( d, D* J# U/ ?+ ~" D! W+ u! W% T" r; ]) w: \
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。. f2 [9 ?4 q& Q& d; G) D
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
! b0 X9 T6 V9 O0 C; j( _{4 I& N( j! `$ {/ o2 e8 ~
pCAcc = NULL;
, Y. y: ], V3 y// 如果支持IEnumVARIANT接口,得到下一个子ID
# B" w" |; g8 G8 C//以及其对应的 IDispatch 接口& G2 ]/ j. B( `/ r, q0 l, H" }
if (pEnum)6 f3 F. u: B1 |. l. v
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
% g+ O# o$ P8 w+ Z& C! g6 Welse2 b- k. F) `- ?0 W+ \% O" _5 g
{
$ m4 O7 \' A8 Z//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
+ f4 G, A) w( m/ A9 R5 G: A; ]8 mvarChild.vt = VT_I4;4 z" n* a) h1 _' T* E4 z- R
varChild.lVal = index;
( B4 q: t0 j0 }# S" Z}
7 h5 o( Z, ?- C  O, `$ X7 C, Z) m) V4 k% U
// 找到此子ID对应的 IDispatch 接口& ?/ l6 K# k  M- }8 y7 D" I+ `
if (varChild.vt == VT_I4)+ T# a) x# v* n  d5 Q  o9 a
{
# [) r) Y  [3 H& _1 d//通过子ID序号得到对应的 IDispatch 接口
3 O/ }/ }5 z/ k7 i& ^1 A( UpDisp = NULL;
/ G5 W6 o8 Z# @0 W8 d* Z0 Q, [4 ]+ Xhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);2 M3 K, q# e/ e
}) ?5 U5 m2 k% Z9 b- A
else5 q1 R% S5 r2 O6 m" X
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口# M3 `6 j" i% }9 x
pDisp = varChild.pdispVal;3 U4 p! E3 \, f- Z: s2 m9 D. Y. ^
& _8 v' E# f1 [
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc/ ?$ a5 S, X6 E0 ]3 m. Y
if (pDisp)
: K! e9 ~! [6 H- ~{
- O/ [- E8 x0 D* o$ W3 Qhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);; u+ N9 v6 U( I5 I
hr = pDisp-&gt;Release();
7 t; C; {& r$ }: a, H}
1 H9 L( Q' b( Q2 \) f
* j# N& w+ _8 G0 M8 C* s/ \// Get information about the child  D' u4 u2 ^! ^1 J+ i0 U2 \( A
if(pCAcc)
# k* e8 ?3 G/ j" ~{
9 q, V, e* `2 {  Z//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
* j( s2 M/ E( ^9 C, i3 }6 pVariantInit(&amp;varChild);6 Q+ n" J3 w( d1 ~
varChild.vt = VT_I4;
3 Y& `" e; X8 ~1 LvarChild.lVal = CHILDID_SELF;
8 T$ x2 i  z! P: L! X0 c
. ^8 v9 J/ F0 C& ~*paccChild = pCAcc;/ N$ U" m$ f' `6 }+ o) c2 y
}( i! h  O( b% V: t2 X
else% {1 K3 K& [% c5 p1 z) o
//如果子不支持IAccessible 接口2 B) u+ G4 p9 v$ M
*paccChild = paccParent;  m, e! K  D' d5 {( w; _  C/ ^

( F9 J/ ^' i- h% B5 t, V//跳过了有不可访问状态的元素
# d3 @" @+ k3 }GetObjectState(*paccChild,
9 L# t* E$ x: I! ]               &amp;varChild,
* A; Z4 O/ j$ f3 U  m; m1 Q, l               szObjState,   G6 P- ^% i( Z2 N2 o
               sizeof(szObjState));0 H1 Z- u% L. b" u" p% g2 x. E0 Y
if(NULL != strstr(szObjState, "unavailable"))
, P1 g% e4 m# |1 `1 M{0 v1 a+ t7 p3 w2 F$ P/ {0 C
if(pCAcc); p3 z' F- t. ?$ g. w5 r
pCAcc-&gt;Release();
- l1 t% t% g% [$ ]9 vcontinue;( E1 A9 ]- x$ \- V' T' G
}4 f' S3 w2 {, ~
//通过get_accName得到Name' [, i" ?( |4 U& F# z; @; f
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));; i( C% Q( ~4 h/ w
//通过get_accRole得到Role
# K- Q$ [: Q  z  l, HGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
. x1 [  b1 [( ]; t, K# W: A5 x//通过WindowFromAccessibleObject和GetClassName得到Class  o1 b0 T+ ^, C
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));& m5 l, F  ?" ?: ^
//以上实现代码比较简单,大家自己看代码吧。
. ]& i% S7 V2 t2 x: g' K2 X5 K) z; T9 [) {' j3 n7 C. D
//如果这些参数与输入相符或输入为NULL
9 U- s; {! Z$ x! Lif ((!szName ||
# T  Q0 S( J; M! I1 U) J9 i     !strcmp(szName, szObjName)) &amp;&amp;
; t& }" P# P- i6 m) N     (!szRole ||
/ b1 s$ e- S) o6 Y+ P      !strcmp(szRole, szObjRole)) &amp;&amp;
  G+ y- v3 W4 D# |1 n     (!szClass ||
; J; K. _/ G! L* E) g      !strcmp(szClass, szObjClass)))4 w8 q, I' O& L1 T) g
{) h) [0 k" n) [) r& Y
found = true;5 e) c0 ~& ?7 B+ ~& ^5 F2 i( u/ h4 ?
*pvarChild = varChild;& w7 s. r% a2 w2 r5 c- e; @
break;; P  T/ H4 R+ q! S3 A
}! \' s# K& h% @, e* P3 s1 S
if(!found &amp;&amp; pCAcc)
7 z& A, Q& d) q; @2 X' ^2 h5 M{8 X* f  o0 f; P- x. K  ]9 M
// 以这次得到的子接口为父递归调用( V$ n  \, N: t* ]5 b  j! d( p; @' r
found = FindChild(pCAcc, # n% z+ Q; U' a* x$ x$ G
                  szName,
5 @3 Y) A& |, ^                  szRole, # ^0 r! O+ Q+ b* e1 l
                  szClass,
- {/ [+ e1 j, F9 P. E                  paccChild,
* G; X9 b$ K" n8 c4 h# {; J                  pvarChild);
6 r) ^: A" W2 k% g- y5 ^$ C# Tif(*paccChild != pCAcc)
+ H! w! e, ?) Q6 ?: ~5 xpCAcc-&gt;Release();0 o& T7 i+ C& `. K2 Z. n& }6 a
}
0 A/ K7 G4 Z# b+ m5 E}//End for
! J6 v# D& a  [7 B, z( H/ B+ W
) {* y) f4 l8 m* D' }// Clean up2 r% X$ _: ^( ]
if(pEnum)
; _* \, M+ A+ @$ E1 qpEnum -&gt; Release();
% z- z+ i- j: f. |' g% V5 w- U9 Q. ?, H2 L! k
return found;
- n" ?( `- o: K  q) p2 G4 s}9 Z9 y! o) |& z' @5 |* t2 g) x
) b( N- a$ [- L) w4 R
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值," h) Q8 z/ u, ~$ r- x3 j6 f1 q
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。; b' @4 c% K0 K9 p: Z0 A
//将这些或数转换成相应的用逗号分割的状态字符串。
4 Z+ K& A' J0 R6 f7 e* SUINT GetObjectState(IAccessible* pacc, ( x* D: `* H0 b/ s
                    VARIANT* pvarChild,
4 ?, x& J" d" S. c5 n                    LPTSTR lpszState,
4 t3 \& @! \1 |6 w6 r                    UINT cchState)" f! h9 s- X4 @4 W( y
{, w( y% s( x2 S% K- _5 H9 R
    HRESULT hr;
/ z9 I3 a2 k( O    VARIANT varRetVal;; q$ ?4 T5 ]4 {! L' w* E

) b' V( D. {: G  t6 p" y    *lpszState = 0;
1 d( y6 Z* u% x9 B$ ^+ P& N* V& E. d) Z" J: d; b
    VariantInit(&amp;varRetVal);+ x: n1 F3 }; ^) `( ?; u# R" v: x
( C5 `$ F) {# x' u  c3 S% O! V/ k
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
% ~" z; l8 v6 e- i) a+ L6 }$ b4 P$ M/ t7 M8 @  a
if (!SUCCEEDED(hr))3 J6 R3 l  c& b. N9 |  {6 H2 @
        return(0);
5 @1 W3 g+ `0 `3 Y# f5 v$ o
3 [/ j# _, N6 Z/ zDWORD dwStateBit;/ Z, I+ [. y9 t( i' v$ _! n
int cChars = 0;. D1 ^- Y5 k! B+ w# L. z* b
    if (varRetVal.vt == VT_I4)" e, w+ q& J2 ]3 |! q% K# K4 ~: s8 G
{
1 L' O; n5 g9 t' J/ @// 根据返回的状态值生成以逗号连接的字符串。
: _9 v1 I8 m. ?5 r: \        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; ; b. t5 O  A% I5 O! d; c8 d# s! r
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
* X3 F7 F+ g& H; I. j& Z7 L               dwStateBit &lt;&lt;= 1)
4 }* j2 {# L0 K6 Z$ O7 k* r2 @        {
5 \$ u2 _  R  T- U            if (varRetVal.lVal &amp; dwStateBit)2 s+ M1 J# a# Q
            {' A& S9 c) }$ [( u/ `; ], ]; v" I/ M
                cChars += GetStateText(dwStateBit,
( U2 Q9 w) A) R8 G                                       lpszState + cChars, 9 J6 Q8 `8 q) h. y/ ?' L' V1 l
                                       cchState - cChars);
# x  E8 X& h- \6 E, h*(lpszState + cChars++) = '','';& X# X  ]% ?5 ?  w9 X- A
            }
( H" l; ]7 E) Y4 g% ]% A3 A8 A7 f        }# R- n2 G1 n* h6 V: V2 A8 z4 _! b; F
if(cChars &gt; 1)
# {* P: l' p! Q8 h*(lpszState + cChars - 1) = ''\0'';* M+ i& T' R' q( P
    }& T3 q: r3 R% T# `
    else if (varRetVal.vt == VT_BSTR)0 d2 a# h3 j* y6 G6 i
    {4 ^8 u2 w6 F, U0 E" f
        WideCharToMultiByte(CP_ACP,
3 Y1 r1 z; w1 f5 l- U                            0,
% P- O# J# ?4 \+ w                            varRetVal.bstrVal, 1 H- v% T" B/ X8 z. K2 [: @
                            -1, 9 u; Y( Q% T! g! z2 w* q7 N" ?
                            lpszState,
/ j( B; f3 N) d& v7 a4 N1 q                            cchState, 7 T  _: b4 q2 k' C
                            NULL,
( o: a4 W, U4 w0 G1 @3 c$ h                            NULL);
2 B7 v! L+ E% L9 y! e4 c% C    }
" \) k, J1 {: f
% r2 w+ M. j) U& I+ N3 s+ i: n    VariantClear(&amp;varRetVal);+ n( H. ^5 O8 s% F

: d5 L% Q4 k' i    return(lstrlen(lpszState));2 V' N6 l  `5 p+ G- }- s! U
}</FONT></TD></TR></TABLE></P>
& F8 v; _- P2 X2 `8 D0 M<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)7 N( i7 Y* A: T: r& N  A  [
5 f+ R1 J! D2 I: b) J+ w
四、在 IAccessible 接口上执行动作
6 ?& C; ]/ f5 G7 Z9 w- K) ]    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
( s& t8 M4 K& d' F( A8 H- }3 O    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
& S9 E1 J' j' Q" {8 x) Q4 c    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
& T! q' T- O9 F% N6 K<>
. j, z: R4 J* o$ U/ U<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 C: g9 \& m6 t2 D5 ]! A

; f6 M- e9 }; n5 ~) V' O<TR>3 V3 N; H% N! _/ L; l6 B" V4 l
<TD><FONT color=#00309c>//在文本输入框输入"regedit"( e$ q3 F" ?; W: o: h
if(1 == FindChild (paccMainWindow, "打开(O):",
( w) V0 Q* @( Q/ d5 J                   "可编辑文字",
  k) x9 @/ z! O5 N$ o8 @9 y                   "Edit", , Z  ~# f/ H: W4 u- B% f
                   &amp;paccControl,
# q- Y, i' B+ \3 R  a+ d- }                   &amp;varControl))
5 a+ K, k( f! d5 Q  d& w{
0 c0 b; ~5 ~  X0 z9 q# m; i//在这里修改文本编辑框的值9 e5 a5 p5 a8 r( H7 A
hr = paccControl-&gt;put_accValue(varControl, 0 V- u2 l& d+ ^1 L! L
                                  CComBSTR("regedit"));
6 q) q. }! X# R" p6 z' d, n6 Q) \paccControl-&gt;Release();
! [3 f' R' Z9 x1 ^# N" T. ZVariantClear(&amp;varControl);
/ |' S8 O4 s, _# I! }}
4 {" R( o' K: D6 e" N8 q/ g6 |8 d/ [: `3 P
// 找到确定按钮,并执行默认动作。
* X7 t# q! `0 [& fif(1 == FindChild (paccMainWindow,
+ c, o5 D" K; o3 O3 P! f                   "确定", ' Z, Y) I* b7 r4 q$ t
                   "按下按钮",
2 W' i/ F: J' K3 |                   "Button", % r/ d; f' ^- w7 S5 q$ @, t
                   &amp;paccControl, 0 n! j. G$ L/ i( e" c  _
                   &amp;varControl)). F8 v: s$ U5 f0 c. n+ w
{" p- z3 c* n8 [7 ~/ Q! ?
//这里执行按钮的默认动作,即"按下这个按钮"* f9 }  {& [. ]/ f0 y6 p
hr = paccControl-&gt;accDoDefaultAction(varControl);
! _5 h" n9 |. x6 Y6 ?' rpaccControl-&gt;Release();7 {% W8 _2 z7 x
VariantClear(&amp;varControl);: m; l6 z9 b9 Z- l
}</FONT></TD></TR></TABLE></P>
3 _: V2 B) Z5 a4 B6 N<>现在,你会发现已经成功启动了注册表编辑器!!
& x6 h( U: G8 d1 x. h9 P9 V+ F</P>
* B; V/ O) f+ @# C0 o<>五、模拟键盘和鼠标输入
: v5 _4 P5 P2 J' R; U  M: D% H& T    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
) V! e" Q3 P* ^# B& R( [    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
. V8 ^& {. ]+ j* W. k    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
- S# d  O. c8 @9 Y( j# r; }; C8 V4 a& p下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
$ ]9 d  U- {! K* s<>' N7 N/ C; F6 V+ ]$ O9 ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
% n2 l4 u0 E: n+ O1 M+ M) h2 N: n! b, h- ?" C
<TR>+ M- }3 i( \2 E1 _( `
<TD><FONT color=#00309c>INPUT input[4]; . r& @* p6 T1 b3 i$ e. F
memset(input, 0, sizeof(input));3 W1 E, k9 p5 d9 M
. z' V& i, l0 Z- x5 |
//设置模拟键盘输入
: V* M" _' m5 |- g' C$ J' `input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
* k  S; d7 l) M* d$ ]input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;0 J1 \1 x) i( u) s
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;; k9 w9 U; c3 [% \2 f: g

7 M) x6 |, o5 p$ y4 ^* s% ~// 释放按键,这非常重要
/ e% e( A8 B' u* l7 R* rinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;. c$ K8 `, V  g1 l. `

. I: x$ d% N& j' f5 O, z' [. XSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
2 O/ M. s( ?" Q8 x<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)9 F7 f- o& k& s3 @$ ?7 X2 U
</P>; M  v+ ?. l+ S; s
<>六、监视WinEvents
" {+ Z; A/ e, _- Y, \' v    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。2 J7 A" z$ B/ ~* f' t+ S: M7 U
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。% |0 [: G8 \' `& j7 k- q
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
6 [3 w/ n4 E+ t. M- q" d; }<>
# D* @! R2 _  A. \, z# q9 I, b<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ }* e; i$ `& G7 g  v  ]
& f- J; {2 H- [" ^
<TR>
7 H/ ^. |: e& o4 L<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
1 M2 e! D$ x4 U- f5 J{
. x( l$ [$ d1 ?& F0 yhEventHook = SetWinEventHook(7 e+ ^# g' v; b
EVENT_MIN, // eventMin ID
, U- O' g3 A+ ^EVENT_MAX, // eventMax ID: X6 b6 S7 n4 R- ^. ^
NULL, // always NULL for outprocess hook0 |3 r0 H* |/ D5 W
WinCreateNotifyProc, // call back function
# X& \% @  K" R0 F2 b6 [6 z0, // idProcess% l. L% t  H/ X1 w0 M
0, // idThread
: X3 m$ D2 s* G; q# Z3 ~         // always the same for outproc hook
1 i8 n- z2 K/ ?$ r+ M0 HWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);( \; F" `2 T7 V
}  </FONT></TD></TR></TABLE></P>
3 l1 y0 E, A0 w( X" W  j0 y5 c% [/ E, R2 B) U/ V) G
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
( p$ t% f- w; O- j下面是回调函数:</P>5 j+ x& H4 a/ R3 G* u
<>4 t: e* d# q9 l  S1 ^0 m5 `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
- n* l# z7 K& J9 v% A
1 P0 t% I" L! A<TR>
$ p% e  h# o" Q<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(7 K+ P* U) E8 H0 i
HWINEVENTHOOK  hEvent,
0 x" a8 w) x& A5 h9 ~' EDWORD   event,
: T7 S. h( t* W, z, U9 [HWND    hwndMsg,
3 Q3 v9 I) b% n0 {" u4 S( X  _" E7 ELONG    idObject,6 T0 D$ h& d  t2 I% u0 ~
LONG    idChild,& q( u; K# M. N
DWORD   idThread,
$ B) \% C1 A4 A; iDWORD   dwmsEventTime
) v, D5 h& G$ d  ^: `  V)
* D$ m+ f" V' p1 K  Z{
# }: X3 Q" `( z& f
' a8 Y$ ]- V8 C9 `3 `if( event != EVENT_OBJECT_CREATE)
3 N, _1 A; Y2 ^, Greturn;( u- \* x7 m4 |6 ^. ]& I
  b1 z7 q; g" `3 H) U
char bufferName[256];
/ ^' C% w5 g& _) ~4 D5 @IAccessible *pacc=NULL;
, M0 `% r7 z1 e4 b& BVARIANT varChild;
. j3 |6 m8 i# z5 ]$ |) j    VariantInit(&amp;varChild);
% Q1 A8 D0 f; g1 v- j2 b3 U/ J2 [//得到触发事件的 UI 元素的 IAccessible 接口/子ID对1 w" _2 l2 ^( x$ J+ h3 H, l2 j
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, ) }' l- d% X* x+ ^, K
                                      idObject,
7 o* k/ q" C4 h  ?1 `                                      idChild,
' k  r1 g  r( H- p: g6 _' p                                      &amp;pacc, ' k+ S- u' C. p! H$ R1 [- W
                                      &amp;varChild);
3 D4 I- \  c2 ~6 u3 t: P/ V
" z- Z* h+ P0 v* E) Vif(!SUCCEEDED(hr))
6 z  j; ~- V: w2 t{! k; X+ g' C9 |; A9 K* g0 a
VariantClear(&amp;varChild);8 z7 D3 G* O2 v5 Z. V  M
return;0 b9 z) V+ v9 h" \
}* R' }0 d, L! A5 q. p+ U: K$ I  r
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
& r8 Y8 a# S6 b( ^0 R) XGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
. b$ J8 R, c! v! v" I$ |; Bif(strstr(bufferName, szMainTitle))
8 H5 m7 d. A% \3 ^PostThreadMessage(GetCurrentThreadId(), * z4 g5 p# C. g8 G& M
                  WM_TARGET_WINDOW_FOUND, 8 L4 q! R0 `- P% n5 H4 ?
                  0,
8 O& D" c8 n6 I                  0);" z* y) `4 G1 T, G% g

4 P! N+ t) {" y& `( |return;) I% Z/ `# u$ `. g
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
0 v$ \7 p, v4 \' ]' M' l1 s: X0 A) l2 W; ?) L
附录:+ [* I8 i! ~! [, b9 \! {) F! n
( _, Y' }* F7 P$ A; E5 D
关于IAccessible 接口/子ID对:4 X  `; ?, X& z& L5 _$ F
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
9 _* g9 N. O$ F, O4 N    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
) W7 o: B; ^/ u7 A( x- [& Y& `- p    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。! p' |" G4 ~+ H* x4 w
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。# j5 B$ `" P5 {
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
5 B, V2 E' }/ }" r/ r8 E+ }* H- I+ B. R! J* w# T/ a3 l6 x0 o) x
注:1 d9 g. h8 |& ^* d% q
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
) ~4 y- @4 Q/ j) w! U8 I/ _/ ^& h    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
* c2 C( ~9 t# V$ o
, `, x$ X, w$ K& \! A) H% j参考资料: 4 f" [/ v8 h  p7 J4 S  ^
<UL>3 [! l6 M& l& R/ X0 M
<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 ' Y$ U- E* H5 d8 k) k  T: e+ X2 m
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
xShandow        

43

主题

1

听众

385

积分

升级  28.33%

该用户从未签到

国际赛参赛者

新人进步奖

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-4-12 07:35 , Processed in 0.380307 second(s), 57 queries .

回顶部