数学建模社区-数学中国

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

作者: 韩冰    时间: 2004-11-21 15:21
标题: MS Active Accessibility 接口技术编程尝试
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>. L8 h  O- P9 I- [
0 x% k% L% ^- O, L0 E0 {
<TR>
) b4 P- w" y6 ~) F<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>8 G9 s! W0 Y7 v
原 作 者:崔传凯
* Z7 Q( L) Q+ X, |1 B& i原 出 处:vckbase
* J# X! I9 h. `8 d, N: q发 布 者:loose_went: ~0 }) X, f0 r# @& S+ i1 D
发布类型:转载
& z) t: P1 a2 s' i0 f4 k9 {& j发布日期:2004-11-12% ?3 S$ ~" k4 ~
今日浏览:6
+ _2 l  M2 X7 X  e9 ~! ~: Z总 浏 览:234  t; o) g4 Z; a# _! V5 q9 @
</TD>
3 p% H- w1 a8 [% T6 Z& u<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>
3 ?  ~8 d& R, l$ r" c<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
$ X7 d- r' K/ o* x0 I  }
, p# @0 J% |* _0 y+ ~! |  ~/ D<TR>) j  l3 q- P( U( B. `
<TD vAlign=top align=left bgColor=#ffffff>
# E# z1 A1 v  m
: u2 W* y* q6 _" p" P<>: K3 N+ c$ T% N, V4 a
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 ?  }" D) g5 }  y8 j: R8 c
$ o; E% O% u1 E( z/ e  E" D, _<TR>
5 L7 L3 t4 B( A<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the + o2 @( \# I$ H- C" x& B
way accessibility aids work with applications running on Microsoft Windows?. It
# E6 f) M9 {4 }( v% H3 O, T3 Qprovides dynamic-link libraries that are incorporated into the operating system 6 n! A/ B! B) Q7 H( m* d( @8 }, j
as well as a COM interface and application programming elements that provide
- X" R& Y% `! J6 treliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
/ x6 ]$ ]3 w! \<>一、基础$ e$ U7 K0 Z) Y1 A! r
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
3 U# O: p. X/ D    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
1 n" t5 _$ k4 X# @' G2 ]( X* Z    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>" |2 K: S' E# V  q  u7 j
<>二、Active Accessibility 原理8 o6 V# e1 |! R7 G# E3 O" a$ I: e2 j
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。& d/ u4 U/ d; j
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。' }, ~  l# o$ T, v" N
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
, t. z+ c. f; X    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
1 e* M% F3 n; J8 P; n3 w" t: j    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
% ?# `  \2 f0 R) }( S7 t- @7 O<>三、如何得到 IAccessible 接口指针3 l) O' f7 N  G- z
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。5 Q+ s3 M1 l% u
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
6 |$ _4 g8 x0 F3 g    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。+ q+ Z4 S! t- m% E" @  q: J/ i$ C8 p& [
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
+ X  F4 q+ Y- m4 V+ z( ?6 ^    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。" y" F1 i, e9 E
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
4 }" V. O4 l% @# a- J1 R下面结合代码介绍一下它的用法。/ v) [% A+ ]  W! o, O9 R2 j) X, J
我们来得到下面运行窗口的 IAccessible 接口指针。! \' [- a2 {% [' ]7 e

