数学建模社区-数学中国

标题: MS Active Accessibility 接口技术编程尝试 [打印本页]

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
( L; ~) v% D, k5 M2 s$ b  t2 F! j1 v6 e
<TR>
- c/ R4 I9 ]" l+ r+ k. v' ?; i<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>. i8 S# T5 }8 W- g
原 作 者:崔传凯
% _; H1 N& V) E7 e原 出 处:vckbase
$ g" D& |0 I2 |% L发 布 者:loose_went
: m2 S: C9 ~% f: g4 _1 G发布类型:转载
' O& Y, w) a/ Z6 H+ D发布日期:2004-11-12
+ t% V, O0 M6 @* n8 m8 z今日浏览:6- Y% |- ^- w. U* L$ r
总 浏 览:234
* ~: V3 M, J; t9 M! u+ ~& \& `& \</TD>
( d' M" Q5 {/ V! H<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>) I& x5 R; z; a. q
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
. T) t5 ^, F: E; ^+ u1 Q+ r. ]
: X- a$ E% }! B& H" g9 E# S<TR>* K% d$ r' G) P8 X! _
<TD vAlign=top align=left bgColor=#ffffff>
: E& v6 ?" p* }. D  [6 m
4 Y/ o4 w; @* j6 C% N$ b<>- V2 c% y4 u  `6 s% n& l
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
) j% p5 k& ^0 S. z1 t, C! }3 @0 S" a' b6 ~2 h  h
<TR>
& b2 A# U! k+ y# ^. C5 Q- E<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 7 ]7 D% p: t# b
way accessibility aids work with applications running on Microsoft Windows?. It
8 R: t( m# N* d2 U  |" ]. ]) bprovides dynamic-link libraries that are incorporated into the operating system 0 V  z& O- ^6 n) W! B. E8 v2 b
as well as a COM interface and application programming elements that provide
6 ^* x( [* L: G% b# M" wreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
" A8 D5 `- {# B( s& J8 X<>一、基础
# x2 u1 T! L9 z/ d, j0 K, y  l    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
/ w' o5 Y3 P; k& I4 ]% g3 L1 t/ V" n$ k    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
) u4 e2 `0 Y& Q  D7 |) W  a    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>% Z* F; I& J/ m' u1 a) o% b3 m3 v
<>二、Active Accessibility 原理
3 T& U. l1 m  n* v7 B' h( v    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
* U4 {& A) Q6 u% g) j3 u    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。9 D) \3 H0 l! ~. J, f) N) C. E
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
4 d$ Q6 K, P) [$ t* M    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
; s0 m9 G9 q) v+ r! R2 Q, t    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>! I  Z/ a! @: m5 W
<>三、如何得到 IAccessible 接口指针
3 e* z: x: k' ~8 g- C    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
0 [7 J' w1 @, E1 y; }9 S' `: a7 j    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
. y- ^* ]  y" G/ d9 u8 l    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。! v/ ~4 G# N4 m5 d4 V  F
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
1 [( c3 [5 ~! L    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。& A- `2 @' e$ }! ]/ `9 N
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。8 I) _; s2 K! ?; C
下面结合代码介绍一下它的用法。% B4 P7 i. i. N) R
我们来得到下面运行窗口的 IAccessible 接口指针。
/ A5 k9 [% U; `/ l5 G: \( l8 T1 X# K' A6 J6 t5 U
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>$ A, ?+ k0 ^& V* T
<>图一</P>
* D8 A3 c- P. f5 p, J2 s0 }<>' [) D+ n% l) y2 K# Z  K0 A( H" ?
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
! p$ @* o& H0 i- T9 L* y
  y6 c( L! k5 j<TR>* p) u, Y) |9 R% u: w+ F6 Y
<TD><FONT color=#00309c>HWND hWndMainWindow;' V) v! H. x) q+ S4 u
IAccessible *paccMainWindow = NULL;, W* d* v: z3 v! @- B
HRESULT hr;
4 k% q* z6 D8 C, F: h//得到标题为"运行"的窗口的句柄
6 R: \" ~9 I# g2 A! F1 M. W& o4 aif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
: m6 `4 O; f0 J+ g# q2 A" N: e{
* j  o4 h# T& X8 t  [MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
5 e+ G" s. r2 G' |: q0 f}
8 X) X0 W5 J5 D& Velse' n& T0 n6 r/ O) A1 \
{. X: L& a1 L4 _
//通过窗口句柄得到窗口的 IAccessible 接口指针。
6 S; ]2 a' ?1 b- o. Eif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, , u9 W/ s$ i0 N1 y) `
                                            OBJID_WINDOW, . A3 ~5 M0 y. K6 o0 D' F2 A
                                            IID_IAccessible,3 s9 w7 f# y0 l# G" C, E7 F1 U
                                            (void**)&amp;paccMainWindow)))' Q5 M, n5 f  @+ ^
{4 ?& T( B3 K6 K, C+ G
//……我们可以通过这个指针paccMainWindow进行操作。% u7 I) e# ~* `( _
paccMainWindow-&gt;Release();8 M+ L: m# M# I& s5 t; o
        }6 Y; L* ?% Q/ N
}    </FONT></TD></TR></TABLE></P>
( S# E/ u; N0 \+ P" y% B; i) I<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
0 p8 i% E0 U; Z/ S+ t& K1 w    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……' _+ F- c9 L4 f* a* y5 R* ]
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>' `5 ?; c: `. Y, ?& \$ g0 z5 }
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>, C1 j$ h6 x  D7 @/ G
<>图二</P>/ H9 Z! O7 E) ]6 ?2 B
<>我们现在主要关注的信息是:Name、Role、Window className。</P>3 w) m( ]% @( Y8 X, ~9 h6 W
<>
( x1 ]- s" q+ F" o7 b0 h<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>: }3 r- {4 F( ~3 L

. H$ O" n8 u9 X+ S<TR>
; p& q* N& _6 l<TD><FONT color=#00309c>Name = "打开(O):"
" D! E9 l5 c. `2 cRole = "可编辑文字"
/ a; ?7 q4 M: i- E8 Z" @* FWindow className = "Edit"  </FONT></TD></TR></TABLE></P>$ R3 e+ B  H; y8 M9 ?6 E' ~
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
( S) [# P$ w' L5 J    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。  o! K3 T/ B5 E7 u$ S: ]
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
. T) R; W- ^, w* y/ k8 @2 k<>3 t" U# w; w5 y( K3 ~' \
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>$ a' ]7 H& C( d  p$ `9 x

9 v# H5 K2 `( w+ w' U<TR>
: V7 k' Q; J- {0 ~<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口8 v% M2 z' k* w- ?1 r  G3 `+ z0 N6 ~
VARIANT varControl;    //子ID。* L4 `' I4 i! K9 g. |" s$ f  _

, H! {' e( I9 [* w* `3 UFindChild( paccMainWindow,
5 }5 I" G6 R5 v, ~* f/ m9 ~           "打开(O):",
4 N( o& |! g( L  H. J: k           "可编辑文字",
, d: U7 N8 R6 ]; O; [           "Edit",
0 Q  D) m7 W7 C0 [3 |% x7 V, D, ?           &amp;paccControl, . _# s3 |3 \7 G. Q- m
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
4 L  I0 [. [' @3 e/ s, k<>第一个参数是先前得到的窗口 IAccessible 接口指针。8 s7 y, R* P6 X# f# a0 H5 u; F" E
第二、三、四个参数分别是名字、角色、类。
7 y: ~5 ?8 l- _# P3 s+ G3 }后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>$ Q  S8 j- J- D0 d" q, b6 y8 k
<>
! n- I* g1 L; t<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" ?/ J; Z' Q  L3 Z: h! a9 T. y
2 f  Q' F/ Q# b<TR>
7 V: O( {5 n6 }9 @5 m$ w4 V<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
. Q1 X4 u7 z/ G: M# f                         LPSTR szName, LPSTR szRole, " y5 x% S6 X. V- ]. Q
                         LPSTR szClass, % P9 p9 U, \. l9 ~! J8 x' E% Q
                         IAccessible** paccChild, ; l, F& Q. p' q( o$ }9 M
                         VARIANT* pvarChild)
3 z" N+ p3 @* m6 f( ?6 h0 K{
' |  }; D5 F( V7 W8 oHRESULT hr;
( q$ z. X- v# @+ _* D2 B# c! \$ Dlong numChildren;! g9 Z1 T5 X: g7 F
unsigned long numFetched;8 V) e# \; N# v" s! P$ d, a
VARIANT varChild;2 f, _  [, X; C4 D( H: a
int index;
/ S- f4 }: w2 Y" z$ rIAccessible* pCAcc = NULL;
! L0 n, v5 X5 P8 k* Y" [IEnumVARIANT* pEnum = NULL;
/ b9 C1 @/ l& E5 i9 ~9 _, L1 j* iIDispatch* pDisp = NULL;; R7 T5 g8 @9 w
BOOL found = false;
- W0 |. N; \9 W- v- mchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];5 Z4 ?8 \: Y1 X1 {" g/ L2 f
" b7 D5 y2 g: g/ v7 u3 y
//得到父亲支持的IEnumVARIANT接口
! j* s4 F8 C. e: n4 thr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);( N8 T$ S, U* K& H. B

( A1 m1 z1 V0 U6 Vif(pEnum)
$ H5 a7 K) s, a9 d9 w- RpEnum -&gt; Reset();; o7 u9 J4 J: ^) L! \, G/ Y! v
+ W; ]* @5 P+ ?0 G: F8 A
//取得父亲拥有的可访问的子的数目
9 M1 [2 h# M" Q/ opaccParent -&gt; get_accChildCount(&amp;numChildren);* C1 ?. ]* V# G

( K: e$ l$ Z1 a- F4 F* [+ C- i+ ?//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。& J3 k$ G5 B* }& _! @
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
: B( r$ q# P7 a0 B- p4 ~{
2 w& [. }2 j; Y( @$ g8 bpCAcc = NULL;
) `  c/ O. p! O3 h// 如果支持IEnumVARIANT接口,得到下一个子ID
8 g$ P/ V& Q" {: p6 K' @* P//以及其对应的 IDispatch 接口
6 s, X; ?' f; E6 B4 z5 S0 Dif (pEnum)
2 f$ j4 E+ J% uhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); ; y" ~! t- c9 \* \! f& J! e
else0 }) Y. o7 C2 W( @2 ~
{/ j# h/ ?1 B2 }
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
! z( r' r4 a5 f/ b# j( t7 u# l  t  fvarChild.vt = VT_I4;' M( L5 n5 T0 ?4 d/ |! @0 @2 P
varChild.lVal = index;
3 V1 k% ~: L* g; U}, D, ^1 H- f1 L6 w% J+ R
* E4 n6 t8 k7 x7 B" c: l: i
// 找到此子ID对应的 IDispatch 接口
! N; I1 t; t* d' K+ X7 W8 Dif (varChild.vt == VT_I4)
( X/ w4 U/ M, ^; u0 P8 y5 m$ H- ]/ G{1 z/ i. K! o' @0 f( h" m$ k
//通过子ID序号得到对应的 IDispatch 接口
7 a2 a7 B, k/ i& d+ LpDisp = NULL;
  {# J+ G' {" t; m8 {# zhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);9 F% q: m; B# q4 x
}) [# @. \6 ?. o, [2 u+ I
else
0 u+ c2 w6 f9 C- M6 `; w* O' J& D//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口, ^2 V8 [6 l# t$ b/ s7 ?& F
pDisp = varChild.pdispVal;
6 C2 w' T" H: G) w' ?8 m' c
# x6 ^7 d9 o; G0 {* p// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc* T' b5 J1 q) L
if (pDisp)5 K3 ]6 _. G: M/ `
{
$ _9 L; ?+ d  Zhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
; `' Q" G* v+ ]  G& J) T# [% ehr = pDisp-&gt;Release();$ Q5 j1 U5 F4 O0 x# ~
}
( z; G; I0 H9 p) }2 L
* b1 u6 X1 K) D; B4 J+ [// Get information about the child0 B5 I8 @9 t" Q# X( H8 p5 y5 M+ n: U
if(pCAcc), Z4 K4 [+ Y/ b& l8 c! A
{9 B/ h7 a, z  l6 W
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
% f( x0 q+ x7 UVariantInit(&amp;varChild);
1 [) d+ N* B( Q: T9 B# t, }5 avarChild.vt = VT_I4;- x/ `/ k* A  _9 ~/ [8 g! f3 v7 a
varChild.lVal = CHILDID_SELF;
* I( Q" g8 c& \
5 f. U, O# r- q, ?7 m*paccChild = pCAcc;
& ?0 r7 Y/ Y8 k3 p1 `8 E& Y4 W}- V8 ~2 j8 C# ]! b! w3 `; Q
else5 O9 G- P/ P6 O8 h. y* ^
//如果子不支持IAccessible 接口9 X9 @0 B! X" {" V4 J  f
*paccChild = paccParent;
+ E0 m+ K/ d& z: Q9 x7 m9 b9 ~' h' h- ]1 V7 b
//跳过了有不可访问状态的元素
- ^# ~# r8 ~  L/ S$ t% x9 dGetObjectState(*paccChild,
4 T; s8 F9 W! ^5 e, C               &amp;varChild,
* X  @6 }3 }5 m+ h  v               szObjState, ! m- R% z3 T6 ~- D# i
               sizeof(szObjState));: ]4 ~9 x/ B4 y% N8 i4 U
if(NULL != strstr(szObjState, "unavailable"))6 K& h$ s+ c! B! i- P. o; P
{
* e. V: c0 Q6 ?6 a) g; [if(pCAcc)
* I$ K8 N; r; e. zpCAcc-&gt;Release();" ^' `1 ^) Y2 D4 N7 P
continue;
5 Y" X& q! z- O2 l3 z/ U; h}9 k- r: \1 e  d' Q/ L
//通过get_accName得到Name, Q: l6 L( e3 j% ~; Q% A" |7 j
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
/ d$ [$ O. i& X6 n: }* ~" r//通过get_accRole得到Role0 X. W( j6 E3 Q; \" N% ]* p% y) D3 f
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
: O" {. E* h6 S$ |/ }//通过WindowFromAccessibleObject和GetClassName得到Class
- s& N! Z: F( z6 o, Y5 PGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
: Y0 F8 R/ V8 k9 u//以上实现代码比较简单,大家自己看代码吧。
- G5 X: l+ U. Z9 Y' F" U4 o
8 L0 j1 e* ^% @1 d/ |9 P7 Y! G. \//如果这些参数与输入相符或输入为NULL
0 W) v+ g. a: Y! `$ fif ((!szName || * m" D4 N8 o3 }9 J9 F9 g: I# j8 \
     !strcmp(szName, szObjName)) &amp;&amp; , |* u( X7 k. t1 H: i/ Q; S
     (!szRole ||
3 s- M% _% O! R4 j6 S" U/ O# N* x      !strcmp(szRole, szObjRole)) &amp;&amp;
$ i' R. R' p9 J0 j9 F     (!szClass ||
/ B( _9 o0 C7 Z/ j7 X' p  \4 t      !strcmp(szClass, szObjClass)))* z% h# M9 J4 o+ g( G+ Z5 @
{  @% Q. Y7 k/ N" R. i  D
found = true;
) Y$ r% a  `' H' {! e: N) k*pvarChild = varChild;2 q: D; W9 L; S4 C5 P6 l) V' u
break;
1 Q; `* r8 N) Y" x! m}
. e$ ]* H9 l+ k3 Bif(!found &amp;&amp; pCAcc)) U: l4 A8 L$ v" u$ [. B
{2 B1 e* Y3 g# E% V! f& z2 H5 \
// 以这次得到的子接口为父递归调用( c' T" O; V+ F- y; T
found = FindChild(pCAcc,
* |* z' N- a7 P6 t7 {  @                  szName,
7 ^# x) b% n9 T1 P2 _                  szRole,
8 F* `/ l) H- }0 D: c, u                  szClass,
* w* t$ ~" y0 Q6 u5 ?                  paccChild,
- N8 F, v( G% @                  pvarChild);: D4 y( ^- _. s3 z! `! w
if(*paccChild != pCAcc)
6 n! b1 F$ b5 S8 [& ppCAcc-&gt;Release();
& V5 X# y3 e* E8 A}
$ g2 V, I9 q- M0 D% t}//End for
5 _3 C0 p) I, V5 ^+ o" p
1 {" j+ n. i+ `* G! }+ c) _// Clean up
4 M5 z9 L9 Q5 N; ?/ c! D8 qif(pEnum)% ]' ^" d* f- S3 `8 J3 {
pEnum -&gt; Release();/ P3 [# q  v; ~; z

8 k' S1 ]8 R8 p, @return found;
/ V9 ]5 ?& s* M* n' r( |}! L2 r5 s7 ^+ C

5 l2 k: W9 r5 H6 W7 U// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,$ F3 L7 r# F& ?. p1 l. {
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。4 M/ J/ g2 D9 b, ^8 W  r1 I0 N
//将这些或数转换成相应的用逗号分割的状态字符串。
0 z' [; U1 }/ rUINT GetObjectState(IAccessible* pacc,
& \4 N- N; _+ m# ?8 ^3 s/ ^                    VARIANT* pvarChild,
+ D9 M! h% |0 |. \; t                    LPTSTR lpszState,
! C# b% {3 k9 C9 y+ s# k8 \+ w                    UINT cchState)
8 V9 l" o. [/ `/ M9 _{
/ _9 }/ Y+ ?$ L) f* ]    HRESULT hr;" H2 a# k$ ]1 X( K+ T2 b* M7 d
    VARIANT varRetVal;! U5 B- @% Y( p" }0 t

& \5 I2 M+ j  ^5 {% t$ U    *lpszState = 0;
. N) g+ B5 W( H8 o
/ |: y$ w" R4 ^2 L    VariantInit(&amp;varRetVal);/ A  Q: M: j7 ?" o

3 P0 Y$ @0 i" t/ E6 {& f9 @! n4 v    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
1 v  ]* |" d5 ?2 C6 v! B
5 K  ?5 Y2 Z% l' ?7 bif (!SUCCEEDED(hr))
0 B. N6 V3 O! T        return(0);
% s" {! v$ m; `) t6 n. q& I: D/ v$ g* ]5 |, m# {1 O
DWORD dwStateBit;
+ b9 K2 T9 K* a- z; u+ M# H2 Mint cChars = 0;& `- g4 n9 H2 r4 g/ H. l5 A, M; ?
    if (varRetVal.vt == VT_I4)) Q: U  d9 p: r( T0 t/ ]0 I/ h
{
% {% [; Z8 O" J6 L7 s// 根据返回的状态值生成以逗号连接的字符串。3 x  H$ G' C3 Y3 K- D
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
) ?. U- m' J% |9 w3 c% K               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
3 g) G7 F! F% f3 N( t               dwStateBit &lt;&lt;= 1)& A: w* B4 n9 \* w9 p+ y
        {
2 J! R9 B: Z' T. m4 \" F- f/ e* F, t' ~            if (varRetVal.lVal &amp; dwStateBit)
7 q# T* `+ j# z8 U) V            {. c3 \0 R" G1 F* b; d+ ?/ s
                cChars += GetStateText(dwStateBit,
0 @6 R, F$ `* `2 {2 d' e" `                                       lpszState + cChars, ; r1 c! K3 s7 {2 O/ i2 p# T
                                       cchState - cChars);
, c8 u0 Z# l: B) w0 {9 ~) |*(lpszState + cChars++) = '','';2 l/ C2 H4 c7 I6 ?# Z: Q
            }
& K1 r7 U& c3 R# F1 ^& ^        }2 a8 j& x2 s: o7 R+ [6 X0 C
if(cChars &gt; 1)
( q4 H$ L) S9 L( W8 o( ?*(lpszState + cChars - 1) = ''\0'';& w9 ]& ?- T* [0 J0 K
    }
3 [+ [+ B( `1 q4 k2 ^, v    else if (varRetVal.vt == VT_BSTR)
8 v% I# L' y* S- L  s    {
! V) R. Y0 E0 p0 D/ }* p: x        WideCharToMultiByte(CP_ACP, 4 B9 A3 \# \0 p4 E& {
                            0, & P7 f( h  q: b% t' a) a: r
                            varRetVal.bstrVal, - x9 x1 L7 Z7 }1 d# g7 p4 F
                            -1,
) `" w' X0 [! c  s                            lpszState,
* B! L' z1 K9 ~1 f9 z                            cchState, / P. M2 P/ b6 L% Q; v
                            NULL, ) l' U5 Y  k( L# B
                            NULL);
' p' Y2 U, J2 x! ]& G9 }# z7 B    }; t( h/ w! T6 O7 F! p
  S" J# p* c' m3 C- o& m$ n1 U
    VariantClear(&amp;varRetVal);2 @  Y+ H* }, f0 |& P
6 @2 ~% x+ c; T% m. u7 s
    return(lstrlen(lpszState));
0 J# J' p8 G- [! a: s6 q- [- O, `}</FONT></TD></TR></TABLE></P>6 V+ v! h' M! F. d
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)+ t2 ]; A8 w* ]1 b- N* r

