QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5727|回复: 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>7 d! S2 j; |1 ]  L. [
: Y& P1 S* h0 Z
<TR>2 O% w  R% `" {5 ^& M0 v1 b
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>7 N+ Z. M7 T# T
原 作 者:崔传凯
. t1 T8 r& H9 _原 出 处:vckbase
- p6 \  @2 [" m' S+ [% q  @: w9 w发 布 者:loose_went
4 O2 k& W. l3 {! v4 y& q发布类型:转载
& r. j( k3 s7 N+ Y# r发布日期:2004-11-12
- R4 U1 f, `$ S+ z0 d0 T今日浏览:65 v' r9 e, D# Y* M$ e7 M6 ^
总 浏 览:234
! }. o) n( d. F  I5 P; a</TD>
' g) C: Y" p5 t. C<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE># @# O. A  F$ f, {# B8 q" E0 w: L
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
( R" g! F9 J3 v; O9 E( f$ r* R- w& I* S( q$ |; k
<TR>
1 Q5 d6 `- ^4 m/ g3 R# W<TD vAlign=top align=left bgColor=#ffffff>0 |5 W- w! l. N: n. r- C8 P) X
0 h0 [7 B) q4 |6 q: T0 j! J2 T
<>
, W" b  y! r; f) B: H- d<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>8 ]! r! S) f) e/ }; [7 d" I

* ^, _- J. ?) }- m+ Z<TR>. y5 Z: B! f* S0 }; N  B' w3 `
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
$ J) B  `! _6 V5 w: @way accessibility aids work with applications running on Microsoft Windows?. It
4 J5 c6 A  k0 P' p+ W/ Wprovides dynamic-link libraries that are incorporated into the operating system + s! K' b& u+ s6 I( h
as well as a COM interface and application programming elements that provide
2 I1 e7 g5 s0 ~, y' {% ^% ureliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>. o, U- z3 _4 s9 h
<>一、基础
" O3 z$ Q: {( F0 C1 h6 J0 J: v    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。# x* A+ e0 j4 t/ e$ ^, P0 M/ c" t
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。! \8 Q5 l$ N) F" s; n& v2 e
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
4 E2 F% k1 Y; {* F6 A3 u2 |<>二、Active Accessibility 原理9 S* y) g0 ^1 F6 ^
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。  _+ P; l& v7 A# p8 Z) p6 w+ x
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。' M( ~7 b) L: d- w# O& @$ Z
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。$ y8 O* E2 }* j, O# e
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。" f/ [. c4 M! D3 e0 A1 p. m( h: h
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
( o* N* A2 x$ G8 b3 Q<>三、如何得到 IAccessible 接口指针/ |1 v" w2 |2 }2 ?, ^
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。, s$ X2 a/ h" {: M, Q
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。9 u: w3 z6 k  }: C- P
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。0 e0 l& J4 o( k  y- ?$ V: Y" q
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。$ t7 B* |+ a$ f7 Q
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。$ b8 V' j' `  U, Q$ @0 z  ]
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
4 G2 v# Z% U$ u( Q/ i4 P# \下面结合代码介绍一下它的用法。
, Q7 P6 t3 Y0 w7 X我们来得到下面运行窗口的 IAccessible 接口指针。3 m0 B! B( C3 a, \& ?: K8 e

4 @! B5 d" G" E<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>+ D) e# \* [  l
<>图一</P>
- R5 i4 b3 J, Q" ?  A3 [- y  S<>
" w6 a& y# X1 ?2 ?# n<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' m; j- r9 l# y7 A