/ k" q( X1 T6 Z* s, |$ S; o1 @<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
& R# {9 A1 A$ t7 D  a<>图一</P>: t& E: z; b5 k: @; _- m; ]
<>
1 z6 W* A% M0 E; r1 m! n+ o<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
! F3 i: b) Z' z/ F6 J, v9 {- [0 x/ c( Y" d3 ?- H; y% Q
<TR>8 E$ x2 A3 K+ L) y" M: Z
<TD><FONT color=#00309c>HWND hWndMainWindow;
5 K; K3 h/ y3 m, r* y( F! a" IIAccessible *paccMainWindow = NULL;/ P( z" @) @, o9 i& z
HRESULT hr;
9 W9 e$ A0 t# t' m2 d! o8 ^$ @//得到标题为"运行"的窗口的句柄0 M2 n$ F& r$ x  a6 h
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))& P3 v' U, G, s& s; r* Q
{* j) }4 ~" d5 E' X! c0 n
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);5 ]- e: ?3 j; ~& C3 q
}+ S! ?) }3 B  i- T4 f
else
0 u/ \, J4 P, d! F5 l{
; X7 j7 M  u) e' T& _3 f//通过窗口句柄得到窗口的 IAccessible 接口指针。
- F. d  H+ P, T4 b7 w; `+ qif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, - v: G) L5 A; e& Z9 G- Y/ g  l
                                            OBJID_WINDOW,
7 Q2 ^5 `1 X: H  l/ F# W, w                                            IID_IAccessible,
$ @" K. d% v! g9 h  w! W                                            (void**)&amp;paccMainWindow))); a# k* C2 c1 }
{& J! k% {( h7 O3 F- S
//……我们可以通过这个指针paccMainWindow进行操作。- [, U5 e3 H' T! y
paccMainWindow-&gt;Release();
+ R4 D; O+ z4 t7 L$ K( M        }
, ]4 c% T8 E; W/ E# f# N}    </FONT></TD></TR></TABLE></P>6 a$ a! u: x1 A' v
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
9 a# M# \9 {- D! w* s, }6 [  T! ?    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……9 F8 j' K* m7 ]# z/ E6 a3 y
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
8 O/ x! s  q/ {4 f: ~$ C* m<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
: m  ?" T- I$ W4 w<>图二</P>
: ]- [; w' L: ^$ q+ C6 V1 k- ]<>我们现在主要关注的信息是:Name、Role、Window className。</P>
/ g) k8 D( ]8 S  ~<>
" l" z; f% T+ R4 M1 T8 v' M<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
, G* z" ~! C6 _4 N6 c8 P9 J, p% o+ ~2 \
<TR>
) Z/ Q- v; ]5 p4 j, K<TD><FONT color=#00309c>Name = "打开(O):", l( s, C; K7 h3 |& _3 H6 m2 I9 ], n
Role = "可编辑文字"9 l3 k: v/ E0 \
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
' R* D7 _' J5 k7 Q- Q<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。  u: ?9 z) m/ R" I+ U+ }7 ?- E
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
9 C: a& k( d% C2 S  g, w2 W) a下面我们开始取文本输入框的 IAccessible 接口指针。 </P>0 m% U6 ~+ ^. V
<>
) j$ p) T5 l; J2 Z( ]<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>3 v. e/ _) g3 |& u( Q/ G
/ t  q2 J+ e& a4 y4 t4 v5 B" W, d
<TR>+ y1 }" T/ T9 ?# z, s
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口6 M  y& W  J' ]& t
VARIANT varControl;    //子ID。
% F' |3 x1 j3 J5 u9 n; N6 i8 q3 n  K
. ~0 @# r8 F9 R) FFindChild( paccMainWindow, ( U1 A$ B+ [0 f( b5 [' H& N& }
           "打开(O):",