# I/ ^( `5 M' w4 i3 G. O  ~四、在 IAccessible 接口上执行动作
8 z# M; x- D% C/ N7 y2 y3 Q    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
) `' u  ]$ m# u, x    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
1 Z2 S- [: R# {- A+ `# U* v# U    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
; c  |' }2 @0 L. S<>( X3 J4 C: L, ?3 ?9 G0 y/ G- S
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) z9 x) `2 ]. H7 F

' w. c* ?* r* a<TR>% s7 o: @, d- J" S# U  s# f! h' k
<TD><FONT color=#00309c>//在文本输入框输入"regedit"2 n, J6 X  S& v3 M2 K
if(1 == FindChild (paccMainWindow, "打开(O):",
  v) A& J9 s: K( a2 I5 }0 L5 D                   "可编辑文字",
: U4 Y/ ^  v% ^+ q: Q3 H9 w                   "Edit",
3 t2 a6 I9 m7 A; d                   &amp;paccControl,
6 j, _3 f+ n: L1 N0 ]+ c                   &amp;varControl))# d: S: g! q" P" @' k% O
{
. e2 b8 k  f. T8 W6 T; |: ~% {) m0 q: h//在这里修改文本编辑框的值
2 F, z8 o. c2 X) Z0 j+ Ehr = paccControl-&gt;put_accValue(varControl, ! B2 b8 c: O: j0 d, i
                                  CComBSTR("regedit"));
& p8 V5 k" \+ P: T  y, Z. B! r7 [paccControl-&gt;Release();! [" ^8 x0 z2 K9 G5 E3 T
VariantClear(&amp;varControl);9 I* b8 Q- L- i& t
}
/ Y' I! Y& H  Y
) E; ]6 h  L) L3 K+ X7 P, U, E// 找到确定按钮,并执行默认动作。) {/ M) |! `: F2 j! [/ s7 ^
if(1 == FindChild (paccMainWindow, 2 N) N$ }' s& g# z
                   "确定",
6 O9 R3 @4 }! r, L, J1 c$ ^) P                   "按下按钮", ( i7 W. n6 ?, k6 X
                   "Button",
2 j2 O/ [! d5 h; I                   &amp;paccControl, 9 n: k8 c3 Z- g4 }" P
                   &amp;varControl))) g. C! w- u) s" \  _; O9 o1 ]( a& O
{
- G; y5 e* {+ e( g//这里执行按钮的默认动作,即"按下这个按钮"
' U! u( Y5 P3 h) l0 ?$ }hr = paccControl-&gt;accDoDefaultAction(varControl);
7 D' \& A7 I: W; ]( z/ apaccControl-&gt;Release();1 V2 i& \" p' E9 x7 s
VariantClear(&amp;varControl);6 x! |% N" y, T
}</FONT></TD></TR></TABLE></P>
5 F; b" q" V% U/ [& ~3 L<>现在,你会发现已经成功启动了注册表编辑器!!
$ N; o; G/ K! h9 b. A2 _</P>
' m1 d6 \7 Y6 E' J<>五、模拟键盘和鼠标输入
  ]( v7 t( r: k3 A    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。! d2 Y7 Y1 t  l7 J) m4 i
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。# E& [4 @8 ]" w. r6 b* d
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。$ f' N4 G* i; \. x& f0 B6 ~
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
6 }! e% S' Y" j<>
/ @* Z. y/ @. D<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
0 A" j) r0 E( Y1 \, v
8 r/ U$ ^6 L* X7 B5 d<TR>
1 v  @1 V( e+ F& H<TD><FONT color=#00309c>INPUT input[4]; : @' @( P# Y; t
memset(input, 0, sizeof(input));$ n, ~. q) v4 ~3 A* E6 |
: V7 h, }$ d' u3 [/ z: G) B
//设置模拟键盘输入1 b" n% @4 D( R8 ~
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;. U: }. S! s5 i
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
1 j2 O$ l- B- V7 ^2 ~( Pinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;  Z* T( \% I  e' k

! {6 x/ T+ Z- c// 释放按键,这非常重要
0 a0 a. a8 g0 ?input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
' x) K1 e& f( d2 q6 d- o9 J' `- d6 ?7 T8 U7 w. }( Y9 D
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
- }& u' s) K  K+ \$ x<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
$ c: k/ ]: s8 L$ J0 c0 R) W</P>
% D; C, p( B& o<>六、监视WinEvents% C7 `: D) e6 \- d( u3 N0 [2 g
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。" w" Z' E- s5 L& X4 O" e
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
1 d) `% m7 Z$ \& V0 C" v* R& j; p    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>' s( r% x% k7 H: L, E
<>
8 w% X* E* Z" j7 b1 C, _<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 ~1 j" |2 }* P% O7 v; M
/ R8 @+ |) m- v3 L0 u) @: G3 z<TR>6 o# [8 Z. F* f* O; b
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
3 \6 _1 z8 N# X4 T+ x+ z. v{
3 f/ S* [/ U% D! ?hEventHook = SetWinEventHook(3 S2 F, ^% U5 e( x
EVENT_MIN, // eventMin ID
# `4 P; ^0 F* f; |( ?EVENT_MAX, // eventMax ID
% x+ X1 W2 N6 c6 d% K/ X0 X. K9 wNULL, // always NULL for outprocess hook( A+ J& e5 ]/ @
WinCreateNotifyProc, // call back function6 N+ ~' _5 g& f; \/ z/ y, F
0, // idProcess" _8 D/ v) N* B6 Y
0, // idThread
2 s2 `$ B7 ^$ l. d. E# r! W         // always the same for outproc hook
* ?; t2 O+ b' Q$ U! z$ u) uWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
4 N0 O" V8 n  H1 x1 |$ J. j}  </FONT></TD></TR></TABLE></P>
7 S4 F* u& y% Y9 @, i& G) B
/ O. d) z# ~3 P2 C* J3 T8 N<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。$ z6 e# [$ q' {8 |$ B
下面是回调函数:</P>
! M, Z5 [2 g  d( I  q9 F7 L) P& [' T<>& V: M, H. n8 l. O5 a& p2 d
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
* Z" Z( _. X5 K* P" J% s5 e6 E- ^
! {! |$ O) q* Z# U3 q7 w; b<TR>6 S6 t5 L1 P9 b( I( m, |, [+ s
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(2 F) W8 J) L% [5 q, y/ ^/ O
HWINEVENTHOOK  hEvent,. \. w5 f6 b( Z  Y( `5 r. c# x
DWORD   event,
4 v" l) |) o" [( X8 T7 \- LHWND    hwndMsg,
; b$ i% X* }. c$ K: I9 u( LLONG    idObject,) X/ ?8 {+ u) z! b8 r6 h4 x4 `
LONG    idChild,' ?2 E  q9 J: Q% @
DWORD   idThread,) }5 `; L7 m3 x8 I- m( O2 E" N
DWORD   dwmsEventTime
- H" ^5 c, |; [' ^  {* F0 C5 C/ U)7 F& L$ B0 z* {& B! h
{
% Q$ C& _$ y6 }) v
! C$ f5 p: V$ {! ~$ ]& M, p% q& Yif( event != EVENT_OBJECT_CREATE)" O7 J9 O. F/ C
return;: M  x1 I7 f- R# i0 W# f# V3 M

( \) ?$ r# }' l, T, s3 Zchar bufferName[256];
1 P# M+ y  z% x! q' ~, X0 \# jIAccessible *pacc=NULL;
, |! r7 p9 K- Q1 A+ a% ?, AVARIANT varChild;
# u/ e/ e* P8 z) r5 N2 Q3 E    VariantInit(&amp;varChild);/ K7 d8 x( W4 Z0 y
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对: H* ]) T8 q, _# N# `& Y
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
( Z/ }; X! R. w4 ~7 Y                                      idObject,
8 ]. X; }. ^! T) w5 [" W8 o                                      idChild, 6 |% {9 ^% B5 Z
                                      &amp;pacc, # u7 Y+ {3 x- `
                                      &amp;varChild);
