QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5695|回复: 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>0 b8 F3 X& \) p0 T

# i' [, ?/ V# x" c<TR>
  r) C* \1 x7 I, o/ E5 J- U5 A<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
3 d( m. H5 _+ H) g. y) z0 B原 作 者:崔传凯0 S) I) V* Y& M! z2 k* b
原 出 处:vckbase+ D8 U% U+ q' K0 q
发 布 者:loose_went0 @- A1 e, P/ k& b% Z2 Z
发布类型:转载
- i7 }4 A$ s. F7 c/ o* x发布日期:2004-11-12# B/ o9 _5 i) w' l9 p. y+ J
今日浏览:6
3 Z: L" J% e+ G0 [! W. d总 浏 览:234! I9 m& t: |7 M. W: T
</TD>+ W5 H( _4 w7 |, m8 L8 [
<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>) {* Z+ y# L2 W. Q7 B" d# t
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
+ `9 j! t) i, `2 {: B* _: U8 F  L* S% }- O' G
<TR>& h* l/ h! b- p* C: H2 H- Y
<TD vAlign=top align=left bgColor=#ffffff>
* ]5 U7 @* U) i2 Z  }: m8 R) n7 n" e8 P( o  H
<>
$ F! c# q/ G8 ]" J! I<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& O+ A& T6 E3 ]! c# D0 f; X* s. X- }

