QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5414|回复: 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>
4 j8 x( I& R* R; E. v( r& l. G/ a  r; R
<TR>. D2 U" I' a& T
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
% t% j( l6 P3 J$ q' u原 作 者:崔传凯; F( j: f  \  I, r! d2 I
原 出 处:vckbase
" w, k+ F. F! f8 s! |; u) E! {( F发 布 者:loose_went
2 m8 s* E0 ^1 W1 s* e发布类型:转载; w9 a% r. f( ~1 s
发布日期:2004-11-12- W$ q- ~+ f- B6 d. }1 t3 g
今日浏览:6
* b6 h3 S( d, M% K) s) ]  ^5 d总 浏 览:234: X3 a) T) q: `3 ^( {
</TD>7 q% M( [; Y8 b  |! J
<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>5 H/ \# i  J" `1 H& P  |
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>* U4 a$ y& e. F+ q* n

0 ~8 w8 Q; d, L7 h% c" O6 m<TR>- {/ g! I- Z( t
<TD vAlign=top align=left bgColor=#ffffff>  `; q8 V; Z2 {4 [

% T+ A# b6 F9 P( l' n+ K9 G  s<>, q( a* O/ T! C8 H( W- W
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& K  G% [5 r8 \/ O; s

( P2 L* S5 f5 N8 |  [7 w<TR># ~4 _! l6 h3 N/ q+ |
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
8 q2 N: Q& L8 h, E& zway accessibility aids work with applications running on Microsoft Windows?. It ( h7 }; I; \) P* P# l' m0 X# N
provides dynamic-link libraries that are incorporated into the operating system   ~; ~- ]+ P0 I+ E% w
as well as a COM interface and application programming elements that provide
: i  j, S" c9 Q$ Oreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
9 N1 [& f- Z: ]# D% I<>一、基础
3 O0 w  S7 i6 L1 g# Q  d: i    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。# Y6 F$ O) e  ]% s" a+ ]4 h
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。. r; q3 n. q1 e' ?# T0 P+ q
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>/ F! d+ ?. z2 x# G" W  ~
<>二、Active Accessibility 原理0 i0 w: H; x6 n  ]+ V: e6 M- h
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。+ U. U  y) j8 ]! p3 z
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
8 r" @2 [8 p5 l+ {; p4 B; n    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。. K8 b2 ?1 L/ @) }
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。* x8 i- Q  F0 j) Y' I+ S  t/ o
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
' @6 ]( i2 Z' v9 R, h3 b, h4 d<>三、如何得到 IAccessible 接口指针
6 ^8 Q8 J$ b8 J7 [1 V  H. n2 r    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。$ C4 l6 ^% x& b: r
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。$ `/ ]" S  @; w$ r7 T& V! z
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。9 |6 s- A9 Z. G- K1 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 开始。
' J$ O2 M- n. v. H' |7 x6 u    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。' E& ], L; I7 @! s; P
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。4 i, W3 Z7 l! F
下面结合代码介绍一下它的用法。' I+ m1 d+ q1 ^% m
我们来得到下面运行窗口的 IAccessible 接口指针。  L# E, i4 b6 L4 i% I! @

- Y- |9 H2 j6 O! a4 E$ B" \<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>+ K# s) t' _, W+ L2 O0 D
<>图一</P>+ p  `2 J6 g  E, I
<>+ |+ ~: |8 F3 Y$ _9 ?% t- u
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
+ X! L: @+ n4 q9 ~6 K& n' `' A9 Y0 k2 F
<TR>
0 R1 z# b( m) ]4 O* u<TD><FONT color=#00309c>HWND hWndMainWindow;7 Y3 y2 e# \( r6 H- N
IAccessible *paccMainWindow = NULL;
8 Q+ L1 U  e( Z" d" QHRESULT hr;
. R0 v$ @  J# ?$ o$ \% @9 u8 ~//得到标题为"运行"的窗口的句柄
. d+ Q8 Z; a8 K% w5 Lif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
+ |9 g$ {8 b) _{
4 J" Y! W5 D# E; R0 L1 `7 Z2 b2 JMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
+ E% u# _+ t6 h- p$ `& A! q- n! w* a1 a4 ~}% p# O. s$ V' ]& }0 C- t# G( \/ S; R
else
! e: l" l; Y. O1 Q7 I8 X- A{! d1 T, y) K7 F' ]
//通过窗口句柄得到窗口的 IAccessible 接口指针。& D6 k) p- `7 b  z9 I# M2 b
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
3 j6 J; D# [8 l" |" T5 p8 _                                            OBJID_WINDOW, 0 ]4 C4 ~5 D" v7 U/ ]# C# z/ Y/ C6 A
                                            IID_IAccessible,5 Y+ N9 C6 A" s- r, m
                                            (void**)&amp;paccMainWindow)))