8 {9 M+ l6 y6 L8 @  i<TR>
, @, ]# X; G% }  n- O<TD><FONT color=#00309c>HWND hWndMainWindow;, I1 I& {) S9 C0 j; S, H( H2 k. ^
IAccessible *paccMainWindow = NULL;
7 l3 |) }! q# j# U& D2 \  p% EHRESULT hr;
7 o# n4 v, S* H/ h! G2 Q//得到标题为"运行"的窗口的句柄9 M, F& C$ i0 x5 O+ }
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
1 b7 h% I$ a% N3 j7 G4 M7 Y{
5 {% ?4 V5 Z) O& z2 WMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);4 r' ], f9 Q4 ?4 }0 H2 h% Q
}
8 M5 ]  {- \( }$ S& b5 P3 Kelse+ `* r" d+ z( B% D7 v' T
{, j3 @3 P7 y' |2 U! s- ~
//通过窗口句柄得到窗口的 IAccessible 接口指针。- Y3 B/ ~6 ]$ \0 y% `3 t- c
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
2 j, P! @1 h# o( ^, r/ t2 n+ R                                            OBJID_WINDOW,
: c4 B9 x, S3 x3 }5 G7 a, p* {                                            IID_IAccessible,: J; C- I; l' w" t/ J$ |( ~6 K3 C
                                            (void**)&amp;paccMainWindow))). N; s6 ]4 B* h, |
{
( h; n  n. t0 ^//……我们可以通过这个指针paccMainWindow进行操作。
3 {: R7 X) V- D5 q( r# h. ?5 F2 R4 E# KpaccMainWindow-&gt;Release();* c9 ]( P. b" r! V
        }, m% B! k& @7 W4 H3 G. o
}    </FONT></TD></TR></TABLE></P>
. d; e1 C6 M3 ?% [9 J$ s# @  W- f<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
1 }2 u1 x9 v1 V' c3 F/ S    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……  T# v# n+ E% i0 }( s5 y
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
+ Z$ G0 x/ B! N5 v, G; {<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>; D6 i! A7 y4 B* r" J( L" h. ?
<>图二</P>
. S% r1 Q6 d6 n* a- x( y' y<>我们现在主要关注的信息是:Name、Role、Window className。</P>$ h. S" X. F6 a4 u6 F
<>
$ U* L2 F1 i2 }3 |1 ^<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>, D" K% ~# b  h& q: {) l
! W8 j( y& \  A' c2 v
<TR>" ]: E  S3 h3 \0 Q' H4 B: o  y: [
<TD><FONT color=#00309c>Name = "打开(O):"
/ z6 t8 m( V! |3 MRole = "可编辑文字"
: i( \6 Z9 T: I- V0 yWindow className = "Edit"  </FONT></TD></TR></TABLE></P>% v; k' k' r8 n4 C& d# w) L
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
5 c! {  a2 N. F) R5 t    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。) W9 @; L7 f2 M  J7 l
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
* Y/ @+ |+ L& @6 k/ E7 H! d7 l<>
3 _7 M. |8 f+ s+ H4 F5 _<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 E( G6 J. m) J
: L. `. S9 u/ Y% d* Q" r( V* ^) _/ D: J<TR>6 u2 \) h9 _* V# t. A# m5 a
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口! ^& M, N. t# y4 R  C0 g, G
VARIANT varControl;    //子ID。2 F* [( e; H* v2 X+ L) R% ~; o
6 n& M0 S: c, c5 i1 w# z- _
FindChild( paccMainWindow,
, \9 s' H. ?. S! |. c& X5 }           "打开(O):", + F/ W3 x# `& V7 L
           "可编辑文字",
: `/ S+ ~( m) V  e           "Edit", & O) P/ g/ }9 a" \- C8 E1 P
           &amp;paccControl,
& w- }& t9 n  x, u  b           &amp;varControl ) </FONT></TD></TR></TABLE></P>7 E* S' w: W% j7 C% j& _6 a
<>第一个参数是先前得到的窗口 IAccessible 接口指针。5 a) D' `' t# Q, R
第二、三、四个参数分别是名字、角色、类。
( X8 v" W/ I  f2 h8 B; g/ ?后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
: p7 [( {" w5 U& e7 x: q<>2 e. R6 w. R! [
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
+ ]9 `% F7 ^! w2 I0 @4 U0 q% P. d5 B' ]4 D) M, ]6 [
<TR>9 I  o6 d# N2 t$ E. S9 l
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,   V, f9 V7 N3 p. f) p+ X1 A3 |% v$ e
                         LPSTR szName, LPSTR szRole,
- u* d  V$ X5 e8 A8 R; y/ H) c                         LPSTR szClass, 3 ~! L/ X$ w& t& r2 A3 j# f' N& g
                         IAccessible** paccChild, - b9 t  Z0 u+ W! }5 n3 ?, o
                         VARIANT* pvarChild)
" k% E! d* G) v9 R0 j{9 \8 K/ a) y: }
HRESULT hr;' i+ W8 T" R! n( S3 f0 x5 J
long numChildren;
* {7 x* t  L# V# {9 Yunsigned long numFetched;
) u; f. c7 t, U5 d8 OVARIANT varChild;
3 B. {; }3 }, A( R# tint index;
- C; L/ k% _7 S; m0 d. |IAccessible* pCAcc = NULL;: f0 d; m6 B7 D/ C/ L& F, N
IEnumVARIANT* pEnum = NULL;
3 N2 p2 H1 l/ P0 j% E3 y0 h, kIDispatch* pDisp = NULL;6 B" @1 _! g/ d# F; P) v5 f8 E' E) K
BOOL found = false;( A. ]/ k) T, Q1 G, o1 e
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
) R- u6 J! m7 m! h6 m" {; K( c1 S; P3 ^5 K
' l4 }' j7 w( \& P$ C//得到父亲支持的IEnumVARIANT接口
5 X9 n# m4 [7 o9 H, `. _, \2 ghr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);9 I' h" o$ A& K

6 o* C) x. F/ }, I3 A1 [9 U; Rif(pEnum)! K4 ]1 p. Z6 L7 P- T5 r* w
pEnum -&gt; Reset();
: s+ {+ ?! n- f+ B) g9 p; ^6 H5 L+ }2 M8 O9 K% M: K
//取得父亲拥有的可访问的子的数目% S/ V' u4 ]# k! J$ A/ k/ j! k
paccParent -&gt; get_accChildCount(&amp;numChildren);
5 Q' n$ r" o5 ?8 h2 |( u, w
+ |3 q( G* y* y2 x, B( D//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。: r, D# T  v, o$ U) q" }1 u
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
. h) `" n6 b, \( }- e+ M  `$ S+ d{4 x4 [& P: ?- D% K
pCAcc = NULL; + K3 `# D$ @( T1 B+ {- U9 Z
// 如果支持IEnumVARIANT接口,得到下一个子ID% }6 }: \% b! o4 I' x
//以及其对应的 IDispatch 接口
% B! x: H/ h) s& k3 D2 B3 T- j% l4 Xif (pEnum)* F3 z, m3 ]9 I6 ~
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
: I* D: h8 `$ n; f  r0 j9 C0 L7 Aelse: h6 W6 |% D/ @3 |6 l. e
{! V1 ?5 |  o$ I+ }
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
2 Z4 e5 r/ i. c; g( gvarChild.vt = VT_I4;
" T& \2 l1 R" z* G* T% u% kvarChild.lVal = index;5 \7 K4 `1 S" J; x/ Z) _
}
6 e' m; H4 T8 s* y
) n4 z& b3 z7 ^& j+ c3 \2 l// 找到此子ID对应的 IDispatch 接口
+ Z5 k4 ~  n9 x2 P5 P" Wif (varChild.vt == VT_I4)7 m7 u0 a7 F8 q( m7 t2 X% |3 K/ n
{7 `3 J7 B# X( x8 S2 d
//通过子ID序号得到对应的 IDispatch 接口
- y/ L, O! f5 Z4 n$ @" wpDisp = NULL;  V5 Z# R! s  B8 u
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);  G% ]1 M& i) |% v
}- n6 d6 h1 P: }/ W5 @
else
1 f) e, }* d, D//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
4 |  c$ D8 S8 x2 L# H) }9 Q, u9 m" BpDisp = varChild.pdispVal;
, F  C7 k2 e" a/ `4 v/ D$ p
) M- W+ O9 ]: L0 Z3 O% @" ~// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
# Q( u0 J2 ]7 V5 u$ fif (pDisp): u1 i: m4 H# z6 p, v# Z. q
{
; g% {' H. L6 F( X9 g  u' ~hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);! a. k" M2 |/ N! \% _  F
hr = pDisp-&gt;Release();
: I, }5 p* `% K}
" [* o4 ?1 m) U" O$ `7 k4 |2 L4 V3 @8 U4 I
// Get information about the child
# c. t" M5 }; Xif(pCAcc)
1 W- I7 S& d( L. ?+ i5 |6 E! _{# ]& d& G( [6 i6 ~8 B
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
5 M$ S2 z9 j' A; u; b( U+ ^2 u3 wVariantInit(&amp;varChild);" x" K2 O  i; W8 Y0 p: z- ?. @, ~
varChild.vt = VT_I4;- t- w$ L- g9 |1 Z' u
varChild.lVal = CHILDID_SELF;
/ [( R. J0 g. W* u* L( Z& r7 a/ L/ F% e- Z* C/ Y# m9 S; A
*paccChild = pCAcc;* H7 C' V4 _8 y+ c3 ?* H1 U8 ^9 ^
}
  a  r. j) Z1 G7 d' S  Yelse
+ X4 X/ l: ]' w//如果子不支持IAccessible 接口
# j  T: t! j, Q2 S$ _+ |*paccChild = paccParent;' e+ t& o/ v5 c5 G9 [

' h" c/ L3 D. |! {6 }3 |//跳过了有不可访问状态的元素. R) v2 v$ z& R9 m, i
GetObjectState(*paccChild, ! j# L5 p; o' r/ q6 e4 W
               &amp;varChild, * s- Q& d7 z( E! i8 U
               szObjState,
+ W: z4 t' Q, f4 S* w7 Z               sizeof(szObjState));& |% O0 o5 E% c9 J. K0 P/ n
if(NULL != strstr(szObjState, "unavailable"))
/ @6 I3 _# j" j8 H{
# t/ r* n1 x: Y( _( s+ Z$ Aif(pCAcc)6 d; D( f0 @( Z. _/ J
pCAcc-&gt;Release();( M5 K/ _. B: h
continue;; z/ E- H5 x0 a' D6 K
}
, t( P: ?7 f/ w, `. m//通过get_accName得到Name) o7 x0 Z  t' Q' Y2 w
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
$ f& j/ b; S( h$ A8 P5 ~0 I//通过get_accRole得到Role+ k& i1 v5 ?/ C, m7 h, l& K! @
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));; F  X' c, c- v- _7 {& [
//通过WindowFromAccessibleObject和GetClassName得到Class% A* k: I$ C  C5 }& I  c
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
9 f3 V3 b! b, p1 i' j6 s//以上实现代码比较简单,大家自己看代码吧。
0 j  D, _$ n9 W' T& d0 Z2 p, ~" `! w6 W# A0 e) z% V
//如果这些参数与输入相符或输入为NULL
: H- J2 p" x+ H" B6 `4 G" E/ Oif ((!szName || , Q( ^. a% P% T
     !strcmp(szName, szObjName)) &amp;&amp; . [7 X: e* D8 `
     (!szRole ||
  t( Q; k) m  i+ j' G) Y8 V      !strcmp(szRole, szObjRole)) &amp;&amp;
4 P- K+ q9 u9 l/ T3 i4 p9 g     (!szClass || 3 K9 _  ?6 l, }8 p! y: F$ w) _9 ]
      !strcmp(szClass, szObjClass)))/ ^- s0 y0 \3 s3 D7 P  E1 S2 H
{0 m7 H; x6 j& k, r% [6 {- ~3 r
found = true;" P6 w  R2 n. W! D
*pvarChild = varChild;" a4 j3 I3 |# |  `* S* n
break;4 F- |- ?* z* y# W/ q+ B: _
}
( F* ~/ `1 |) ~# A) M( ?- tif(!found &amp;&amp; pCAcc)
0 Y- \: g! F3 I/ g9 K! E2 @) c{' K: Q; k( X: f5 X5 j$ y8 T) _6 \
// 以这次得到的子接口为父递归调用
: }$ T/ P+ V! Pfound = FindChild(pCAcc, 7 l; h  y2 o# t
                  szName, ' N& S4 \1 m. Z% w# X& m
                  szRole,
. l+ K3 ?! C) _                  szClass, . k( H8 N2 }" u  e  E" J4 f
                  paccChild,
0 K* p6 H7 |; v/ s                  pvarChild);
5 H$ {- Q$ g0 J- j5 gif(*paccChild != pCAcc)7 D3 r0 b1 W3 Q- _% z
pCAcc-&gt;Release();
' j. h* }# q; M6 u8 C& s8 R9 i0 ?}! P( y3 U  q* }3 _: l- P2 s
}//End for
& e5 g8 B& r: c4 m1 h& _/ W% h6 J6 P  n  o6 X& }+ U5 j
// Clean up
7 R9 n2 H- {+ u1 X5 C  M$ ~if(pEnum)
  K" g& q7 g0 A+ h% \9 N; IpEnum -&gt; Release();
% x: |2 C0 Y8 v# D' o4 t& I3 \
3 a4 r* k. a, t, S" D3 greturn found;& U+ G' ^( ]- f( |1 w# N
}
$ z1 ]( ^, j  \" F8 u
; |- `" J4 z7 F" {0 B; t// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,) X0 k# B/ i  m' Z
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。6 V/ P3 M7 `$ ^* ~  Q$ N
//将这些或数转换成相应的用逗号分割的状态字符串。
$ ]! |' w' [  h9 c* W& JUINT GetObjectState(IAccessible* pacc, ' ]$ M( T! }6 \" S# N, x
                    VARIANT* pvarChild,
2 ]" e) d8 d! j& S: ?* \                    LPTSTR lpszState, ! p/ l: B/ ?5 b
                    UINT cchState)4 @+ S. C( t" Y" J% \
{/ ^; z7 O- x& J3 t
    HRESULT hr;
( G* F( e! X: f  E+ Z5 S0 _    VARIANT varRetVal;
& T, A# b8 N' c( I- `" v" D
4 z8 Z5 d0 C( r" N; ~    *lpszState = 0;
- J0 b% u2 q( r: E0 m
# e, T+ t6 O' c1 h4 I    VariantInit(&amp;varRetVal);+ F2 J, u* ]8 i7 z
, \4 x& w9 F% b3 G
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
% c; F9 S6 T- T0 Z" Q6 [" d
$ V- t1 X  Q/ kif (!SUCCEEDED(hr)). x2 p( d* ~5 e1 ]
        return(0);* {2 o0 {/ [; F) ?. P% P. P! E
% M: E5 ]9 ^8 y* y
DWORD dwStateBit;
1 Y" m4 i6 O4 `, W8 z& dint cChars = 0;
, ?2 ~5 |1 C* q% y0 z    if (varRetVal.vt == VT_I4)
, l. L0 {9 b  r2 Y7 h( V+ S+ o{
/ u5 P- z) s  a( U// 根据返回的状态值生成以逗号连接的字符串。
+ S% e' R6 _' g5 e* q        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
% `3 B3 `6 c( h0 L+ y               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; ) @( z( |/ {9 k  y2 l
               dwStateBit &lt;&lt;= 1)8 @! r- }' [5 G8 x, A
        {
( m5 T8 f' C* B# w; ~7 a7 Y            if (varRetVal.lVal &amp; dwStateBit)
6 f2 L1 v1 y6 q0 e# U            {4 M+ u3 J' Q( y3 R
                cChars += GetStateText(dwStateBit, , p% v/ w& N$ q! i( d
                                       lpszState + cChars,   ~0 U+ Y2 E5 C8 q( m2 D
                                       cchState - cChars);
/ d) w5 z$ [  q; j' m3 p*(lpszState + cChars++) = '','';+ j3 V/ S& f5 G* n
            }
* ^+ a$ D1 B* f+ @! f4 O% m        }
9 k' J6 Z1 u: M9 Hif(cChars &gt; 1)
& P( t: x! {$ U*(lpszState + cChars - 1) = ''\0'';0 H( g# N! @& ^1 Z! y
    }
& l  y& e1 g. {! I8 q    else if (varRetVal.vt == VT_BSTR)
; c( q9 T9 z1 [    {% K& m, W% G2 k3 U) e( `/ H
        WideCharToMultiByte(CP_ACP, ; ]7 J7 L3 j. a$ V  n7 M7 g
                            0, / |- z. N  q$ A3 U$ N
                            varRetVal.bstrVal, + {$ K9 _: g  N9 n! ~. @" o% ?( Z
                            -1,
; T  F# U. i1 i& Q: l3 I2 ^: @* D                            lpszState,+ @7 w8 r1 z" f" X2 ~( N
                            cchState, 8 J7 D6 ~0 q1 R1 h1 c/ H
                            NULL,
, C& h3 B0 f( B3 B1 l                            NULL);, u! H9 y/ S  d  d1 x) R) G1 L' f
    }! @3 G7 C$ H: O; ~
; R6 Q0 `& b: \, P
    VariantClear(&amp;varRetVal);% c9 z0 h! b8 B$ ~, U

6 q7 `! g: S( d+ n9 @+ f    return(lstrlen(lpszState));
6 G0 m& u) W9 ?; p}</FONT></TD></TR></TABLE></P>
9 Z1 M3 O8 u$ O, b<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)$ x* d$ E+ [5 G7 o$ S

! T2 W+ H0 h6 k6 ?( ~- W四、在 IAccessible 接口上执行动作
5 k1 X; p1 f, D. D+ B" N2 e    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。* A. Z8 Y+ X0 c* u9 f1 n
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
  p* C" O& e' p$ O% M1 @    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>8 L' ], K  C; `( `  Q$ f$ O
<>
! f$ E% X/ @# [8 V9 C0 w" W<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; ?: j+ a6 ~9 i* ?
2 \' F! ?, p9 s9 w" G0 E) |# V<TR>
+ c( v" H, [/ c  X$ V- w" S<TD><FONT color=#00309c>//在文本输入框输入"regedit"
! H- T0 J. c) }* w6 R7 \! gif(1 == FindChild (paccMainWindow, "打开(O):", ( [( E5 h' r6 \+ i- G  G
                   "可编辑文字", ; f4 v( ~: L8 G3 G1 i
                   "Edit", 8 q) e* H) D5 ]0 c% H) |
                   &amp;paccControl, - q, n& U" j; c1 S9 `9 V4 u
                   &amp;varControl))
9 ^1 N7 E" m! m- p6 q& c; U; A{
7 P% j( }7 F* J) f3 R, ^; r//在这里修改文本编辑框的值
7 ^2 Z3 F  n! f+ ~hr = paccControl-&gt;put_accValue(varControl,
% N5 [1 a* T* e' `* A                                  CComBSTR("regedit"));- _7 _. o' Q) D
paccControl-&gt;Release();
& {7 r: o5 n0 m2 f6 o( zVariantClear(&amp;varControl);' j  H" _1 R0 h4 Z
}
+ Z- Z) r% o* B/ A' {# @+ m' u
% M9 z7 J3 z# y$ |, U4 e& \// 找到确定按钮,并执行默认动作。
. o7 h5 I7 m' `6 R: vif(1 == FindChild (paccMainWindow, % f2 C$ n& l# G: L6 ]. R% G
                   "确定",
. c, g2 Q7 G: Y, k9 x                   "按下按钮", 0 e5 c( p: [/ V! L. H
                   "Button", ( b5 X1 Z9 x0 W- N7 H
                   &amp;paccControl, : B6 T, X8 G/ m
                   &amp;varControl))
0 w) K. E6 j# x" S; A. z7 T{  E7 D. Z8 s8 f, r5 I# G0 z2 r
//这里执行按钮的默认动作,即"按下这个按钮"  i2 G% v. V6 l" x9 N) a
hr = paccControl-&gt;accDoDefaultAction(varControl);
& u4 [- |& D6 `; ppaccControl-&gt;Release();8 O" \2 J+ j/ T. \- Y% l
VariantClear(&amp;varControl);: e7 B) ?  s/ w8 Y2 s2 v4 G
}</FONT></TD></TR></TABLE></P>+ \, i4 i  |# a3 w  J; L( k: }( F5 V
<>现在,你会发现已经成功启动了注册表编辑器!!$ E6 i! v! }1 p3 b! C$ z, u$ T
</P>8 f0 f' b, d% Q" `
<>五、模拟键盘和鼠标输入
7 L2 U- U4 [# q    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。8 F" y# Y& c2 \* p2 S
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。# K" a! W0 h' n! \  X: p
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。+ |" i( f8 W. O4 v  F# i
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>& {& W( s7 O0 _4 m8 K
<>& T4 M/ H7 J6 p7 }5 C9 o
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
3 q1 K0 ]0 L: b9 E/ k: C
+ c  Y+ c6 o, D% b& a<TR>
+ o" g" m; k7 }$ {: d<TD><FONT color=#00309c>INPUT input[4]; % P1 C, P% J0 k; C/ o" Q% N2 _# E
memset(input, 0, sizeof(input));
' l! V# S7 M1 T$ _6 r4 ~% o3 w- x0 e( o, k; J" `) S
//设置模拟键盘输入
  I" v; m+ V1 F' k# \# pinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
" K$ j4 ^6 t; b; j. D2 C- r; |3 vinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
) X5 O& Y8 |- F+ b4 binput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
& n/ E1 q, `* Z/ m+ Y! S) _
2 Y) r* |( [! r0 d- b+ l// 释放按键,这非常重要1 ?% A& c2 x$ E8 o$ ~
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;3 v! [% H* t4 @: f

+ q2 ~; W4 C; [SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
% `0 d( r8 i; ~6 d6 t<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
6 F8 {" E+ P6 L' v</P>- y! N7 [, |% X2 p! Z
<>六、监视WinEvents1 m# c, \/ L2 ]/ ]
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。# k- D5 M4 a, v/ A. s3 I
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。* G( P( ?0 l, i7 y
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
( Y$ \+ F- @  Y. d) m8 z% F- @<>
! O5 ^9 K( }3 r3 J<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>" y. O' j7 B  J
% h4 A6 H; A& g3 z" y6 \
<TR>5 C; ~* z2 K, {" s) F; W: X
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
/ v( T6 S0 J' d( |{
1 s' V  ]/ K, `hEventHook = SetWinEventHook(2 p& q, f* D3 }# f" P: i
EVENT_MIN, // eventMin ID
) R! l! E( `6 U, Y3 U4 PEVENT_MAX, // eventMax ID
6 x2 h$ |: q) uNULL, // always NULL for outprocess hook8 ?  h$ g( V( B/ Q6 g, g, Y' r* ?
WinCreateNotifyProc, // call back function6 @. U7 T4 p$ d" k: A
0, // idProcess
4 ]3 j# Y; s* x. e+ Z& ~0, // idThread
/ H' n& V: C( ?: m         // always the same for outproc hook9 d" N9 c' k7 B
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
1 ]6 }3 b9 H2 O$ Y4 |2 z}  </FONT></TD></TR></TABLE></P>
6 ~& R3 Z* }" ^4 l
& Z5 \5 b+ [: i5 p<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。7 w$ o1 ~% `" o( \8 K8 @5 F( `0 p
下面是回调函数:</P>
# E( ?3 H' G$ Q" I: P<>
  f* ~  G7 z" {. i% `# Q<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>3 p; ~( u' U# S; k$ b7 h
3 G4 V" Z# b7 ^' K0 _3 \4 k
<TR>2 p0 X% u! u8 W' H) s
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
6 X1 U: \. L+ h& M$ w6 b5 KHWINEVENTHOOK  hEvent,
0 v9 P; M5 u" @" D5 @0 ]DWORD   event,
; ?4 w3 C4 a" l& jHWND    hwndMsg,# S# x, m* @( I/ E. _& m2 J
LONG    idObject,2 f4 W& N' K' c) U
LONG    idChild,
" X0 n5 \8 A& `6 [- A$ I* jDWORD   idThread,  g) Q  a9 p' F* F% }0 X
DWORD   dwmsEventTime
( Q2 `( s* \) T& Q3 j' ])
! c" l6 }$ y$ J7 K0 A. e( A( N6 K{5 [; H. g/ b% ?' R1 N
5 y& Q. j  ]( g2 c1 W. S# Y
if( event != EVENT_OBJECT_CREATE)4 x' F. o" z+ k8 @$ h" B
return;
5 L- l. X8 h  U6 l
* q7 s8 Z& F& L; Tchar bufferName[256];
" u* w3 A" |8 n2 zIAccessible *pacc=NULL;
  G8 B, {: _1 ^2 n: Z9 H: a, R1 P8 e! zVARIANT varChild;& V" i* \$ }1 u4 a% c2 r5 q1 t
    VariantInit(&amp;varChild);4 n9 ^. j  Z, @
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对# Q4 v6 t  y& J7 y& Z: o
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, - F1 u& W/ b, ~$ k
                                      idObject,
/ J% [, z! a" d& U                                      idChild, . w+ Z* [; ?8 H; C
                                      &amp;pacc,
. s+ |7 O/ v. @+ V& K                                      &amp;varChild);
1 e9 X0 c, H# D; I5 U, o# G1 O0 A9 ]1 M5 f7 ]' y& A0 m0 v+ z1 d
if(!SUCCEEDED(hr))1 T% V' d  l; e( A$ S8 E; f
{7 M  v4 y3 v' @0 R+ l
VariantClear(&amp;varChild);9 ^4 S- h* x: P0 R0 s
return;1 k3 y9 `$ z7 u" E- T
}
9 m/ D0 _: T- N* T6 A/ ]//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。5 U& E! K+ Z$ x4 l: x! \
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
" u, o3 r8 V" S* D7 |6 xif(strstr(bufferName, szMainTitle))
" z; @4 V' C# Z* U- H; XPostThreadMessage(GetCurrentThreadId(),
4 n& l7 C/ n% o' i8 I                  WM_TARGET_WINDOW_FOUND, # U, N" o" O9 R+ [, @! F. n
                  0, 5 S2 k) L* B: n; b2 F7 x3 ~7 S
                  0);
. C- [2 x+ N6 `) Q+ [
, G! S3 `7 V: M9 u* ]* E2 A7 _2 qreturn;% y  B! s+ Y9 ^; O  i7 L
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
' @+ R( \) x! {
4 u( w# t" K4 z: ?! @+ o附录:
# v5 l# H; q7 O  V) \
! G8 |5 @+ @  V# D8 d+ k关于IAccessible 接口/子ID对:7 q- N- A% A) b% [
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
! Q2 B+ h1 r+ V+ p; X3 {    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!) R+ L3 D. h( F  X* V% M; k- m
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。8 G2 h% v' t9 q5 [/ o+ Q1 s9 g
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。  c/ @/ G, W) R+ t9 `
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
  p! a" u% ]  O; ?. @
- Z' }, [" H0 E2 _5 h1 f注:
1 {0 f6 Y& w& J, i& @    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
8 f! p3 [$ Y% t! `" n' X    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
. o1 c/ q* F  p7 [# k3 k1 d
1 Z5 ^; C0 S2 O- i参考资料: 6 ~% Q2 X: i6 \* l
<UL>$ h* w2 g5 s% N  q/ Q- Q3 T
<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
/ ]+ r, n4 @: r) b1 y: X* g5 ]<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-8-23 05:51 , Processed in 0.446469 second(s), 57 queries .

回顶部