8 e* `9 s- [& h) y! F           "可编辑文字",
5 u, ^4 B2 u# J1 s7 x& @( _           "Edit",   s. j3 A5 m, d5 A* o" @
           &amp;paccControl,
% o/ @* J* U& C           &amp;varControl ) </FONT></TD></TR></TABLE></P>0 ?3 E- j& }" n
<>第一个参数是先前得到的窗口 IAccessible 接口指针。' z2 p7 r! w0 F# R! u' L: Q' h+ L9 [& P
第二、三、四个参数分别是名字、角色、类。; g6 n2 e7 e) e$ _0 O3 y2 q0 X
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
, r  l' q/ r2 P8 L" S5 X<>3 ?, s( W6 \, t2 t; m' m
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; }* F1 c% B* M+ I& v. w4 f( _0 J6 K* y7 ~3 `
<TR>! Q+ C2 @' w$ E8 F7 h1 K$ S, m8 W# t
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, ) U; P. q0 {- Q  I0 C1 [" L
                         LPSTR szName, LPSTR szRole,
' a9 O+ C* m2 V* Z. ]# s9 a                         LPSTR szClass, . C+ W0 h4 {/ D8 ]) L0 s
                         IAccessible** paccChild, 3 {7 w9 y# w! h& b; D( _. D1 {& P
                         VARIANT* pvarChild)2 l2 M3 P6 `* e
{
4 M+ ?( }! \) E- K2 _9 Q# qHRESULT hr;
6 V& ]" m# D% Ylong numChildren;
# {  _4 f' C0 i3 z) y, k, Xunsigned long numFetched;
' f/ v* N; k- QVARIANT varChild;1 \5 z; H  o* M2 A
int index;% g7 q6 Y0 H& Y" s
IAccessible* pCAcc = NULL;
+ `7 z! c& x, H4 P, u: L% c+ tIEnumVARIANT* pEnum = NULL;
% v  c( a7 b  QIDispatch* pDisp = NULL;
" z5 |9 o4 U! ~! p' K* wBOOL found = false;
& F; a) |+ W& r  r. ichar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];! D. R& |3 {% w# p

, ?, \4 |& k0 ]6 e//得到父亲支持的IEnumVARIANT接口6 t4 x% M: k# C- i8 [! m! B& H
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);; L% E, G# n- x
7 s' w" B8 N( D, _6 h# h
if(pEnum)% s: R# |( v# v5 D1 `; G5 h
pEnum -&gt; Reset();
1 A7 m4 K  o3 \9 D3 b4 v
& V" u( i8 _9 }3 D+ Q8 y//取得父亲拥有的可访问的子的数目
- }' u! k7 U- R) vpaccParent -&gt; get_accChildCount(&amp;numChildren);0 a' c8 F- a3 c

3 r* r  P5 i9 o7 N# T: G//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
0 J3 P" k0 j: }3 Dfor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)& ]* o. `9 e* h. ~% R8 t
{% q9 g. I/ u+ [
pCAcc = NULL; ) G( `/ d; P& j% Z& {: T7 x
// 如果支持IEnumVARIANT接口,得到下一个子ID& `, T+ k& @3 i7 V6 q% m, Q
//以及其对应的 IDispatch 接口
4 f. P; f2 U6 p: X; x0 Fif (pEnum)
$ H+ u! S& ^7 ]/ uhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); . _) G5 F" D( f2 |# S, L* b& o
else
2 D" n' J' ~5 p& R; g7 G; C{
+ q& A! S3 J$ J//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
- ]# _/ ]: T7 z/ q. y4 d) cvarChild.vt = VT_I4;
0 B, [& L$ ^) m9 q$ `' T4 O9 m/ EvarChild.lVal = index;
' b  @$ W& j; [% E3 o  |" k% e" a}
* z8 G  J) k$ ]* f: U) `' `8 S2 T2 K4 L: o3 |' ^8 O
// 找到此子ID对应的 IDispatch 接口9 n" W' p$ Y$ |5 Y& u
if (varChild.vt == VT_I4)
. a0 Z: q, {6 N5 X# _- [% y9 m{
+ c2 y' p! ^, {//通过子ID序号得到对应的 IDispatch 接口$ z' {% ~2 w! V; I8 n* i# o) s/ M
pDisp = NULL;* X9 r9 T* ^- r  K; e: F
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);/ q& T, f$ S( b7 C2 Y
}
$ U9 F' l5 z( _$ w: Z( b, Nelse
) h$ l2 G  I2 j; }5 U8 h  c//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口% i2 A% a+ c1 w8 Z- P8 }3 }2 B9 {
pDisp = varChild.pdispVal;
9 e) w' K- @0 f, \% W2 h: p8 W; i
! V. y' X' ^2 @$ @// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
. c' v9 a" b% q7 Q5 @) c& Zif (pDisp)( ^% Z' h/ u8 ~7 L( K
{
6 l! O" r+ u1 [. ?+ y7 jhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
9 V% U1 I- `* A* X. x  I1 [9 shr = pDisp-&gt;Release();' C6 h, `7 U. D% G) g
}
- G- J; g4 R( r  F; L& H6 X& p. D/ @9 ]9 g
// Get information about the child
* l/ ]0 _4 w, \6 b! ^/ V$ s) `if(pCAcc)
) e4 O# `3 `5 |6 z{
+ o7 @" B2 b- R8 W4 g" _% c//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF' ~- }$ R0 Y8 Y( i
VariantInit(&amp;varChild);
. f! v9 [# g& [; W8 KvarChild.vt = VT_I4;
9 [4 i8 Y: D$ o4 ?$ yvarChild.lVal = CHILDID_SELF;* N% i4 L; b- ~) j( o0 s8 z

, n* z9 e2 ]! a3 x*paccChild = pCAcc;' B, k, s& ?0 `6 B8 }
}; ^/ h3 ^3 \7 d, l2 c5 e% A4 U1 T6 x
else
5 J7 K- p/ M0 M: r3 u' m- @//如果子不支持IAccessible 接口3 b( T" S7 X* {: Z, ~1 ]6 a4 N; G9 T
*paccChild = paccParent;
" m) d- h' p: D# H$ ?
# l- U, I# m! I3 T//跳过了有不可访问状态的元素
: f% A" p# Y1 j4 p4 SGetObjectState(*paccChild,
1 Q& n1 x# H) q2 |( j+ U               &amp;varChild,
+ ~. D* l$ j: _2 Y+ I$ S( W3 C               szObjState, 9 _7 Q6 x& w0 }' t% Y# _
               sizeof(szObjState));