4 i" \* t" C- G1 L4 H! i{
8 j3 ^6 p" S$ I//……我们可以通过这个指针paccMainWindow进行操作。
5 @. l7 V$ m  e3 E! v* R: Y2 opaccMainWindow-&gt;Release();3 q5 K" A- U$ a: N
        }
' o' x. J0 |& L; B}    </FONT></TD></TR></TABLE></P>
! ]) K$ z' z+ b* [( g* S% f<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!- i+ E/ d& v/ |3 U) {; X
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
$ s, r* L5 t% N4 Q* O4 V1 d& A    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
1 ~  s  f4 p$ x<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>, _, @! c& j2 f7 C
<>图二</P>/ n* h: z4 T4 T
<>我们现在主要关注的信息是:Name、Role、Window className。</P>; P$ W" Q3 I6 E+ `' Y
<>0 ?: \- @( Q1 T+ ?1 H
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
6 F0 O5 f/ \" u3 u1 d- R: s; h. Z& J2 g6 C
<TR>
) S9 r& D! r" U: k: t# `: m& T<TD><FONT color=#00309c>Name = "打开(O):"
1 ?' G' L* b; s( R/ U! oRole = "可编辑文字"
" b% Q; N% d8 e& B$ wWindow className = "Edit"  </FONT></TD></TR></TABLE></P>( T  M( `5 _" F4 t) {
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。* [; J* M2 M8 L6 q( J& B! u
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。' b5 ]9 X- v1 s6 e2 M! [' K
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>+ B# o+ J9 c/ {6 ?; [- Q
<>9 F8 {' @3 }7 ]9 R, }9 L
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>! E* Y; I" _6 N' _8 H2 Q

1 n" F6 N  M; [5 G# g5 l<TR>3 r  N4 |# [; k) t' d1 p7 s9 Q
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口- M! |0 u- X2 U: H
VARIANT varControl;    //子ID。
  v" i% z% d; L' A( G3 O
: v2 [* y* n* E6 n% `FindChild( paccMainWindow,
! Y- o" p! z4 l# h1 v) L. }1 |           "打开(O):",
$ T4 ~" z9 G! j7 i: i6 T           "可编辑文字",
9 i3 u6 l, b' F& s1 a% P           "Edit", * v+ c& e6 u6 M& r2 O" g' B( F' ]" @, m
           &amp;paccControl, * e$ V) |7 v0 F6 `; t' v& @+ v8 T' s
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
) S, `2 _  m0 x<>第一个参数是先前得到的窗口 IAccessible 接口指针。
3 O% m2 ^- d( e  c% b2 ~  c9 c第二、三、四个参数分别是名字、角色、类。
# N2 J% [, M7 i2 b  v3 \/ H* M后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
' ^! x+ X% u/ J) J7 {' c, b, B6 S<>
+ p/ r) L5 g' W+ p- r4 U" r<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
$ W# P. E4 Z  @0 v6 R9 \7 E
+ A/ a/ d# F* Z! o<TR>
; F3 D  Q- M8 W+ b<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, & e1 g# k' q8 t0 d0 S5 o
                         LPSTR szName, LPSTR szRole, ( V2 D0 g3 ^9 f4 {
                         LPSTR szClass,
0 ~) u) ~8 q9 u& k8 ^                         IAccessible** paccChild,
3 d) g" C& m& V% e$ t& o, x                         VARIANT* pvarChild)
  C- F$ b  O1 ^* ]. v. p{
" p$ i" B& Z, _$ kHRESULT hr;3 {0 j% Y5 y$ ^
long numChildren;
! T5 c. ?% f- d( @" ~) Gunsigned long numFetched;
& V# D! D: p7 d; {, Z6 `6 ZVARIANT varChild;8 K- F( Q  v  X2 |  T
int index;
" Z6 u% f$ e( G0 CIAccessible* pCAcc = NULL;' v9 p0 H: B# |4 N- e
IEnumVARIANT* pEnum = NULL;
! Z( w  ^* H, Z2 PIDispatch* pDisp = NULL;
$ Y5 {& K% n6 ABOOL found = false;! x& |- S/ ^7 k  y/ l7 f8 Z
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
8 L7 ?$ t; k/ _, ?
, M( Z$ ?0 V( X. H" H2 d& M* x//得到父亲支持的IEnumVARIANT接口
; X6 S# |. J' Q+ ehr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);3 j$ q4 ~) ~9 g$ z9 s5 C) F

/ z- ?0 x# M0 W' b: G  I8 s/ u2 B; Jif(pEnum)3 M" \2 B# E5 c7 l7 g
pEnum -&gt; Reset();# D2 E% k( W4 I/ M9 D2 d  V7 E* |
. x0 W# A$ p: a; Q
//取得父亲拥有的可访问的子的数目
3 _- Z! t2 \! Q4 u$ LpaccParent -&gt; get_accChildCount(&amp;numChildren);5 s! `5 E- ?0 |2 ~, M
- R. m$ W% @  E( q" _$ \
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。' a+ s5 G( U0 {2 t
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)' D" v9 h- Z0 D8 ?' S2 R5 H' Z
{) {. {' B4 C6 J+ M; x1 w3 `
pCAcc = NULL; $ e. {- y6 e8 ~# ]1 ~$ ~
// 如果支持IEnumVARIANT接口,得到下一个子ID$ E, M- p8 p2 j) l" N
//以及其对应的 IDispatch 接口6 `% p- f1 S# L# Z( d- E, f
if (pEnum)2 X% X& w/ t' R% i0 V
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); + j# `3 v8 Y- I3 ?1 v1 [; A! x
else4 O0 \4 t: x' r5 U+ V+ }  y
{( v! H6 ^1 v+ a* J& k' B
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
; t. F% |6 n9 avarChild.vt = VT_I4;
( N/ V# j) P2 }* D5 H9 P: l% RvarChild.lVal = index;6 W' B9 i5 g8 A4 f3 m6 ]* [# `9 v
}) x# d/ S% q0 Q' a" C. ~7 \7 g- ~
. A( N/ d' t: N* l% V# K% [. K. j
// 找到此子ID对应的 IDispatch 接口
3 L* ?8 }3 k! Y4 q; y" ~if (varChild.vt == VT_I4)
) U' Q+ o1 U& }{
8 v: R0 j6 A9 o. X5 @! j6 n4 n//通过子ID序号得到对应的 IDispatch 接口9 C3 m! B3 j8 r# O2 E; I
pDisp = NULL;: |0 ?$ b& I+ ]. {
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
- D  A. V) E. V}+ ]* r" g- }, f4 e2 z, z5 i
else
7 R- b) }. \/ I; i4 M//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口: a% U! j% [" u4 _4 i
pDisp = varChild.pdispVal;$ i- G* n& [, w  f0 X7 w) \' G
2 {5 p1 @+ M7 k' {2 l5 o
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc+ V7 F" q# b. _1 W
if (pDisp)% }, i8 d0 N; H) ?
{5 z7 R; W* v/ A
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
7 v% \* i( W+ Y; L1 ahr = pDisp-&gt;Release();
/ G$ e0 ^( x, R9 a: H- b}
8 [, H( f6 m" S9 B& ?' \' D! c: R2 g
6 ?. ~$ ~: m, Y6 u+ c, J2 s// Get information about the child
  q( g% W1 _6 G% Y: R/ J  t/ lif(pCAcc)
! Z0 _7 q# L" _2 `: H{
* D2 U( ~5 J' d4 T" {2 c* [' `//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
6 F/ V, s) }/ c! V: e& @VariantInit(&amp;varChild);- a9 _+ U7 Q4 x( d' v* [0 Z9 _  N& O
varChild.vt = VT_I4;  y: ?8 G2 f' q% t) ~
varChild.lVal = CHILDID_SELF;; N  P7 N$ h+ H) k! K; _
1 ^0 U- G6 e, A" h4 w
*paccChild = pCAcc;
0 z; E+ R- J. K, @  }5 t}
0 \" V/ s8 P& G& }9 Felse
$ @  R7 {, R8 c' J1 {+ G9 w# c1 ]& ~//如果子不支持IAccessible 接口% G+ J" s$ j% F2 T
*paccChild = paccParent;
& o" K# F$ Z8 r. x* [7 W! n. A& _( Y) P& a& U: a, i
//跳过了有不可访问状态的元素
" r" E3 y9 s5 |/ o4 dGetObjectState(*paccChild,
+ g% R- Q* e  X* N) b+ M! U5 d               &amp;varChild, , L  g# V8 ^0 w2 }9 ?* n/ K& l& H' o
               szObjState, ; f- l* L) |1 V6 ^& i
               sizeof(szObjState));
1 b6 E  K) N( {! l, Z/ ?* N. Bif(NULL != strstr(szObjState, "unavailable"))
( O% \& C" B$ s5 q$ \{6 v  f5 L7 x3 ]
if(pCAcc)9 |0 M$ i) ?, m5 E: V$ K% P
pCAcc-&gt;Release();" Y, M& s5 D1 f+ U/ i# `7 ~8 l) F
continue;' Y% s* G4 u0 @; c! y
}: Z* @0 ^! r8 B$ |  S) j4 }' q# X
//通过get_accName得到Name
* O" _& q( z8 y$ Y) I1 u$ VGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
* k: u- o& M' @- o7 Q* ^4 [//通过get_accRole得到Role* y7 Q9 u# N2 f
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
+ ^6 R0 s& s5 \! s$ K//通过WindowFromAccessibleObject和GetClassName得到Class! y# j* v- O' ~, |" [+ C8 g
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
1 f1 u/ P# s) z* v: ]% M4 W//以上实现代码比较简单,大家自己看代码吧。/ ^% U/ L2 ^2 P

0 x4 C* J% J0 O3 ~; @//如果这些参数与输入相符或输入为NULL
1 }, J, `) k! w$ Y0 k, Zif ((!szName || 8 z; z( w+ m0 m5 I
     !strcmp(szName, szObjName)) &amp;&amp; , T4 K+ w4 L; K- K6 Q7 \
     (!szRole ||
# }( `) D+ A( H% s; u1 y! Z. z      !strcmp(szRole, szObjRole)) &amp;&amp; ) T, t3 Q7 p4 \2 N" @' ^
     (!szClass || 0 s4 E' o( m5 v2 X
      !strcmp(szClass, szObjClass)))- ~; r5 Z1 o9 N8 [4 y& O
{
, V! @! c7 K/ L3 n) A. N+ Lfound = true;' @- J# ]2 N4 m) U; q6 N
*pvarChild = varChild;7 L. E  ^4 L) n! q1 P2 X; u$ Z
break;
& i* n  t9 N2 c( d- `: F}
6 ~* m$ W/ N3 J8 o4 w% yif(!found &amp;&amp; pCAcc)- z8 Q9 S/ p1 o6 l+ S6 ]
{
$ F$ D: e6 C% T( R5 ~// 以这次得到的子接口为父递归调用
4 B% u. M' L6 A8 Cfound = FindChild(pCAcc, 9 X  j! A! `: _  a! C5 i
                  szName,
1 ^3 c# J. v; Y) m4 ^) g                  szRole,   x$ [1 Y; V. H, f# I3 J5 i
                  szClass, : [* M- M* |9 I# h9 P" B& T0 S6 ?
                  paccChild, ! V- ~5 A+ v9 X/ ?( I8 X
                  pvarChild);
; |4 l9 g  `$ J+ N* }if(*paccChild != pCAcc)6 `* d/ a9 @3 Z: t
pCAcc-&gt;Release();
6 J& b9 x! S  i1 _3 j8 ]}6 Y" G  R! |; Z* d
}//End for
3 e% g0 m; Y. S- I$ O' Z# K, [- U- r/ u0 A2 j; D
// Clean up
, g% C) i9 P/ D+ oif(pEnum)1 p6 S6 M: l+ D0 G! k" Q
pEnum -&gt; Release();( K' W; I1 p" N! Q
8 U6 Z1 o$ i0 O0 C0 J
return found;* Y. _1 V: a# D. f" A- a
}( U( z$ T  S# ~4 y$ Z

' ?& f; F( e. \& D( w; S// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,& }, d3 U( B' H0 n* j
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
5 G' l" `8 Q( c+ m//将这些或数转换成相应的用逗号分割的状态字符串。
4 F" c7 ]; \) c9 V1 _3 NUINT GetObjectState(IAccessible* pacc, ( A$ V( P5 F+ Z& e1 [
                    VARIANT* pvarChild,
6 P8 l' a* u( q5 R6 Z                    LPTSTR lpszState, 5 j' `, {% T- _/ K, s" ]6 w& M
                    UINT cchState)
$ C8 S# G- \3 u{4 C5 m- |" U  [/ c' ~& v4 |) o  P
    HRESULT hr;: [8 w# u+ P. `1 B" ~" F0 v
    VARIANT varRetVal;
" q8 y: K, u5 W7 T0 A# y$ U0 z( n) g$ x
    *lpszState = 0;
1 F8 Q; A7 `) r, a$ S' i3 D$ h0 B4 L& d
    VariantInit(&amp;varRetVal);
( P. n5 O' z* K# n7 b" ^- {2 \$ f/ s) _1 ~1 T4 b
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
: i$ N/ y1 s' k8 O3 J/ n' m  ?; ]6 V4 G) W7 x
if (!SUCCEEDED(hr))/ _/ e  G0 R5 n
        return(0);+ e1 J6 G$ P# f; c  H  y
* q" n3 l! m, \" a2 w% x, a5 t( B+ t+ r
DWORD dwStateBit;
0 {+ h8 _# j8 p2 T3 _  ~int cChars = 0;& @) H! x- B4 c# `
    if (varRetVal.vt == VT_I4)
( ?; V+ f' }. Z, f4 R' O{
  r. T9 ~! K) q7 r0 H8 [// 根据返回的状态值生成以逗号连接的字符串。* {& B% E1 p+ t% ^, Q- a  D9 l! l( B2 f
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
& w  ~0 d" r- A3 U+ ]5 C; G1 E9 b               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
* z- ?$ [9 e% K% P( z               dwStateBit &lt;&lt;= 1)
+ k' b- N& O8 P  P% g' \$ P        {
5 F9 f$ p/ C  ~5 W- e' T- U            if (varRetVal.lVal &amp; dwStateBit)
- u) D7 f& M3 d& ?* N9 O# z            {( Q* r8 j# S- ~+ D# N% @# Z
                cChars += GetStateText(dwStateBit, * ?0 m; t7 g; t
                                       lpszState + cChars,
* [7 m# \( [$ t5 f( w                                       cchState - cChars);7 A5 q! D" Q5 H  G" P
*(lpszState + cChars++) = '','';1 ?3 D$ L0 i/ Q3 z' T2 g6 d3 y
            }
- d5 n* g' i1 o5 m        }
" G6 W, W( X# H0 m7 P4 }) m9 zif(cChars &gt; 1)% f; Y. ^' Y' [: L6 N' @" d
*(lpszState + cChars - 1) = ''\0'';& Q0 z" y' S. ~3 T  m  h
    }" R1 n% A, c* p- y
    else if (varRetVal.vt == VT_BSTR)
+ }9 q: z2 I$ Q% ]6 V    {
% X- C+ Y  }3 @; h6 g9 v) h+ ]! M        WideCharToMultiByte(CP_ACP,
& l, G( N* m1 s/ {/ c5 k                            0, 3 E) X+ A+ i0 }9 i" l3 B2 E
                            varRetVal.bstrVal,
' t- J2 A2 O- ~3 u" ^( H8 N% `& l                            -1, 4 ^! W. \6 o" ~. G7 V
                            lpszState,3 {& u4 G# e" X1 h8 ?0 t& n! I7 ?
                            cchState,
" }4 B4 X3 I. N9 g3 d" k                            NULL, 0 R- l! z' ?, S; o- j  j+ t* `
                            NULL);
% M% f/ W, C  ~2 Q9 L    }: J& T3 N0 |. C& T
7 _- A, q/ t- N% [' |
    VariantClear(&amp;varRetVal);! g% L" `  }1 H2 G9 y% ?6 X

5 I% y3 s" @5 e; Q    return(lstrlen(lpszState));4 ?4 M+ q3 t$ c
}</FONT></TD></TR></TABLE></P>
7 j0 z& M4 h% }% N<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
5 ?' {$ N! e0 p* U' r7 Y" a! Z0 {1 G8 ~- q1 M, Y
四、在 IAccessible 接口上执行动作8 ^, z* M/ [/ \$ i- v" s
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
3 ?) I* q$ N# }# z! l8 P+ y1 }    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。+ Y, ?- S$ F( b& E+ p
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>4 i$ D6 m( U2 |
<>% l0 ]4 }; Y% n# q1 F- v  ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* x( c+ }+ u- ^* V& w. `% z