0 M) F: Q( o' m. Z$ ]2 O2 }& k* q% t  K8 Z
if(!SUCCEEDED(hr))
% F9 E# a$ t. g4 `9 O{& k0 `8 @( ]" K- g" R7 n5 ~
VariantClear(&amp;varChild);$ p6 R/ i% {: a( O
return;
3 P" w+ y* k" S, Q1 D+ k}+ H5 V/ a' s' S4 D. u7 T$ @; M
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
( n1 N" h4 e5 g0 d9 }0 S3 I$ ^GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));0 P4 _& f* ~) _8 |8 \6 q; X
if(strstr(bufferName, szMainTitle))
: x1 u. J9 |4 d, I2 ?. EPostThreadMessage(GetCurrentThreadId(),
9 C0 n9 ]4 l; Y, b5 r1 ]! z                  WM_TARGET_WINDOW_FOUND,
! C$ F; P" R$ G% S" f                  0,
. a, j* S( c! _  h( @4 G# N, d                  0);7 u0 Q9 H. \( `. X7 B* h

" N: p6 F( B3 Kreturn;
3 c) ^9 e! \+ ~; ^5 b  ?' [) m}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
  S; Y$ H1 A% |6 U; M8 m* P9 Z+ O0 [; z! }5 q/ X2 J- A: |. g
附录:
$ P. C* ?3 ?* T7 @5 T: D) w/ H0 L' _
关于IAccessible 接口/子ID对:
  f' P7 Q( o5 Q0 {2 X" k6 g    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
( D; s1 s/ q1 R- w  H; ]    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!6 |1 t$ {1 E, F1 D; u& u, e
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。% {4 b: _! O! d) f
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
' j/ ^+ k4 p( `1 |2 l+ F    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!) u3 r; i5 a8 s. F8 F
5 X, p5 j0 o; _. Z
注:
5 `% J4 L! h0 H  j    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
2 `' a2 O1 @5 m7 e' \$ Y    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。5 J3 _0 |- C- E5 F) M' U

" E9 L& R# `2 C0 Y* Z参考资料:
6 D4 }7 n2 Z- U7 p: M: @5 z<UL># o+ ~6 f8 _. s2 H' x5 C
<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 2 L: y0 V9 L5 q
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5