3 B1 _- h0 }0 M: j. C<TR>3 l, }* y/ A( N7 N. n
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the ) U6 A8 B% j+ j$ J1 P' n
way accessibility aids work with applications running on Microsoft Windows?. It
3 t  W6 ~# x4 {* q; {$ ~# D; qprovides dynamic-link libraries that are incorporated into the operating system
1 W! v# B5 R' H( D/ w0 a/ Pas well as a COM interface and application programming elements that provide
' _/ N; I. \) B4 Xreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
! z* ]- R. Y: S3 G. v4 b3 N# |<>一、基础
4 N) T3 F4 h0 h    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。- j- ^9 S9 s# x, S# e
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。! s! d% b/ E2 L0 v( E* R0 t( t  Q
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
2 }3 p2 L6 g4 R! d" Z( v<>二、Active Accessibility 原理
/ d- D$ O; Z9 R    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。+ w7 k8 m/ ?% v: ?' S! u3 a! p
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
+ c% j& [  e$ E4 r6 O! H    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
, Y* w  i( N$ B4 Q    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
4 c. m3 g* n' e3 v) g. g    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>! b& ?* K2 x5 S1 z/ [9 |- s
<>三、如何得到 IAccessible 接口指针. O4 g- C/ H$ W; }( V; q
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。+ D( \- E' R& Y# I0 ?$ t6 N: n
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
  r( l% U" P' }3 i# ~" x' A* `    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
! o# }0 [- F/ [* P8 f) J( f6 ]2 B    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。" S( U8 _* C% s) Z' E
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。! t) k; x0 h  [$ z4 \
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
& B4 k0 d$ |- j下面结合代码介绍一下它的用法。
% y' Y4 ~" ?  A1 p4 d我们来得到下面运行窗口的 IAccessible 接口指针。
( g4 p- J  u0 t/ e. k. r( f, t, K& N5 t- @
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>/ {% W% O8 a- @. k0 M
<>图一</P>
1 ?5 m  \9 v. k' s<>9 U- ]3 g; M* I" V4 Z4 h! T
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>4 K2 K  O3 N: U5 m
$ J/ x" |4 z/ w, c8 z3 b0 z
<TR>$ p2 c$ ]: |; [, S1 S' U2 X" ^2 z
<TD><FONT color=#00309c>HWND hWndMainWindow;
3 v1 J1 t. s9 jIAccessible *paccMainWindow = NULL;$ ?" A8 e( m7 ~1 d" s
HRESULT hr;
" k- ^" c/ \. T* S, u//得到标题为"运行"的窗口的句柄9 F2 |( ^" c$ {! c$ H9 P
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
! N! n" U, z6 M( G7 G0 A2 g  j{$ Q$ M) Q! G. x/ \! u6 ]
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);' x7 F9 L9 I$ C" p
}
' J0 N/ @6 N$ h" `1 J/ m* S1 F! Oelse) ~5 ^% I2 w, A6 s1 g1 q  N; ]
{& {  I' F$ q0 u( `7 ?# {% ]* ]2 G  b: K
//通过窗口句柄得到窗口的 IAccessible 接口指针。
3 k3 v  |. D( V  G& K/ n% }if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, : \5 s1 N: X( C- T- q
                                            OBJID_WINDOW,
5 }5 j/ g* ?$ u, [4 J7 B$ E                                            IID_IAccessible,2 p/ p0 [/ i* Z: m) ]/ {! W8 G
                                            (void**)&amp;paccMainWindow)))8 q& {+ G. w0 j, c0 D
{* q/ }) P( R9 s
//……我们可以通过这个指针paccMainWindow进行操作。9 Z6 u# {8 w( p/ v  E" f
paccMainWindow-&gt;Release();# E. |4 F+ `# _$ ^/ F0 Y
        }0 x# j8 J" V% n8 h; F! b) T& b  V
}    </FONT></TD></TR></TABLE></P>1 z5 Z% g& g) T/ {
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!9 O0 C, W% A) ]) G" X0 d0 {2 F+ G" |
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……2 z4 `4 ?) m, j% x8 N9 x; x  Z
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>, |- \0 N, ^) H; I
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
% d' v# U5 I/ U4 ?9 _9 w* u<>图二</P>" ~* {/ S3 U+ V0 M  U- ]
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
( ~, L& w, t6 h  Q9 D<>
+ E) `! t! f* e<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
/ W) s# N( t  w' z3 Y$ j8 }- H) b! e( C. F6 H$ X
<TR>! Y/ _+ C! C# b5 B& _  N
<TD><FONT color=#00309c>Name = "打开(O):"
3 W3 o3 N, |& R0 \Role = "可编辑文字"# u2 G3 F) n& B: f
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
' Q7 G+ i8 d8 V+ ?9 B0 V<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
: s* u' ~( v( E% a) @' J* q    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
' q+ P, _* {% T- R& J$ c下面我们开始取文本输入框的 IAccessible 接口指针。 </P>/ C1 Z. A  K( J* D: p6 B: _
<>+ S1 Q* H5 [' ^; M* M; s
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
- B: }- ]8 I  k  t
0 t# @" t! _+ g7 H6 }2 n<TR>: d( b8 F) j  T/ ~
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
4 S- \, j; g' }, K) `VARIANT varControl;    //子ID。7 @6 k" R( R. a
: @+ O+ u7 A. c6 O7 I6 }8 \7 Z. ]0 f
FindChild( paccMainWindow, 5 p1 K- e/ D. N; W4 D* t/ t
           "打开(O):",
$ d5 I, J+ k7 E3 p- n7 a: M           "可编辑文字",
) h8 s5 x, c# }6 s  E- f# Q           "Edit",
! c; _* `( e. L. Y, a( q           &amp;paccControl,
  E2 y; U1 r) E! W" ?( V           &amp;varControl ) </FONT></TD></TR></TABLE></P>9 V* L/ L% V  b& @
<>第一个参数是先前得到的窗口 IAccessible 接口指针。1 Q. R+ k/ n: k. l: u' t# A& x
第二、三、四个参数分别是名字、角色、类。8 B/ N) _1 Y1 N) v# P
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>6 F4 a5 i7 R. Q
<>
% V5 h. D, s6 Z7 W<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>. k" ]* M" Q2 O9 B, W, W. n1 k

( N5 M! r, C( }; f2 D3 R& W4 Q<TR>
& Z2 N* t* o% u# Z2 s<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
% U0 I: Z- Q% [* I                         LPSTR szName, LPSTR szRole, 5 U: U0 e( V  K1 x$ O( q
                         LPSTR szClass, 7 M( Y* h6 V( f: U
                         IAccessible** paccChild, 5 A9 J3 R5 a0 r# S
                         VARIANT* pvarChild)
  f$ @  [/ L" Q! R6 ^* u7 f( U{
; M1 y5 P0 L+ T; h* U" ]HRESULT hr;
5 N! _! P4 B7 x1 A$ B* L2 f# tlong numChildren;
8 k& v) T, ]9 T9 Z# B" l, l' T! E; Sunsigned long numFetched;
; S  @: Y) ~- oVARIANT varChild;
$ k4 z8 m7 k# N6 O% Kint index;* C* i* S& Y4 H5 c& M; W8 f5 U4 f$ V
IAccessible* pCAcc = NULL;% j3 w: F3 t, P
IEnumVARIANT* pEnum = NULL;1 K$ e9 a% [0 f- x8 J
IDispatch* pDisp = NULL;0 W! P2 ^# C1 p" d' U3 o
BOOL found = false;8 C  h9 z" s) ^; X* ^4 B4 V
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
5 K8 C1 d4 b2 B" ]
& U% a! |8 e" c//得到父亲支持的IEnumVARIANT接口
# H- I8 w. C4 P- g' Qhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
( `5 M: O$ |5 a- O5 P% U3 s7 j* x7 L  }
if(pEnum)
8 H! X  Y& m) J) ^% upEnum -&gt; Reset();7 C- `, t0 e& K' P6 Q

# Y3 w( Q* T6 |# H- l* V- K; ^( J/ ^//取得父亲拥有的可访问的子的数目
) _! D) B- V: G/ N' x2 t3 ^: lpaccParent -&gt; get_accChildCount(&amp;numChildren);7 d, |$ I+ a  N) B

1 @" g! O* h+ A) u//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。8 C  _- F, S5 b2 x
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
4 S. g% ?* a: E/ a0 ]* ?$ a{
% e: {& t5 G: G$ \, y+ @pCAcc = NULL; & f2 T2 L! P4 Y% _9 G4 _
// 如果支持IEnumVARIANT接口,得到下一个子ID
) Q9 ^* V, y% M. P//以及其对应的 IDispatch 接口
) q% r+ @2 f2 v! F, q6 B  R  W: fif (pEnum)
! S! @' {6 q, H: [* D9 u' E7 [- jhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); + m1 z" L7 b$ h0 |
else
4 s; X% Z. p# [; E2 `{# K% G; Q& H! j0 |8 M5 ]# G5 p4 A" v
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号8 I8 g. ?, {% w% F- g9 X- g2 l
varChild.vt = VT_I4;1 E; e  o" G; D) V9 M6 K
varChild.lVal = index;
$ C8 [: Q( q5 \2 `: }2 A$ q}
8 p6 f. [8 Y6 D& I" o$ w- R$ Q2 m' {6 e$ x! l  e: B: W
// 找到此子ID对应的 IDispatch 接口  E5 E8 {3 q; }5 t3 i% _+ E
if (varChild.vt == VT_I4)+ x- T; t" e8 ^/ [
{* ?" ]3 P+ o/ p
//通过子ID序号得到对应的 IDispatch 接口$ b& a, V" Y1 F  }9 V
pDisp = NULL;" q6 z5 V4 c8 y5 z  |/ U" V
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
- n( I3 y/ n" j; S' [}
2 F! O1 K2 ?$ d, Q% B, K2 ?else
" t7 H' M0 ]! g5 r//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
& |, w' l, I1 q. z& l2 BpDisp = varChild.pdispVal;
. Z: `7 c3 t; C# ~7 v
5 N. I; `1 w4 x4 p// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
2 r' @8 w* B# N5 V7 eif (pDisp)
- Q: T7 t" }: w" U+ O{
8 n7 x8 d2 Z4 j$ b. L+ y& ?' w9 ohr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
0 ?# d7 p+ f2 \hr = pDisp-&gt;Release();: Q) n( m- R. I* A" l
}& ?( Q" r2 a  g. L% w0 N9 u

; S9 ?, j) {& l6 T7 W// Get information about the child: w: |/ I* \% G) d2 E6 a2 ^4 S6 ?
if(pCAcc)+ p& A, U- ~+ T$ s
{
! W5 T' G: P, U$ J" l; j- J//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF& R+ I* c7 D# b
VariantInit(&amp;varChild);
. s/ H4 Q1 J. U/ nvarChild.vt = VT_I4;! @/ G/ K/ @1 y% d& x0 o
varChild.lVal = CHILDID_SELF;  T+ v( Q: [7 Q

$ K/ w+ y- j* y; P*paccChild = pCAcc;
( {' H: p- l4 C5 O8 ]}0 j% j7 Q- \8 ^: f) v+ t7 y6 Z
else
% N; D  D0 l8 P3 `//如果子不支持IAccessible 接口- M6 W( u1 |% A- x! @$ M
*paccChild = paccParent;7 n' Y: b+ P1 |3 S

) L; e8 D" C# z$ R0 Z//跳过了有不可访问状态的元素
  L( _: q5 j( ?GetObjectState(*paccChild, 8 Q- D/ d/ c, k3 Z7 s5 S
               &amp;varChild, 2 P! Y( W; M2 z1 O# N; Z$ {
               szObjState, - f: n- M; z8 n
               sizeof(szObjState));
9 X) k/ u3 D  }1 }if(NULL != strstr(szObjState, "unavailable"))
! T" q9 o6 W, S  o% @4 b  h% v0 Q/ |{6 v2 @0 N7 i2 ]* [0 H
if(pCAcc)
) g' o9 U. l- [( D; @- ppCAcc-&gt;Release();
- I# V' P+ k; \; f1 fcontinue;( V6 m" m; _7 L5 n3 `
}1 [% B1 n& S) f
//通过get_accName得到Name
: X1 b+ ]0 M& v1 N! qGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
5 F: a9 P& }0 Q. q//通过get_accRole得到Role6 [4 D- m" W8 E
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));& ]* z8 V; Q1 a( z! F+ ]5 w
//通过WindowFromAccessibleObject和GetClassName得到Class! C! w9 w" ]8 ~* t- {
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
6 P# R! r, c4 ^; @; w6 [% j3 w; @//以上实现代码比较简单,大家自己看代码吧。
  R# P) `9 ?$ }3 x% j; R6 x" C8 \% [+ p" S! y( I" g
