数学建模社区-数学中国

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

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0># T9 r9 ~, I7 L3 ~, O8 Y

0 d  [$ S. z. x. c: C* h<TR>
' T! g6 o' t; w( m' H# E<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>- M- g2 J1 s4 H- b3 M
原 作 者:崔传凯8 c- Y& g3 ~6 H7 i" _
原 出 处:vckbase5 r' I+ a& F& P$ p4 M# ^$ ]
发 布 者:loose_went4 W' Q: }8 a0 O9 F( m
发布类型:转载
0 `( s3 L4 h4 c  d) Q发布日期:2004-11-121 r- f+ _( E" j" [/ J5 K9 ~
今日浏览:6' U0 l' F5 R+ ?; ^9 t2 v
总 浏 览:234
1 z2 a; h6 o, q8 {$ r- T- z</TD>6 n5 x* Y$ t$ R$ l; i
<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE>- G) T# Y  X3 h& d8 e" S
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
- H4 R7 L) X' b9 L& H: {6 I1 R+ H, h# `% H
<TR>/ m3 m) r/ }- {+ \% r
<TD vAlign=top align=left bgColor=#ffffff>/ |3 x* N1 `6 Z: n6 Z2 {# F

: R5 M* E6 V$ _<>
" S. {! T7 p5 Q% p<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
  a6 E5 d  G2 T! Z6 v
- s; l! u$ k9 t5 t; y5 {2 A7 d<TR>
/ q* O$ k3 ?% H<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
6 n) x' _4 g2 G2 u, T. ?! tway accessibility aids work with applications running on Microsoft Windows?. It 3 j, U: m$ p0 j* _0 {  r
provides dynamic-link libraries that are incorporated into the operating system
% U" y7 d1 U. ^/ F0 Jas well as a COM interface and application programming elements that provide
2 @/ ?; Q; Q' C. q1 O% C' I# [$ V! lreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
' \7 p! y* |& e/ o: g<>一、基础" l* L& f( e( Z/ X$ s" J/ U
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。9 W! P, C  M, Q  X
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。1 M9 K" i7 j! \6 @5 i
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>4 {& ?. X/ j$ F& H# z; }6 ]
<>二、Active Accessibility 原理' D8 f& H2 f6 U+ L$ h
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。- M& f1 k7 m' }) Q# s% q( C$ v+ J9 ~% b
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。+ c$ ~! q4 g  D
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。! y7 u9 f5 r  U4 x+ z
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
8 X4 I4 d; r9 t    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
  r4 B1 d# l9 X/ B<>三、如何得到 IAccessible 接口指针, [6 E- ?7 B- @1 c
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
2 N$ {- D% J: g5 n7 B- N/ v    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。( k/ |; ]* \, O: s: T% X  i
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。7 K' `3 h  p; P$ v" R
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
5 G) d# ]) I9 X' h    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
) L# p5 p  A  H6 }) z    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。  O: G0 {' N) c. C
下面结合代码介绍一下它的用法。
' S! w2 K8 i0 u  x我们来得到下面运行窗口的 IAccessible 接口指针。
8 w! @* _. ?- S, a) y+ ]5 }, L2 d
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>* M# Q$ U' z. C
<>图一</P>
. _6 j9 w1 }' \+ M' |: E<>; n2 g$ U% V3 @' P
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
* X' y5 j) n# M9 I6 I
* p) i: S* h) ^( C/ G; p" c<TR>
$ \! B8 K' s% q$ {2 o) ~4 l<TD><FONT color=#00309c>HWND hWndMainWindow;
2 H  A8 n1 Z! p7 lIAccessible *paccMainWindow = NULL;' @# R9 {' Z& n8 u0 ^0 J, }
HRESULT hr;4 B" z: V# ~) \" w+ |+ r. y
//得到标题为"运行"的窗口的句柄/ i2 G; }, u0 q
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
, N' Y! y: X/ k" s+ E$ l{1 F! `  J$ `; f6 C# L
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);+ m1 K2 O: j) b' \5 ~, r$ ~
}" a. i7 B4 Q  F# P6 @9 E8 Q! h
else
- A8 d* H. f' G4 H" x4 s{
' @6 I. W3 W; k5 B2 @" T* I//通过窗口句柄得到窗口的 IAccessible 接口指针。
& \) ?* D1 `' k' C. t+ xif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
7 O1 T* ]3 A% q3 F  o/ R1 j/ `                                            OBJID_WINDOW,
, [8 |9 H8 Q6 C3 {0 S                                            IID_IAccessible,3 _+ ]# ?# ^9 _& z2 k) c* Z2 o  ~
                                            (void**)&amp;paccMainWindow)))4 M8 p9 M$ o0 E1 _
{2 |5 R8 j, a* `  {( ^. ^3 r
//……我们可以通过这个指针paccMainWindow进行操作。9 k* `6 `$ [  L* h$ Z3 a
paccMainWindow-&gt;Release();+ a/ q( Y! t. Q: m$ x
        }, Q9 y% h6 G! g4 P
}    </FONT></TD></TR></TABLE></P>
4 G* C# E) |5 B, f<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
! _! _4 L. S5 b( u: h" n& c# K    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……6 O! P" x$ Z( q* b* x1 h6 ^
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>) n+ u* ~0 m5 J: b
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>8 u/ K2 a1 {5 N& v* D) a' o
<>图二</P>
* b8 @( _; E% W2 }+ F, j<>我们现在主要关注的信息是:Name、Role、Window className。</P>
: Z2 [9 H. l) [( |<>3 j3 {2 v8 D5 x, v4 B7 P5 g* A2 ^
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 z! a2 x6 n/ H. O$ F3 k; k
% j4 W* G3 x0 f; Y7 d
<TR>% ]$ i. H2 K# Q) B
<TD><FONT color=#00309c>Name = "打开(O):"
7 Y; v; X" J4 ^+ nRole = "可编辑文字"
% o8 P2 |& G, N7 C9 b) p- |Window className = "Edit"  </FONT></TD></TR></TABLE></P>
7 f/ O% t  x3 G% i, K<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。1 w5 N5 a. l- ^# D7 d$ n  d" ~
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
6 ^$ S' A* f( c1 b/ g8 r下面我们开始取文本输入框的 IAccessible 接口指针。 </P>9 x7 e! z9 |7 o7 \% @  O
<>2 f. S2 O+ E6 U( a+ A) K
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& x: a0 C+ D) t# z' ?* \  x
9 D; U, [& u! c7 b, y
<TR>1 x+ b- L! I5 ]2 I
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
7 o) S  L8 @+ p( MVARIANT varControl;    //子ID。
6 ^! g' N; Q* |/ p: S+ [) @5 U8 d. }7 `% K  l, q" K! @
FindChild( paccMainWindow, 7 R: ~! k; S. H% ]2 a
           "打开(O):",
: f& o' I0 P. b7 _& S. g) \6 L           "可编辑文字",
( s4 ^4 J' {" b1 [" L9 M           "Edit",
2 l7 L; b8 u: L# L+ ~           &amp;paccControl,
- a$ `+ y; \' w: C0 ]4 Q           &amp;varControl ) </FONT></TD></TR></TABLE></P>, a6 \. s  U+ q6 h! y6 ]
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
/ M9 g0 O! p* B第二、三、四个参数分别是名字、角色、类。- E+ o1 Y4 X8 A9 f( p
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>7 U4 E; ?8 F# N* F6 P) ^# ?
<>
! K- o, ^( T' Y/ d& A. h) x3 h! G<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>6 }9 e% j: z6 e/ h1 r; f( X( M& S; B

8 T0 f' B6 j8 e. ?7 Q2 H<TR>
/ W$ R- J2 a% C<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
7 ~9 i( s7 y0 t/ s- x                         LPSTR szName, LPSTR szRole, - Q! O& u% L5 A6 M! Z6 U5 T- t2 X
                         LPSTR szClass,
! f0 f; O& D: U                         IAccessible** paccChild, 0 E  h# H* D1 E. ?% A6 u3 Z
                         VARIANT* pvarChild)
$ I2 m+ i9 t, e8 ~0 w{+ `7 ^: s3 X6 q4 f
HRESULT hr;0 X) a$ ?8 f1 _% W
long numChildren;6 f% ^, B; m# t9 _: \0 @
unsigned long numFetched;& f) ?3 k* B% i2 |  a! ?
VARIANT varChild;
: B$ g  i; m- u6 \5 vint index;
: _1 R# v( I! m# V4 Z8 F: r" pIAccessible* pCAcc = NULL;
$ j. w5 m& ^$ gIEnumVARIANT* pEnum = NULL;
0 f- ^; v1 u$ P: b7 gIDispatch* pDisp = NULL;
* i( _! O) z0 W  B+ ?BOOL found = false;
2 Z3 w3 x+ {6 l2 D; o8 Rchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
$ \# e1 p1 X  d- c; a
" L$ B' [0 A, @: h2 Q) R' {//得到父亲支持的IEnumVARIANT接口
0 G' `& ]5 h5 a, }# [hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);) k& s8 r) D' j8 {
# d9 y) j5 D3 k" D" |: r8 T" [+ d+ Z
if(pEnum)0 B: o0 E  _; C
pEnum -&gt; Reset();5 z$ v6 h) x7 a7 k9 Q% |
* S) `( l, H' O% j6 E- c8 _9 L
//取得父亲拥有的可访问的子的数目
$ _/ o6 I' E1 YpaccParent -&gt; get_accChildCount(&amp;numChildren);( ^5 F$ S, ^$ @% E; B9 B8 W
# r/ E; M# E/ W, l: X; q
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。4 ]8 Y' \2 t$ ?3 `0 }) C
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
. i" a$ N; ^5 l{; _6 D4 Q, E' n/ I; |
pCAcc = NULL; 0 ^; t( t9 H/ A- _+ y( N
// 如果支持IEnumVARIANT接口,得到下一个子ID" V  }+ U* a2 u. w1 |8 R
//以及其对应的 IDispatch 接口
& _% y% X$ A. }6 Z5 `# Z2 Tif (pEnum)
( J) x1 h8 v5 ihr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
4 Z6 a! O% P# @% r$ @else
- {* Z* I* N* P& I, x{8 G2 M% O  T8 r' p  C
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号* _; I# G, u( n% m7 I5 d* P
varChild.vt = VT_I4;
. W1 B  |1 X; W8 CvarChild.lVal = index;
9 G  R# ]. W! |$ b1 y- Y}
' f  j6 D) I! w: l# s3 W
: A" N5 @. Q; w, ^$ z* C. x" O// 找到此子ID对应的 IDispatch 接口) i+ Z. M& T( [1 Q! ?1 g. q
if (varChild.vt == VT_I4)$ J4 D+ O! V4 P: n- o
{
7 {. R6 G/ c( ?0 L; @$ c//通过子ID序号得到对应的 IDispatch 接口
- p& L+ C- t+ ipDisp = NULL;
3 j2 l* y- d# U1 Z0 Shr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);6 x9 Y* F/ u# E1 {* b5 e/ B  g; g
}
+ _* z) `' @3 @( relse, R+ S* Y; c$ P) W- Z- S% |- Y
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口5 f; V- Y. R! L  l
pDisp = varChild.pdispVal;$ {7 Y7 v2 N% f# q7 [! p: V
- c; I: j+ J' E- Q
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
8 m$ a- E* Y& r2 W% G0 j! A. G! [3 y/ [if (pDisp)$ d# R( d4 U7 q/ }% e' d
{
( {: [- C* t% x1 V8 T+ p# B0 g. Rhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
, N) p- s  q: ]$ s2 d& Nhr = pDisp-&gt;Release();4 I* ~  y0 Z* ?* h5 H8 p4 R
}
# W0 f' A; X' h7 r" Q) ^" m# ]$ S1 `/ S
// Get information about the child$ E8 T" N( X' k1 l/ z. j8 I
if(pCAcc)
; U% u5 o" }& e- z{
1 Z2 Z/ D0 O7 r9 I% i5 Z" B& `//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
/ t  A+ q) I  Y5 v3 q' x$ R" UVariantInit(&amp;varChild);
% `7 A( N/ O) r1 m- Q$ u1 t: Y" ovarChild.vt = VT_I4;
0 f: D, x% m8 G6 B, k  A6 lvarChild.lVal = CHILDID_SELF;
9 p2 s2 ~& g8 G) m$ z5 z+ P+ Q2 [/ h$ N* r
*paccChild = pCAcc;" I" x. A0 Z. Y$ c, ^% ^7 X1 R
}
5 d: N. N: I7 Pelse
% d% b( c! r5 l- ^: ?//如果子不支持IAccessible 接口
6 F; f2 U. ]+ ^" j6 }9 S2 G( W*paccChild = paccParent;. C* i' ?# c  J9 D0 h  P
( n( t6 E( [4 j) F# H; m1 T% f
//跳过了有不可访问状态的元素& T- p; ^9 k% h3 K% E
GetObjectState(*paccChild,
* z0 W4 A  L# G; l) \, L$ L! b               &amp;varChild,
5 z- E- p  }1 @: E( ]& T               szObjState,
) Y9 S. f3 B' r9 n) L               sizeof(szObjState));# x, r0 ?/ D& H! L8 S2 k: o
if(NULL != strstr(szObjState, "unavailable"))
3 }' S! Y+ i9 U9 U{+ C0 w7 c4 w+ ]$ \
if(pCAcc)
1 a% Y, a/ f$ P8 R+ {. }8 Q5 k& @4 lpCAcc-&gt;Release();6 I. o( u, v' c6 F: Z
continue;
% y& J# _3 G% {( g2 B0 o: S  ~: H}! E/ r* K, v. q7 @8 I6 ], ?4 \" _. r7 q) z
//通过get_accName得到Name: e, G0 T/ [) i8 \
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
! m# Y& r% n% Z  T//通过get_accRole得到Role2 }; K5 R) U. Z) S
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
9 Y( y0 h( B8 d# N4 k: P//通过WindowFromAccessibleObject和GetClassName得到Class9 y* I% I2 `4 C4 f
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));, P/ t$ ]$ w8 _  j
//以上实现代码比较简单,大家自己看代码吧。
: G' o1 _# L: K' v. p; b: t3 I7 z
3 M* U* C* G- i//如果这些参数与输入相符或输入为NULL- Q: o( H; f! u( U3 `  A: M1 u
if ((!szName ||
' h9 g; b  C  v2 O  M- E" `     !strcmp(szName, szObjName)) &amp;&amp; + ?5 L! i7 b! {  `# k
     (!szRole || % f. ~6 j& }. V9 t8 v
      !strcmp(szRole, szObjRole)) &amp;&amp; # d. \" H  t# }2 O" ^9 S8 c5 S
     (!szClass ||
1 n8 U# P% U$ X/ D' I4 ]+ m* O2 g9 S      !strcmp(szClass, szObjClass))), \% e4 T( @7 p& M) O0 |) Z( F
{  {( a, j2 ]% S5 x
found = true;
$ B% w# H! F) x: I  m- L3 w*pvarChild = varChild;1 I- P4 I, ~# K8 h4 v
break;! s5 M3 |3 |. I6 w, _
}
: F3 Z2 D/ u7 I  k% k7 q! K! ]if(!found &amp;&amp; pCAcc)
7 n2 W, v% [" m4 _( s{& E# _! w, j/ M
// 以这次得到的子接口为父递归调用
* O6 _" t; c4 l* {$ v. lfound = FindChild(pCAcc, 9 S* S1 J4 R3 h4 ~6 O$ F5 c
                  szName,
$ K$ I1 D! h' a4 q1 P8 c$ \! z                  szRole, * z# n  A2 _+ ~( L& @2 u# z4 l" h
                  szClass, 9 B- q/ e+ Q. |8 ]) B$ t8 Z
                  paccChild,
; I9 [. D" D2 {& Z4 b# Y) s                  pvarChild);- a1 ], Y9 l3 Y0 [# `! o
if(*paccChild != pCAcc)6 [7 u; A& {0 ], `: z7 ?# L
pCAcc-&gt;Release();
  F+ F3 k: Z  l7 V& a}
; h# R$ O; b% X" O+ [; |2 z}//End for
* E/ K& R1 R5 S9 [) J  u) Q3 o
% s! ~1 ^1 H5 ]" r2 ^& S8 Y( r// Clean up
, ?6 ]7 ?- c% M3 jif(pEnum)" G3 ?( m4 |3 j1 n
pEnum -&gt; Release();
# D8 }5 N% L3 H6 d
' ?) g: r$ t. `/ |7 i; @: I$ creturn found;
+ A8 T; s4 [% W7 v: M& R6 P}, Z& l% G+ _9 t) f# w5 Q* f; F

. u" m; {+ A  }: }. r9 x// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
" K. H8 r0 Z7 i- s2 `/ A* a+ u, |" V//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
1 y9 V3 t: k7 H) G: ], B//将这些或数转换成相应的用逗号分割的状态字符串。3 z# \) H! c8 S, Z. R
UINT GetObjectState(IAccessible* pacc,
1 \/ P* j- K  ]1 [7 l                    VARIANT* pvarChild,
$ D1 E! W! f- G1 {6 }' R                    LPTSTR lpszState, ( a. s; @$ C6 j
                    UINT cchState)
, W# x0 H: {8 z* U1 u# z6 ^: v8 T{  X1 \9 B" t3 H9 m
    HRESULT hr;
# S4 t3 D, B8 y$ O    VARIANT varRetVal;
$ f( Z8 B' }' U: v" n. r1 k
4 f% ^9 w' C9 h; q8 C    *lpszState = 0;6 X6 w' V6 x! o& [6 _, e5 {5 \) ^, K
. W4 j: ]6 C/ K1 @7 ^! c3 u
    VariantInit(&amp;varRetVal);2 e7 F) W4 k" ~0 ]- H# l, v

' j% T" L& ^1 Y& `3 \    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);% _" I& I; I3 v( L9 [) [% a$ z
3 d6 S) ^. L, [# Q8 u
if (!SUCCEEDED(hr))3 U7 y0 N2 E8 K2 P% p
        return(0);0 v+ m) d# p5 `. I  T" j- ?7 M