- U* a5 C3 u% v3 G3 G<TR>( B: g3 x/ Z! c: {6 t0 R% J) r
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
& B' w: R% X* Y" G. \0 q. }if(1 == FindChild (paccMainWindow, "打开(O):",
1 ^! s- Z9 `9 @1 V* V% o                   "可编辑文字", ) Y8 ^, P& T& D4 r$ r
                   "Edit", ; v9 X- Y$ \) c/ h! F% ]& K
                   &amp;paccControl, 5 r8 i/ b+ @/ w0 H
                   &amp;varControl))0 ~0 I' e& F2 Q+ y1 b
{2 ]/ _/ j$ I0 h: M
//在这里修改文本编辑框的值2 {5 }7 A; Z# Y# m( ~0 @% s
hr = paccControl-&gt;put_accValue(varControl, - U  F  d' R! j! K( c; e$ c
                                  CComBSTR("regedit"));
* g: I# ]7 ]: d7 t! ~paccControl-&gt;Release();7 C  L2 P" F, L9 _9 O% d( y
VariantClear(&amp;varControl);
* i3 ^2 T  n) l0 w}
+ T& \& n& S- t% y; ]. i& }
7 `2 s; q; G! K7 V// 找到确定按钮,并执行默认动作。  d6 S; I4 h$ v7 ^1 E! M% \' O
if(1 == FindChild (paccMainWindow, 8 l2 @; C- A0 D/ O; Z! u  _
                   "确定", 5 x5 v( n- @/ T3 z: I) I
                   "按下按钮", * Y* W& b2 g6 Z- e
                   "Button", . K# q4 i# r7 P
                   &amp;paccControl, $ {- r  C3 s/ T( n6 ^
                   &amp;varControl))+ N4 p4 g2 H' f/ @
{
2 P, J4 l( F" L5 S0 \( Z5 H6 L//这里执行按钮的默认动作,即"按下这个按钮"
5 O& V( Z2 ~6 A" t2 chr = paccControl-&gt;accDoDefaultAction(varControl);
. U( g* ~; F) I" @7 H, MpaccControl-&gt;Release();  A1 r3 T+ J2 b7 ]& Y/ y6 Q2 }7 |+ \* r
VariantClear(&amp;varControl);6 ^( ^* I0 b; ~- f% Q( j5 l5 Q
}</FONT></TD></TR></TABLE></P>
  A: G% ]# @1 a$ w! j9 |<>现在,你会发现已经成功启动了注册表编辑器!!
4 c4 k1 w: P# @</P>9 Q2 }+ l1 c3 z& E# N( _9 S
<>五、模拟键盘和鼠标输入
4 E* M5 ~9 s9 M! p, x8 ]5 Z    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。, r$ K3 H1 c& r9 o# F1 k
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
# r7 T3 v+ ]" I$ X! `8 ~2 a7 W    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。+ X& ?9 a' D) _. j3 X  C# _
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
+ d3 p6 H' b* y! k1 b7 c% x! V* x' ~<>
& a" i/ Z, ^/ w% K2 N' {* p8 n5 f# _<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>% ~/ c' ?+ F# `) I% U& W

( U0 Z2 M; N, B& l  b/ d<TR>$ \; [- r% M. h( O2 h: ~
<TD><FONT color=#00309c>INPUT input[4]; # `( O6 q" _) Y" k3 z) y& t
memset(input, 0, sizeof(input));
. ?- r. `7 R# L, W; B3 Y! T. ], p1 I+ e4 P( Y+ B
//设置模拟键盘输入4 W5 C. k8 ^' `! o! i3 R
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;% `; D2 W5 Q8 a1 v4 s
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;* n5 y' `% N/ _* d4 r& ]; G: P. }
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;# n4 |$ Q5 \" O+ ?- \& |

; n" V9 c2 d( ^4 r7 s// 释放按键,这非常重要4 r6 {0 n4 k3 d& s/ P. Y5 A. k
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;, s; Z2 U0 |3 X* a5 R' b: ~
/ p- g( F8 Z: n* S
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>+ v5 ^6 m  G3 I3 A- m. J) {# F" ]
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
  m( T- n7 b7 Y6 N</P>) A5 }) D( a% b& z
<>六、监视WinEvents$ F% I" ~$ l' v
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
( M& V) S+ h7 y: |9 y) ~3 s    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。/ J; n5 w& Y8 D3 z$ ~
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
$ I# v8 o. L- L6 R8 s. P<>, g1 y4 l' I$ B3 d
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>: L" T. C- ?: }' {

2 b4 ]; `2 j- j) l+ i<TR>2 u( s  O) [4 }# J0 g" {
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))2 G$ I* `/ q8 s& X
{
  E8 F' W2 i8 ahEventHook = SetWinEventHook(
$ H- u+ b1 [2 T% g" zEVENT_MIN, // eventMin ID1 {3 {) p9 J5 H( Y6 o( l
EVENT_MAX, // eventMax ID
. h% X# h3 e4 TNULL, // always NULL for outprocess hook, s5 n* m7 c- {+ n1 Z; K5 C
WinCreateNotifyProc, // call back function  t" p" ?) @& t  U$ M" M# J
0, // idProcess1 y8 p( f; X! f7 H2 j1 q6 z6 I
0, // idThread 5 w: b/ s0 K, O4 b/ @: ^" v
         // always the same for outproc hook" ^6 l# x1 @: r- d
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);/ R/ m* S8 M/ t& y1 a* B& Z
}  </FONT></TD></TR></TABLE></P>: t) I- s4 b: z  h& P
4 @; s& L1 j) A5 Z0 W9 p
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。4 v: {6 O1 s8 p5 P' `" {+ n0 X
下面是回调函数:</P>
* V3 T. ~3 U$ J; T$ q" ^+ ^$ z<>  Z  A* ~" z& H( A$ M7 v
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>( T( b, X' L7 n+ ]* ^
+ W+ E9 a8 x: l2 S, g  }- n
<TR>, D5 d. f- l: H8 z3 Z/ l
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(6 x8 K' {$ X5 ^8 r
HWINEVENTHOOK  hEvent,
# S  C9 X4 q2 t) SDWORD   event,* Z' E* m6 P9 Y$ D& ]
HWND    hwndMsg,
& v- z! q6 g. |4 fLONG    idObject,  J! T! Z" }8 O, l/ u) n4 D( o
LONG    idChild,
, j& S' O. T4 _DWORD   idThread,& T5 ~" w, N' w9 a1 P3 V
DWORD   dwmsEventTime, ~9 m, K) a) W! z+ Q  J/ O
)
4 v* [3 o+ @6 k' r8 n; v{
1 S2 g4 K/ b$ U, |  f5 {1 G  Q- V- ?3 x  C  m4 b/ W
if( event != EVENT_OBJECT_CREATE). B0 ~. ]: f, O2 G+ M. B! Z& r, e# J
return;
( a+ H# W; ^* ]0 B- C! U( P4 S7 q2 }) b1 s  I7 Q9 k( S
char bufferName[256];; q. a0 W( a+ V9 `
IAccessible *pacc=NULL;4 A$ k5 m: B! D
VARIANT varChild;. p  Y  R5 e1 _% F5 A
    VariantInit(&amp;varChild);/ |- B6 E0 y4 a2 ?8 h
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
0 I1 H2 ?. h, T4 ZHRESULT hr= AccessibleObjectFromEvent(hwndMsg,
. z: X- [$ [1 N7 A$ K* R" p; ^; s                                      idObject,
5 M/ T7 x) k( g                                      idChild, 6 H4 c' `) }( t, e- H4 Y
                                      &amp;pacc, ) [8 ^9 B  a$ J1 u/ p
                                      &amp;varChild);
9 }1 `- g$ I& x+ n8 `1 m9 j3 e: B8 @9 S, Y2 m4 g
if(!SUCCEEDED(hr))
# K+ B. D' ?* w$ Z3 E2 T{# C4 b6 {- T9 N- [6 s
VariantClear(&amp;varChild);, _" J' e1 E6 W* j, H
return;5 j7 g1 a, k& {+ \! M* U
}3 u4 D6 q8 h) l3 `& K+ ]
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。; o( a, }* x9 T
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));3 I& j1 ?. o+ a0 {  \
if(strstr(bufferName, szMainTitle))
; B# S  s' a8 F" fPostThreadMessage(GetCurrentThreadId(), ) l3 m( }  F. U7 y! ]# ~* }
                  WM_TARGET_WINDOW_FOUND, - v# k4 Z* R+ s7 w% q0 k7 ~7 Y5 `- X+ m
                  0,
: r9 ]* C" Y/ m$ \3 }8 s) q                  0);3 ?9 c& `: g2 F. y3 }& X% T+ w8 U. d

' d& W; I* w5 L# D1 b/ treturn;! _6 T7 e9 q+ i: z  T6 N) @
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。: g" N+ ?: h8 K/ c- B
- x& N! {2 m8 `7 S
附录:* \5 x; a+ y" y& N' V9 x5 q" T" J
6 p% X9 _0 A' Q/ r# M' n
关于IAccessible 接口/子ID对:+ e) I+ `. a9 R
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。4 s3 ?% C: \* ^: c0 H
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
; M- @8 y6 u) s4 w, V# j- E& ]5 @# ~    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。; G$ w4 l* G& L3 @& g9 X+ e
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
  o. T* E$ |1 h/ e5 a6 G    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!8 b8 t  ]5 R: R+ l& k4 X

3 t7 \% ^8 @6 P注:
( m! x  e& E* X3 }4 F4 S    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
" T# J  r' ^8 r8 G' ?3 ^, U    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
. ?- x  `* F( Z( x& u$ J& Q0 d, Z) ^! \5 `
参考资料:
& K  l( O5 G' z' m5 q<UL>* Z- T# b, {; i5 R5 p- n
<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
/ P4 G1 S+ x1 i: ?# @6 Y, k9 Q<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, 2025-9-17 00:16 , Processed in 0.512767 second(s), 57 queries .

回顶部