: V/ }9 k% f/ X8 Iif(NULL != strstr(szObjState, "unavailable"))
2 y& T4 K4 O3 M& X- u{3 q$ w1 ]0 `, l1 u- j6 ?  U
if(pCAcc)
% l/ h2 K% ]; U0 ]3 RpCAcc-&gt;Release();; x9 O" H' p% w4 w, B) C6 P
continue;1 [( d; J/ W, A2 O
}/ y4 p9 p; M+ h  f
//通过get_accName得到Name
& k; L* M& A$ A9 k5 C  D" oGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
8 C# t- s) }* F! ]7 N" H//通过get_accRole得到Role
$ h! U' Q8 a( n/ Y* }3 i, NGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
+ a3 [! L9 c" f3 K% R/ Q8 Y8 z//通过WindowFromAccessibleObject和GetClassName得到Class
" A  t& N* |7 m# y. A) KGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));, y- y8 q$ a6 v; S
//以上实现代码比较简单,大家自己看代码吧。- a3 d" R& d: Q9 T4 C  q
- N, z9 Q- g: J) E" `$ J6 a
//如果这些参数与输入相符或输入为NULL, |' {& U9 s3 b; e) |  z
if ((!szName ||
" O- R* ?) [. z, V2 f! R     !strcmp(szName, szObjName)) &amp;&amp; ; ?7 `. `; o/ c9 }
     (!szRole ||
' N# C6 V$ ^0 _# S9 n& j      !strcmp(szRole, szObjRole)) &amp;&amp; * ]. R* `% R$ K7 i& a- A
     (!szClass ||
. M  W3 E& Y" l  a: d      !strcmp(szClass, szObjClass)))
! P5 K( @7 T% O( u' R! H3 L{" Y7 q% i; g8 R8 ]) d
found = true;
- Y- q$ }4 o. w: C*pvarChild = varChild;; s% \0 x4 D& ]$ Q$ Z+ e
break;, {' N0 @7 j, g$ e" J8 r0 d
}  z) r( f* [, U; ?1 y; M
if(!found &amp;&amp; pCAcc)
3 w* ]! r! B7 N: N9 z9 ?( L( R1 g) C{
5 H  H+ n3 s* ~2 L. N9 H, @* d// 以这次得到的子接口为父递归调用- u0 d- O) {5 F/ G, O6 h0 T
found = FindChild(pCAcc,
* n5 \8 X/ C" s$ J8 B( R2 m9 w( h                  szName, " I5 U& @2 c& M) b2 W: ~- ]
                  szRole,
( H( {, _4 Z" C+ z9 P  f# {                  szClass,
& A. p- l! f7 T  ~" Z                  paccChild, 3 Y! J! I; @8 C) @2 F
                  pvarChild);+ `* E* H; O& h" e
if(*paccChild != pCAcc)
$ x% n+ F! C$ s2 ?* o) G- W0 gpCAcc-&gt;Release();& i* T7 N4 P4 N7 m
}% m7 h( O5 K; B7 K$ s( E
}//End for! c! r7 U1 B. o4 |

7 |5 ^' k3 p2 S8 I7 S* C  r// Clean up
9 e3 \) c  O5 z6 D% Gif(pEnum)! @' M5 D4 x5 a3 \, F! r$ d
pEnum -&gt; Release();$ f+ [( k$ l. B7 U! q2 A
5 O( F: }5 W: L5 H
return found;4 ]% F% W# g4 c
}6 K* I; u/ x- W! g
1 d7 ^1 p2 w) @% o# I- ]2 J
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,6 a* i" J! Q# Z' w' j
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
4 y. A9 V0 z8 Y6 @3 N4 P. b//将这些或数转换成相应的用逗号分割的状态字符串。( U: {. z* P, c. r2 c3 C/ ?
UINT GetObjectState(IAccessible* pacc,
% J; J3 @6 \5 ?* E2 c6 @                    VARIANT* pvarChild, # W5 E& L5 X9 v2 P9 R7 i* V9 f
                    LPTSTR lpszState,
" V! R- q. l2 b( i; M' T                    UINT cchState)
2 O/ E- G' u4 ^7 r/ u3 q* ~{4 @% [' u' J! Q1 e0 [. D/ c' G) `
    HRESULT hr;
# K5 u! I' _& C    VARIANT varRetVal;
+ w: b3 F0 Y& ?) p
! d# i. e, f- v& d8 @    *lpszState = 0;& Y0 D+ k2 G! M1 s/ k! H; H8 t

5 z! v) z9 L  C' t! V8 P9 T9 h3 g( }    VariantInit(&amp;varRetVal);
5 j( z* Q6 F% u8 T/ i: g/ E( l( m  M- G. _# @3 U9 X* V
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);% J; p0 }0 M4 u0 z6 D7 m

, |" |1 N2 h3 @5 }& xif (!SUCCEEDED(hr))
5 F3 Q; ?* t5 }' E; a        return(0);, u/ G3 B6 y  m: p+ ]

/ ]& J8 m* P! o; a# v: C; w8 ?DWORD dwStateBit;; B4 j+ {- L$ J  p9 E/ g3 f
int cChars = 0;- S, P8 o" A6 h" L3 G2 s
    if (varRetVal.vt == VT_I4)
. t. X  z/ f- A% B{% k: D* z* a6 \" L0 u8 l9 Q+ Q
// 根据返回的状态值生成以逗号连接的字符串。1 c, |% m4 f/ K: v) |$ F1 q, L
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
, |; |3 b/ T6 b               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; ; U$ U+ E' y- D! `6 ?% `
               dwStateBit &lt;&lt;= 1)
( }8 r5 W! y5 R; \: a  n        {" D& d$ A! r' h8 f, m5 ~5 z
            if (varRetVal.lVal &amp; dwStateBit)
9 h8 a" j9 w( Z& v. J            {
1 n. A! P# V* c7 r  s1 k                cChars += GetStateText(dwStateBit,
* L# V1 A5 l8 b/ f) o9 j                                       lpszState + cChars, : B! f) n$ d  p! I
                                       cchState - cChars);
$ v1 p6 K- {; ^. c0 A1 r5 E" A*(lpszState + cChars++) = '','';) b' B) t0 I0 S% M  J& \
            }
: Y  w8 U" v7 F& m( A- `        }+ k9 L$ h' f1 u, }' u
if(cChars &gt; 1)9 G2 H6 }8 M3 o1 F& t9 g
*(lpszState + cChars - 1) = ''\0'';5 u# b0 y% G4 F$ }9 Y) T$ v
    }
0 H: B. M2 K+ g, \' f4 H7 m    else if (varRetVal.vt == VT_BSTR)8 R: h% J0 Q5 y$ T8 i6 N$ n
    {
  L' z# l/ Y, J2 R& a$ D        WideCharToMultiByte(CP_ACP, ( K3 W, n# ]$ a. Y/ S& G
                            0, # w: l1 V- {. T/ m2 k) v' G0 z
                            varRetVal.bstrVal,
* ?7 d& Z) U$ H! ]8 L                            -1,
/ g9 K0 W+ r# P+ [, P" e) }7 A                            lpszState,. `" D$ D, @; F9 Z9 n; X
                            cchState,
, [8 w+ c( v, y7 u                            NULL,
3 v% G: E: t2 [. l8 _                            NULL);7 g( j4 ~: T. s6 j5 W
    }
; k5 O9 I8 `. a% M3 u6 `4 }4 {4 W0 I! q. i3 Q
    VariantClear(&amp;varRetVal);
# r; ~3 _: c; t: l- |2 D9 s/ k: d$ j, v- Q; W2 d6 U
    return(lstrlen(lpszState));
- t: j% O9 H' s* b' c+ u7 A1 k}</FONT></TD></TR></TABLE></P>
# N; K1 y8 y7 x) Q7 b2 U<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
3 Y) C# H) e! Q: ~' s$ u  c/ n) ^
四、在 IAccessible 接口上执行动作' u: C# {9 a* v! |
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。4 M" O! W* y- \
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
4 G  z' Z! t. H! F, W2 ?    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
6 A' M$ v8 u2 E3 C<>
5 C, q$ p' }4 L7 i2 ?1 Y8 t<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
& E* E$ {8 g$ \) F6 ], W
% H! e& T  Q* H<TR>
0 [$ [4 g. V: M' R2 ~/ x% Y4 {<TD><FONT color=#00309c>//在文本输入框输入"regedit"8 Y5 B+ ]# ]4 Y8 l7 B
if(1 == FindChild (paccMainWindow, "打开(O):", $ `& {. q# Z+ Y, n2 F4 a  a
                   "可编辑文字", ( r; v7 r+ X0 m' `
                   "Edit",
) f* a  G; @6 ~0 b                   &amp;paccControl, * ?' \3 e# z3 ?4 f  A: Y
                   &amp;varControl))- V/ f8 h% _& _  \8 _- j
{
" _! v' F% E- y1 ]4 }//在这里修改文本编辑框的值
( y  `& H" R/ t+ ~2 q1 G/ ehr = paccControl-&gt;put_accValue(varControl,   t$ f5 x3 M; C; o" M" t
                                  CComBSTR("regedit"));
' r1 ^9 H3 |: x5 ^paccControl-&gt;Release();
) K; [  z) C  D* G, PVariantClear(&amp;varControl);
6 X) n9 P! w' a4 g: T}
2 j4 c" {4 z7 C1 Y4 h# C/ M3 b7 n9 ]
, f7 B. S7 I9 I' n6 X# i; M- {" c! D// 找到确定按钮,并执行默认动作。
( L, t, |; X- G3 E4 D  t& lif(1 == FindChild (paccMainWindow,
" W6 G5 X( K8 g! m3 T, V2 \5 U  X                   "确定",
- z4 R" a( Y( y+ p                   "按下按钮",
  Q+ c$ b: I. k1 y: |. W                   "Button",
. R5 O& J0 a! j4 |' w: i4 [                   &amp;paccControl,
' b. {: [$ r: r2 V: K                   &amp;varControl))
/ K; @- A/ S' @7 J( [+ s* h9 A{
7 s# J" n# K3 }% ?# l& t4 U0 H//这里执行按钮的默认动作,即"按下这个按钮"
3 v. O& c, t$ ihr = paccControl-&gt;accDoDefaultAction(varControl);" Y) x: h0 S- @: k
paccControl-&gt;Release();' [7 I4 t! o- L4 @6 i* z
VariantClear(&amp;varControl);. n  m: g% C" W* b9 Y( Q
}</FONT></TD></TR></TABLE></P>
! s1 d; x# Z4 c  n" X. I  K) \) x5 a<>现在,你会发现已经成功启动了注册表编辑器!!- W3 W% F  }* f
</P>- N3 l, Y$ I+ L2 M+ k: i
<>五、模拟键盘和鼠标输入0 G- q" `* N7 r" x" J
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。4 Y, [( [( j4 T" `9 @3 Q8 s
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
. u  x$ K$ O$ }& k- k    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。0 j( ]+ z: X4 t0 }) ?, ^! E) |+ u
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
* b3 o  B6 y& g<>$ a( O4 W0 ^. l. |6 \: C5 u7 W" e4 _* A
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>9 T5 H# I  m$ j
) b0 P+ |0 t- m" J+ p; Z$ z
<TR>. j% z  K( l# n$ T$ g
<TD><FONT color=#00309c>INPUT input[4]; , W+ v& q3 a8 U4 l( t" c9 ~4 f0 e; ?
memset(input, 0, sizeof(input));
0 k6 r3 _) J- V4 k: T' H. Z3 S) R, r$ t( Q5 \2 K. Q1 C$ I
//设置模拟键盘输入
7 X  K: J7 _9 i* Uinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
/ e& w- x+ i# W0 o. Cinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
' v- h1 C; A+ s% Z% E3 |8 oinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
0 v; z8 o) p# o+ U& n
' q& h+ F/ [4 V5 b, Q5 a  T// 释放按键,这非常重要
3 l& g; q' A' B$ h  ainput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
  V! G, |2 f: y; K% j6 O6 t
0 B# `$ H9 t0 F1 [0 s% XSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>% s; U  T3 Y! V
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)" ?: i. e$ C5 F  i
</P>2 y, d0 |4 }+ q  c( h
<>六、监视WinEvents
& c+ u9 d7 U. G6 [    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。9 a( A7 {% F1 Y! T9 m6 e
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。  x* M. f5 @, J2 a3 z
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
: A" P) x$ H, ]<>5 n  O9 W* h0 u( e; g( T* K
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>! ^( Y5 q4 e2 @$ B% @3 y3 f: Q& A3 N
* K* C: m7 j3 [2 [5 c
<TR>  y( `! @6 t1 ]/ `  o2 c
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
6 Y  U" T/ x+ Z{
) G: b- ?6 {2 v9 f" V$ F. EhEventHook = SetWinEventHook(" n! j* z* [/ W
EVENT_MIN, // eventMin ID: b" t. x" [6 a! O& y: [) J
EVENT_MAX, // eventMax ID4 f; A8 y8 O9 `; K$ Q: Y* ~4 h1 O4 A+ N
NULL, // always NULL for outprocess hook4 S! z2 h) s& H
WinCreateNotifyProc, // call back function
  v) a8 I* w; q- z- [6 v6 ~3 E/ X0, // idProcess! y5 ^3 R+ Y' Z  u
0, // idThread 5 O# r1 q1 P( I; w* W
         // always the same for outproc hook
" i1 A2 |% C( R3 z  D$ D. R' v  PWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);% x* F! k, y* n9 [
}  </FONT></TD></TR></TABLE></P>
" ~3 j7 C' l6 y# y- j% g: j2 \# q5 g  {. y+ Q6 A
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
+ i1 _4 c* p6 i& |6 [7 p( B5 o( p下面是回调函数:</P>
6 C; c" W# y. g) h& x+ c<>
, B, E- k" ?% ]" N, [% r<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ T# X0 d" P) g2 F  \  h# F( c

% H" _7 C' [7 k/ K+ S$ a! q<TR>
! z- v/ i" e! ~7 \& s0 \% D<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
* ]1 K3 X/ r. d9 l+ \HWINEVENTHOOK  hEvent,& _( C1 a% |+ C5 D+ W
DWORD   event,( k4 k: x- T; v4 y% {
HWND    hwndMsg,
+ i  u, [, H) J; H, eLONG    idObject,
. ]- c+ @! j" c, P' k5 O. v4 V9 KLONG    idChild,
1 A( H1 N; n! ]' v2 BDWORD   idThread,
* m% l+ P# e5 o! }7 tDWORD   dwmsEventTime& {1 F1 p; n8 ~
)
+ r% P5 |2 r9 r! Z0 x{
* c6 B8 H3 X2 i2 N) L1 q; h0 G# c8 q# D  L2 H
if( event != EVENT_OBJECT_CREATE)
; I$ L0 C# _1 }0 F" g1 q4 I; y5 N8 Yreturn;
0 K0 i. i7 L6 T9 [* |1 ^0 p. t* y/ q+ ]3 E9 [. {
char bufferName[256];
" T( f0 }) k6 d( p4 \! \+ EIAccessible *pacc=NULL;
+ Q2 d' b- w: U0 ~8 tVARIANT varChild;
  e7 H9 R. X% a- Q' k$ r) j    VariantInit(&amp;varChild);
( Y7 o* M/ Z: T8 J//得到触发事件的 UI 元素的 IAccessible 接口/子ID对% @/ K3 h3 M( Q+ F/ s- v
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, + c# |7 @, c0 T; t& p
                                      idObject,
- Z  {' \( {! y! K                                      idChild, * R  l1 o$ G6 ]# C1 D
                                      &amp;pacc,
- l4 B4 C! A+ U                                      &amp;varChild);
! |: l9 f0 }/ Z  T! k% P/ J! J1 k; L* F1 _" F% b
if(!SUCCEEDED(hr))
( f* T2 L1 A/ H{/ z' `( k0 j2 S3 s" i, T- {
VariantClear(&amp;varChild);1 w* \& h; c- F# H! p- t
return;
# a9 Q: ~$ Y) U3 p& h6 S}" ]9 W$ k6 F- }; x: b: T3 }
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
# n: Z, Y4 S3 U! m9 ]8 CGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
; o- A4 d6 F8 o6 T2 A; W. B6 Cif(strstr(bufferName, szMainTitle))* |: @2 N9 j) z# }! k
PostThreadMessage(GetCurrentThreadId(),
5 U2 p# G& u* p6 t) ~3 e/ B6 P# Q  F                  WM_TARGET_WINDOW_FOUND,
1 B; g; Q6 I5 c; d% {9 G4 r& W                  0,
  S& K* \0 K- x3 M% s5 d                  0);
- Y* F! c& k* f
6 }1 E: x8 T* i2 freturn;
- B0 K* ^7 p. M% c- X}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。( H5 M- z* d6 A( {
" J& r6 m! a; V7 B& @$ R$ Q
附录:
7 \* @/ ?/ V- B; F6 s# d
: Y+ A' ]% ~* n7 Q关于IAccessible 接口/子ID对:
7 I" |' p6 Z3 _3 D- |9 s    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
# u6 g$ G% W) z, b) Q8 Q4 ?( W3 k0 z    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!" i+ ]; n3 P% s8 k* p
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
4 |# E; @6 B/ h2 H1 ]$ D    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。+ k& `6 B7 D1 Y' ?5 G; \
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!/ O% j& ]% n/ Y9 m9 J7 ?  f3 S( n

  W! y6 Q0 ^- D: H3 p" B% Q注:% c: j! A, ~: H% ~7 _  P1 f1 O
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
* |) `4 x0 _) p( `: |' o    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。8 u) E# P: ]7 D2 f" s7 H) u

7 q$ {4 ~7 ]$ p( O5 B$ ~3 n参考资料: / s' I* {& Y1 ^! D# r
<UL>1 w, Z; v8 f2 E' M0 e8 {
<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
( v  B( T2 M+ L( w. E8 y% S<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
作者: xShandow    时间: 2004-11-21 17:25
不错,,顶.绝对精华.




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