$ I. q( c2 b' e$ V9 \6 k( n+ @
DWORD dwStateBit;
, \' `2 v& a8 j! M' M8 k1 r& T" G3 yint cChars = 0;% A0 u( I$ \# L6 p
    if (varRetVal.vt == VT_I4)
5 L* w% g7 o! t' L1 f+ ~{+ e: H. i" t1 L6 q% n0 e, i+ g
// 根据返回的状态值生成以逗号连接的字符串。
1 w, y7 a. g" l0 j2 q        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
$ v1 A* a7 P# H: ~2 u! {               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; 1 a! N$ C' T7 o) {4 }0 o
               dwStateBit &lt;&lt;= 1)
0 P! l8 L$ b; w3 d# o2 D0 U( b        {$ R% L: \/ K  ]5 t2 `# M
            if (varRetVal.lVal &amp; dwStateBit)
3 B. r, u4 E$ B$ L7 v3 C8 q9 N. v            {9 e3 T4 W" t( I/ K
                cChars += GetStateText(dwStateBit, " K3 {9 O- O" l* y& O( e# S$ M
                                       lpszState + cChars, 0 j. F- w3 v% n1 Q
                                       cchState - cChars);) G5 _$ \0 E5 j; r
*(lpszState + cChars++) = '','';) m/ U5 G! t! [! B* u) N
            }