//如果这些参数与输入相符或输入为NULL
0 V! U7 E! C3 `  d6 Mif ((!szName ||
! F7 u& g3 }3 U2 N( u     !strcmp(szName, szObjName)) &amp;&amp; 2 X/ [$ ~0 R0 v8 u3 t$ Z, @
     (!szRole || ' L# ?% D7 S3 o3 d- }
      !strcmp(szRole, szObjRole)) &amp;&amp; # L$ C# i; @4 W" f
     (!szClass ||
) m3 P9 ]7 `* g0 C& s  y* m      !strcmp(szClass, szObjClass)))
# F( b  `+ Q4 S# U{
2 I5 g' [" G4 y7 j' Ffound = true;
, q$ E* c- U% S7 I) }*pvarChild = varChild;6 Q7 R8 k7 d  x1 X3 i- i5 a
break;
5 x, D$ J( ]8 f+ S0 R* E}, n" P. v& D% ]8 V3 ~
if(!found &amp;&amp; pCAcc)3 a, t$ z2 O7 D- ^% @4 x- M
{
' U& ^' F. H8 m" F( W$ X5 E7 g, n// 以这次得到的子接口为父递归调用: {# w  M+ L* @& d! g" V
found = FindChild(pCAcc,
5 ]- T  Z$ B" {8 @( [% I                  szName, 4 p& m/ n9 S* ^6 C
                  szRole,
7 K' T, G) _' ^, r0 n( j                  szClass,
) e. _1 u- r; ^" H1 u                  paccChild, # [: u, p* M: n" f
                  pvarChild);
) E0 C) @7 {4 k2 r" Bif(*paccChild != pCAcc)
# g" S. `5 V' `  z: S) `5 S1 t+ j" kpCAcc-&gt;Release();
# {8 w" S7 {5 X}
4 f1 n4 F  I# Z2 S}//End for2 s, ?* k; L. _0 d1 ^5 h1 x+ @

$ t: |; g! _+ ^( l' ]// Clean up
0 Q/ g0 x3 e. q7 B; s. yif(pEnum)& E; d6 ~3 m$ c
pEnum -&gt; Release();
+ U6 `$ S: N5 E  t$ |+ ?! Y1 W5 ]' V  M  w
return found;
' s) s  m+ }) Q9 n}
6 V4 T0 S) [: h$ y' I- ~# t' J7 N5 ~) H- l" ?. a
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
" D8 `: W6 l6 O( i: K1 B8 V//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。# f# r) h+ |2 ]2 T" d- d
//将这些或数转换成相应的用逗号分割的状态字符串。
' b4 h- E/ a: f( ZUINT GetObjectState(IAccessible* pacc,
# f8 b% x2 X" r4 T                    VARIANT* pvarChild,
: E( T4 q4 O& u$ Y; X                    LPTSTR lpszState,
* R* D$ g7 i2 t, W: o/ ~( l5 k                    UINT cchState)
3 o, E" N5 Z& x3 P. O1 m6 I{
9 @" ?( J' k6 o% I5 O4 m& h    HRESULT hr;
# k& v! d7 |! f  ]2 _1 U4 v    VARIANT varRetVal;
3 }  M8 [5 [  f3 j4 p6 {+ `0 [1 \/ v5 f' ?0 b3 |# L% g! f9 D3 J
    *lpszState = 0;. {. k9 ?+ ]1 F5 T! d8 x% b" @

. U3 K( w1 M( r, e$ \    VariantInit(&amp;varRetVal);
, k  p+ ]  ~! q  t/ p5 l* n( o) u: O1 ^, |9 T7 k
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
. u: y9 }1 y" L/ d6 a
  E' W! q! l- z2 q3 o4 o2 \if (!SUCCEEDED(hr))$ s2 M7 Y  e0 v% Z" v* P
        return(0);
/ f! X, C1 p& l- t$ B+ f: x6 p% a: T5 l5 ~0 R& A
DWORD dwStateBit;
) I( L* M& i( x" ]int cChars = 0;
) i' G9 \& _. r5 U) c: b: o% \/ p    if (varRetVal.vt == VT_I4)' O, B4 n' d1 B! G. L& C
{
' c& ]8 ~! L2 ]5 J" \// 根据返回的状态值生成以逗号连接的字符串。
7 d  i9 b: ~6 C4 I" Q; a/ B        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
. I9 K0 }: f$ H# _  C               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
9 x4 j6 t, P) n8 f% P- A               dwStateBit &lt;&lt;= 1)
2 {& Y  F- E9 D' U        {% d* a: Q9 z$ q2 P  c2 G* ^
            if (varRetVal.lVal &amp; dwStateBit)
9 |/ b4 G: d8 |/ s  \5 k            {. g- P! j" A# q( o
                cChars += GetStateText(dwStateBit, 7 B9 U/ y4 k' V( V; |
                                       lpszState + cChars,
9 \1 S; x; }% _; ^. g2 Q3 t                                       cchState - cChars);% D5 J9 p" d# s
*(lpszState + cChars++) = '','';
1 K/ I$ V4 H6 V            }. x, C) C) p* ^9 g- G% T
        }; L" ?* H% F% E1 Z
if(cChars &gt; 1)
# H$ h. [$ p3 i/ e- x& j*(lpszState + cChars - 1) = ''\0'';
/ ^9 ?; f5 u+ z2 q3 _7 S    }3 @0 I2 a0 G3 q3 O  N
    else if (varRetVal.vt == VT_BSTR)6 A2 ]& P. }' N1 Y2 d( Z
    {
! r4 g, J5 L/ i        WideCharToMultiByte(CP_ACP,
" P8 L9 h! v' h, v# Q0 r                            0,
6 f5 X1 c4 s- q& V                            varRetVal.bstrVal,   H( e  q" r4 x) O9 |$ v
                            -1,
, Z  h- o, Q/ ?# `                            lpszState,( I' p4 d5 c9 Y3 ^- ~+ d  o
                            cchState, % p- K/ ^1 j+ _% M9 w  \
                            NULL,
7 D8 G4 [6 R) b6 d                            NULL);
1 ^' O; B( |$ R5 L0 G% ?2 A; b: a    }
  L4 X. E6 V8 @  O7 ?
% u& T1 ~8 R# U0 Z    VariantClear(&amp;varRetVal);
% v* W( T# a. k! _! s6 a: W, U& r5 f% O& [
    return(lstrlen(lpszState));4 @* _# `9 d1 |
}</FONT></TD></TR></TABLE></P>0 w( i0 \* \$ M8 c4 ?
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
- t2 l/ m. h& f$ t1 ~5 A/ m) d! @2 m  v: N: O
四、在 IAccessible 接口上执行动作8 u" n1 C2 n: x" W. L
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
' [8 r3 y9 |& y% U' ]    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。& e- q) r0 [/ E$ i- g
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>/ \% d; M' M  b) D* u
<>( C0 D) G& u2 C- f
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; ?! r( c# r. c! U8 O& v: ]. v% h; V! Z  Q9 {: M' r: n+ N$ L) K
<TR>+ M- V7 h4 `8 u( Z) G, i4 r
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
$ v4 N- H2 I3 X' |) `6 N: \) Q& F( Jif(1 == FindChild (paccMainWindow, "打开(O):",
  F1 m+ _. o' ]8 `& e; l8 B                   "可编辑文字",   e% G2 N# Q4 M  u) K  I
                   "Edit", : |! V" }& U9 ?: u( U- j5 [
                   &amp;paccControl,
, \* |$ ]& w3 D! a9 K! |1 m% c3 U                   &amp;varControl))- z5 E1 ?! F' [6 J# h+ R) d
{( X9 |) {$ [/ H+ b2 @
//在这里修改文本编辑框的值
+ o# a# U% w+ m* }% fhr = paccControl-&gt;put_accValue(varControl,
/ E: s. E/ b6 q8 ^& N                                  CComBSTR("regedit"));
' c) _! I+ N5 c8 z8 a' epaccControl-&gt;Release();5 `# L2 g+ _' P6 w5 x
VariantClear(&amp;varControl);
! h4 J6 U% h: M& o! ~2 Z& E}. m0 p/ X- B: {
. H8 L5 {, J* W2 j; l7 G' T
// 找到确定按钮,并执行默认动作。1 u) d! u; M4 o( v
if(1 == FindChild (paccMainWindow, 2 {3 [4 W( v6 @1 Y! |. A
                   "确定", / A& ?+ j7 M) F2 u! W
                   "按下按钮", 4 C' x8 ~+ E3 x9 z+ Q3 K4 z2 J0 m
                   "Button",
! m9 y8 |$ _$ w* U4 l. V                   &amp;paccControl,
( {* B/ S0 f! l0 K" ^/ K3 y5 {) _                   &amp;varControl))( V! s: F/ C+ _# r9 q8 g  Q
{
: r* ?. t5 V3 @" _2 U/ c! ^0 L//这里执行按钮的默认动作,即"按下这个按钮"! I& @3 b2 I. B( [  o5 w6 e
hr = paccControl-&gt;accDoDefaultAction(varControl);
/ w3 P) @8 X' X/ z& ^0 i. r$ mpaccControl-&gt;Release();1 U* t5 J) D2 K
VariantClear(&amp;varControl);2 G# l6 }5 R6 t* H6 m1 i# C7 q
}</FONT></TD></TR></TABLE></P>  {; L- y1 L" l& q
<>现在,你会发现已经成功启动了注册表编辑器!!
* m7 g# P" z5 S! u2 h6 Y3 M, e  S1 h) B</P>. U1 l3 H5 x# B+ G/ E0 [
<>五、模拟键盘和鼠标输入
3 w- w% ?. t7 O    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。1 F1 \; a1 d& E1 _. H  w! S
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
' d$ T8 d# t; u3 h; q% D7 t    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
, K6 p1 v4 b( a3 t# Z1 o; T下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>% M1 |$ A) a) U8 ?0 o" y
<>
! v! G! o! Q- G. q$ j& ~+ m  B1 \<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>- T, {1 M" |1 X& |) e

( a( O, ?& {# ^9 v' W) j& D0 [) E<TR>
' `0 x! B  d7 C9 h! w<TD><FONT color=#00309c>INPUT input[4]; # h/ D  F5 H. b) ^
memset(input, 0, sizeof(input));
2 H1 C: m: d/ h5 Q! G# E) Y5 o
; i+ \$ l* h( F, I9 N" A, m# S//设置模拟键盘输入7 ~" Q- O: n" {0 i' Y
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
# @0 f" w- v: qinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;+ ?- X+ A2 d. r+ x) r
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;5 e3 y7 [) s/ X1 ^

: b+ u1 r' C1 j% [6 f7 z// 释放按键,这非常重要9 [6 R6 `7 r* D; ^3 M
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
* {' @/ i5 l. x1 U9 h1 \& U
; Z/ O* U0 A; ]( V) aSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>' T. G' W% E' g3 k- K- I
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
3 Z* f1 E9 q/ o6 W4 s/ n1 X</P>$ O( s& O% o9 o3 l; ~; T, C8 ^: g
<>六、监视WinEvents: \9 s% [; v, k
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
* k- K# F) w! C/ z6 c    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。  K6 ~) T6 i1 k1 x! v  A
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
, D! b% z0 c* l6 P6 y/ b" E8 j<>1 ]* t, e3 z8 ]+ ^" L' H
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& `6 r7 Y5 j+ ?2 g# f4 t
2 ?2 [6 L/ }3 c; b) {! |
<TR>
" w3 ?: o, c) m. W! m5 W4 q/ F<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))1 G: w5 c: B9 r
{. |8 N2 {2 u2 G( \. Q$ e
hEventHook = SetWinEventHook(
, ]; r7 j7 u, Z+ B. y7 jEVENT_MIN, // eventMin ID# q( @' ]2 Z: Q) S: f
EVENT_MAX, // eventMax ID5 M. j# A4 v/ r/ b" P: R" y
NULL, // always NULL for outprocess hook
1 J1 h' g( x" f! E) L: Y  v6 r; KWinCreateNotifyProc, // call back function8 c9 q; l/ h) B/ h
0, // idProcess' w5 e' V# M0 F7 R3 b: G
0, // idThread
% K6 l; W! r1 V         // always the same for outproc hook
" T; q+ o) T1 M8 w  ?; @# SWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);) ]0 B7 d  S6 p. N1 ^& {
}  </FONT></TD></TR></TABLE></P>  _+ m) }6 V' N
3 V+ A' r! K* w
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。$ o9 j$ G  y0 |; F
下面是回调函数:</P>
2 p% A  r0 f5 n8 j$ g5 l<>
) l- Z. I4 p6 x* f<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ L2 V; U: d4 g. ?2 f$ h% E- o' x; d

( j# n* I  x* G6 Z$ N! L& I<TR>0 R" E: f  v& H& \; @
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
- H' P$ X( ^' R/ y8 c  F# `/ Q+ IHWINEVENTHOOK  hEvent,
; g7 p2 |4 m0 n4 i# a  [, u0 C$ a; rDWORD   event,1 M/ W. u5 C. {& g
HWND    hwndMsg,4 _0 |) ~4 N8 W/ r% A
LONG    idObject,
$ F) p, z/ B% m  ^LONG    idChild,
  U) N' ?- g( N) q- _. d4 ?DWORD   idThread,
3 N3 p. H9 a" B% \DWORD   dwmsEventTime
9 y9 ^# F; p* i( V7 K. u7 x6 r)
# O! n2 y3 N- T( i+ g" h{
! d( j1 S! C! F& V4 n! U, M
9 n( k6 l) r6 Qif( event != EVENT_OBJECT_CREATE)- w, \. T7 @: @5 }% D" O- E
return;
- k$ |* N3 B( |7 Q* q$ W+ I) ]- U& J3 t
char bufferName[256];" M) h9 V2 S9 x, |6 S; \9 W
IAccessible *pacc=NULL;% m- Q; m2 e  {$ p" i3 C# K; W3 z
VARIANT varChild;
% _8 Y) @" L' Y9 n# d/ _    VariantInit(&amp;varChild);- G% M& W+ Q) p' [/ i+ Q2 P
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对0 q9 x7 p% R9 ]" H2 @
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, * r1 V0 V) J( v
                                      idObject, 6 p$ V( Z- r6 v2 S; |
                                      idChild,
' l0 A2 b' `# _0 H% ~3 m                                      &amp;pacc, " [( f: c, C1 q% ^" U$ y* [, J
                                      &amp;varChild);
' U' n6 N2 r' R! }9 M
$ R9 i$ O) J3 O8 Bif(!SUCCEEDED(hr))2 x# I6 T" ?5 k* C
{
, c5 y" y1 y) S4 F/ R4 _; sVariantClear(&amp;varChild);
# Z% m2 }- x1 ]1 h0 X: Lreturn;
/ Z/ b( C2 Z; x/ X9 E* w}: q+ O3 m1 i+ s! |& r0 t6 D
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。/ E8 i+ D8 X! z% c' k% C
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));: R# p/ k' h1 _* n+ ?9 ]" e. P
if(strstr(bufferName, szMainTitle))& J& K& x2 @- z
PostThreadMessage(GetCurrentThreadId(),
, ^/ D2 [" n, l( Z" n, Q4 l5 r* o7 D                  WM_TARGET_WINDOW_FOUND,
" ~" Y" F$ l, H, [) ^! u0 `                  0,
2 ^& n  S* W3 H0 P0 N                  0);
% S; q' P' L6 s1 z8 m7 _0 w. U& P1 [2 r9 i6 k
return;/ |2 j" d2 ]/ a: M' \; L! F
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
. O6 `$ P8 ~# x/ k, g, X0 g5 o4 C
; I! R) {2 s4 S附录:% m& B/ q8 _4 j  Y: z
# d6 a9 h3 ~+ ?2 `" ~" j+ @
关于IAccessible 接口/子ID对:
( z- J! T& L# g- v. q/ p0 h    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。* m" @( ]5 B  D7 r4 f# w) |
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
1 w( c) _6 j6 j3 Z/ L& N    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
, q( f, ]0 N0 r! w6 q2 _* J3 g    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。1 J8 ~4 a3 w' w+ }: V# j$ I
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
: ~' w3 `9 i& I6 X( ]/ G  n, ~( Q0 c* |& u0 x) h" G) f
注:
4 M6 l& b) `' T    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
) V1 |3 E; i! `' ~# D    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
: R# S. r3 m0 p
& D7 H+ X0 x* j( g; O参考资料: . S$ `4 {$ k: T5 t$ L$ B) C: T
<UL>/ X4 g. n! |' l
<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 ' `; m6 L' A; v3 H3 F
<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-6-11 20:54 , Processed in 0.479254 second(s), 58 queries .

回顶部