: s3 S# v/ D- F$ s        }( C: C& T5 h8 L, V: B
if(cChars &gt; 1)6 p. V2 t' P7 t- d- G: S  S  i0 H8 A
*(lpszState + cChars - 1) = ''\0'';: x# G9 T+ t, P& y  ]9 ^7 G
    }  N* c! `3 g) d* v4 f
    else if (varRetVal.vt == VT_BSTR): {3 `; v% ], T4 K/ |, D$ r, ]+ t8 @1 G
    {2 t4 }3 z/ A9 }$ \. D
        WideCharToMultiByte(CP_ACP, " q& Q- j- K& z$ Y1 u
                            0, $ Y" d9 l* o8 p% q' p  g3 ?1 ~
                            varRetVal.bstrVal, 7 Q! r, q) g* n  X( K
                            -1,
& m! X4 B4 k( y+ h3 G0 C                            lpszState,
" |# t, ^9 M) \' Q+ B5 T                            cchState,   b3 o7 a  U  ]2 a/ R
                            NULL, " i0 j7 T6 A6 O0 C' E
                            NULL);  O& m3 h0 |) ?4 M: n5 L9 R4 G
    }$ O% g% K; L' @5 ]7 b/ P  X* J
" ~& v0 m1 B# ?: k" k9 M- K
    VariantClear(&amp;varRetVal);+ q. {2 m$ l; p' h, K! l8 W! y& _! N
6 y  U3 P* Q/ ^3 r# D
    return(lstrlen(lpszState));- }' I: V! v2 o1 n- R& z$ @
}</FONT></TD></TR></TABLE></P>
0 |, ?! @4 f- y" O. @* A<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
  L5 m, }" o4 g* A3 `6 U
4 S, x, d3 t# M# w四、在 IAccessible 接口上执行动作  X! B+ H1 ^/ \9 Q4 T, \9 M- J
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。# t" ]; g  [$ m0 k% d; I" d8 I
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。4 o+ n" }  G5 ^4 C
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
* u+ d& u$ _- @7 L<>
3 P; Z6 R/ ^2 V+ @/ J<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
% B  B$ W3 b. ?" `  q: G% u* w
  N9 p. v$ @& ^* x<TR>: k- x! b1 T8 K, I1 _+ w
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
' H% b2 p1 k4 o. Q" k4 q  fif(1 == FindChild (paccMainWindow, "打开(O):",
' P& L. d( R; Z4 D+ d" N1 a1 q                   "可编辑文字",
7 \* w, Z$ m# o2 n7 M                   "Edit", 6 N/ Q2 a* o; l6 q; C) g( \
                   &amp;paccControl,   m* y+ [6 y( \4 j% `5 Y
                   &amp;varControl))
  d# w# a8 n) W' D! q% |4 x7 P# }9 P{3 M2 q1 D3 n8 C/ u
//在这里修改文本编辑框的值9 D5 P: s9 J' a9 h- U; g
hr = paccControl-&gt;put_accValue(varControl, 2 P9 I. w2 F) a2 c9 w
                                  CComBSTR("regedit"));3 F! `2 d; E. D! g9 K
paccControl-&gt;Release();( y# ?4 I  L0 ~% f! ^
VariantClear(&amp;varControl);1 X) T: X2 E. |; [, s
}
4 ^! S1 a7 ~, Z. a: ^3 v+ P& w* W% h4 v  z# g! A- N
// 找到确定按钮,并执行默认动作。
) j0 l( f1 X9 E% J+ B) `if(1 == FindChild (paccMainWindow, ' m* V8 t9 Z8 A% [8 _/ F
                   "确定", . a' u7 e' |* \( N" w& ^8 p
                   "按下按钮", ; _8 |* v& E: i% z% x! Y
                   "Button",
/ {6 p, |0 n- l% \. E* N) e                   &amp;paccControl, 0 c1 D- f; g  m) l, j  S2 P9 v, u% ]$ u
                   &amp;varControl))( _9 b' X1 h0 k6 l
{
$ H7 |# R* m: W$ Y3 h: a. H; D//这里执行按钮的默认动作,即"按下这个按钮"  n9 k+ D+ G, {/ Y; G
hr = paccControl-&gt;accDoDefaultAction(varControl);4 b1 U% Y1 O  x8 C2 y
paccControl-&gt;Release();
: r& t! B6 e* Q7 {# Y; d  ZVariantClear(&amp;varControl);
- O) q2 Q8 h# O. c1 z2 V6 Z8 @}</FONT></TD></TR></TABLE></P>
" r. Z3 ~: C$ [. z3 S  ]<>现在,你会发现已经成功启动了注册表编辑器!!
, T) t* r% {  z% A</P>9 s9 r/ a7 d  F2 t8 V3 K) B
<>五、模拟键盘和鼠标输入
  \- w% c+ P* q6 h9 q% Y. ~    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
0 A  {) H( d/ D$ S: C. b    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。! i6 q1 W4 Z! O' Y1 @
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
# \4 t) i' l% C9 O: K下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>' z7 C5 E( H% P& x$ ]! v# v
<>
1 O4 W9 m% d3 Y0 p7 X, c$ ]) V<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
) O' @& c9 c+ I, r/ C  L% l: i# N' _: R* g% M3 W, r3 r8 a9 X$ k
<TR># n0 C3 w/ ^- A7 [" e
<TD><FONT color=#00309c>INPUT input[4]; 9 m( Z% D) |; @' Y" G  |5 a! I: `5 X
memset(input, 0, sizeof(input));4 i' s  f" K% Z7 j9 J9 n7 Z7 A
  n7 \& v% U& o' G# c! A4 [
//设置模拟键盘输入
7 S* m) l8 c. s/ jinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
0 ^8 o- M  k  n) B/ c# Dinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
% S" F& l7 P/ w/ T7 T: q' Vinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
6 m; C: ]. _" Y6 g
. e% T  g& F+ d6 W$ s/ A: m9 v. k// 释放按键,这非常重要% f* T, l6 G) G" y, O
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
. k3 ~  q) \  s" z6 i& F* r6 P( E0 A2 d# m' {
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>: A) O) ?% c; U! o$ o
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)" @8 P- h& \2 q+ J' h, H( c
</P>2 q) R, v  O$ g7 Z
<>六、监视WinEvents
* o( H' J6 p$ U- ]: w2 U    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
  E! D' k' M0 T! l( [7 l% [. R    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
- S. q  D5 _% F' X8 R    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
* H; P! z, @7 |3 g# x<>
" L7 n/ _# G0 n) C4 `) z4 U. L<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ ^3 O2 X! C- f

3 \" H$ K. M$ m: [6 z<TR>: r# G8 W  s. k, r
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
' \6 v% u% |( F{6 o7 V8 E4 a( m, R
hEventHook = SetWinEventHook(' j0 W. h) h$ ^4 j2 M% U
EVENT_MIN, // eventMin ID
: {4 J2 j0 i# kEVENT_MAX, // eventMax ID
7 H& S$ ~. W0 t7 {; SNULL, // always NULL for outprocess hook
- U' M- f( Z5 b; _9 w  ?WinCreateNotifyProc, // call back function# d8 @3 Q* e) }1 F% n# I
0, // idProcess) I# {# B7 i6 c  r2 E
0, // idThread
, }; Y9 j2 I( _" O7 I( M         // always the same for outproc hook
+ ?! ^3 Y! R7 C( B: t- \5 OWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);4 s) S* _+ c/ \
}  </FONT></TD></TR></TABLE></P>. y* \5 t5 j! G% b/ y1 k
0 w* w  ~; A+ O- J8 p0 _7 j. h
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
7 M7 Q1 |. j, ?. {下面是回调函数:</P>) R) l# L' j9 u
<>
- C. C4 d) x" R% \1 T$ @9 ^5 a% l<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 L( _0 A2 o$ O: [7 ~
% `* I7 A$ J3 |, j<TR>
6 E8 L# _2 s' p7 L6 B<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
9 m3 {/ d& g& S' W5 G) b7 c0 sHWINEVENTHOOK  hEvent,
8 f3 r" e  u3 N! X+ D$ H) ]% xDWORD   event,
+ i  U, j: z; g7 q1 s, k4 c9 y1 P* _+ dHWND    hwndMsg,
$ q! `! m/ c& n5 o9 j" N2 PLONG    idObject,
' n9 b4 E/ g( ^: OLONG    idChild,
6 g! H; r2 S- M( q$ XDWORD   idThread,
* b0 W7 S) |& C3 d% T- @3 \2 ODWORD   dwmsEventTime- ]) J* C* W' b# F
)
0 I$ g' P0 {+ v{
0 W9 E+ H/ M- @" W9 z+ w
( u1 l. w) `- v6 p3 i% nif( event != EVENT_OBJECT_CREATE)
" i. C, v5 X( `. qreturn;
, H/ T7 f# m7 R, [) a4 V  O0 l+ h
char bufferName[256];2 z: p% o6 C" t% x7 p
IAccessible *pacc=NULL;
- O2 A* y. p" B6 y# y- e% MVARIANT varChild;# Q, L, @9 q" J7 q3 I, ]! H
    VariantInit(&amp;varChild);
$ _, R1 t  H) k//得到触发事件的 UI 元素的 IAccessible 接口/子ID对  ]1 y0 \) j1 Z+ w8 N
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
" K9 E1 f) s$ J6 c5 N                                      idObject,
: l; H& o* r; N) q: M' P                                      idChild, ! S6 {7 @& C, M' a
                                      &amp;pacc,
4 [$ p1 _" _! S2 ^' c+ T- \                                      &amp;varChild);
! K/ [# I/ \* [5 o0 Y' k8 e
) v5 C7 g$ L1 X2 Nif(!SUCCEEDED(hr))
8 C5 Z1 L3 R0 k1 c: o) M5 e  r9 \{8 [7 n" G. g+ B. ]2 c. H- i3 z
VariantClear(&amp;varChild);8 u8 n  e6 V; O+ a% i
return;
4 G7 ~" s! [' V: |1 ^}' B  X( c; r- }% ^5 U, b
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。1 b8 K* R; ?& ~
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
* A7 l  T1 X9 F; i0 i2 r& f2 tif(strstr(bufferName, szMainTitle))( M: {8 Y& X& z  _
PostThreadMessage(GetCurrentThreadId(),   R/ D! j' }5 L) u: ?
                  WM_TARGET_WINDOW_FOUND, : c% _' V- O9 j5 I& V/ z
                  0,
) R2 O8 H4 r- \                  0);
6 H6 ^# C$ _5 L5 {  E* ]7 i7 A
& M2 d0 o) ^% g, {return;
% D0 |0 J  ?8 ~+ e' C) o}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。0 A0 E6 {" r5 a  B' X, I

7 q! g; s0 I  u* x0 e! O) t7 K! C附录:
  y1 O) g; i! o/ ]# ?* E* X& ^( p/ J7 R+ x& k8 r: U+ G0 n
关于IAccessible 接口/子ID对:
" @5 L+ p9 ^! }! F% k! }) X( g    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
  U' `' z( s$ r+ q& S    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
, i% y' V  X) w* u) D    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。- D- n3 G% n0 h7 e- z/ [: t
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
: o0 R" U" L# q    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
/ \8 ~. J* z2 U6 w0 w6 [/ u
/ \0 N% `8 B* P& u3 _( ~注:
, K# U7 a" W& D( P    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
) w+ p6 d( ], I! M* C$ V    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。7 H) O( `! u7 d* x$ @+ v

1 t9 p% b0 ?& m& q2 W" c8 ~6 e参考资料: 8 K8 {+ \( n0 ?- e6 u( b# w
<UL>
( `! P4 Z' M: h8 A% M<LI>1、 Dmitri Klementiev写的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》及其源程序。http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx ( W5 T" R7 O2 r3 F
<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




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