QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5723|回复: 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 K, L: a* v  }
: u4 g- Y& E2 e/ A* h<TR>
: _1 g) @$ T5 j! v0 }  `1 S<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
0 K! P4 N* w' c! \9 L, [: l原 作 者:崔传凯4 U. O3 R7 {' Y  C1 l* N& P% i
原 出 处:vckbase2 R2 r3 k( y  f1 s" Z7 U/ a: w: d" K
发 布 者:loose_went
2 D0 }: E9 u& s# Z$ e发布类型:转载5 p$ F& O6 N" s7 g1 @' g3 Z
发布日期:2004-11-129 c$ q  p! F  a# k8 c
今日浏览:66 t( F3 N& v1 g- S$ ?
总 浏 览:234
$ l$ b* X# t8 a- {</TD>
- E' R9 y$ m# 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>7 l9 }: G- h+ V+ R
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
* V) v- o! J* H
- @6 V8 w2 S7 u% h# C  G: S3 [6 o<TR>; K$ @* C# ^( D6 a/ ]. j
<TD vAlign=top align=left bgColor=#ffffff>. j) j; S. n# j; m) n

! t& x. s1 o0 i7 j& B! N- L<>
0 {2 r7 }$ _/ S* G<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
5 U. \" ^3 i6 R) i2 _% S) K, D0 e0 ?! `0 v! A/ k& K: T% J
<TR>
. ?9 }! z1 l& S<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
) v6 J% K! e3 Y2 p7 q4 o5 ~9 Hway accessibility aids work with applications running on Microsoft Windows?. It
" v( R8 H2 w" T7 f1 v5 T  Bprovides dynamic-link libraries that are incorporated into the operating system
' V& |4 t& d- K& a4 x9 Mas well as a COM interface and application programming elements that provide
7 x1 i) w, I9 C! y+ Creliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>. v5 N0 L7 I# |- Q8 Z; Y/ x3 e
<>一、基础- M% r: V1 B! k$ ]
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。4 F) c2 L1 T8 V8 V8 p
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
" ~& w, f2 |+ N- K2 T    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
$ c5 ?* V1 e; d# l1 p2 |<>二、Active Accessibility 原理% N% \/ r" v$ t0 y
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
2 B' P% I$ C0 ]$ M    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。! |7 q$ }& M: a0 G: c
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。% U/ Z$ u' ^. k7 G  r7 u; h
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。/ X% V- S. x; H7 ?
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>+ ]/ d6 p6 T1 @+ `# g# c
<>三、如何得到 IAccessible 接口指针
5 C% D* D# N0 k7 O" r; G% s    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
3 o( q; J4 B9 A" `3 q- ]    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
) H8 l9 @2 X9 s& s. C: b* Q: i; x, f    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
  K6 v* Q2 t) g; K5 B- @4 D, G    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 b0 m- u) |6 T9 P) \& P+ O
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
2 h* W, ]! e% h/ J, Y) h    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
, c/ v) S' f' J) _5 i& w下面结合代码介绍一下它的用法。+ I2 i, r' q# k) j. o2 {# u" H2 T! I
我们来得到下面运行窗口的 IAccessible 接口指针。
+ u: B4 ^3 }1 L) H* D3 T9 ]% A: B* S1 W- f  k
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
6 x8 c2 u* d, F& P1 x. P& l<>图一</P>1 f4 N$ [9 X, i1 k1 o
<>! k; {' l, L6 y: G
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
/ ]% R" L/ d, t0 W5 d! }; L. N5 M' S! f( j% \
<TR>9 R- {( d, z! E/ e
<TD><FONT color=#00309c>HWND hWndMainWindow;/ U$ y5 U. w1 U; y  i2 k0 d
IAccessible *paccMainWindow = NULL;
% W# }% X0 t3 q# ^0 U  d+ i; EHRESULT hr;4 E5 \( V+ o8 l$ `+ p+ e) x9 h
//得到标题为"运行"的窗口的句柄
4 a) h% n4 m( W! Iif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))0 v' ]& g* ^- u4 K  r
{
, ^/ d1 z1 ^; a( CMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
+ f5 \% Q3 |5 ]: M" l' C' F}
+ j- `. v: d* M# h; n) Eelse+ \) _" G3 n. I, e. j( B4 q- t4 I
{
6 p: l* {/ Q6 o1 q% S8 ]& x5 C//通过窗口句柄得到窗口的 IAccessible 接口指针。
# M7 ?, W$ n1 {! Lif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
' w  Z- T" O) Q( w: K; d; }9 w                                            OBJID_WINDOW, ' f% H, @9 I5 f  n6 k4 \
                                            IID_IAccessible,8 Z  b% y; q' n
                                            (void**)&amp;paccMainWindow)))9 O& C. {9 B8 G4 q1 d, q
{
) o8 ?/ K: ^9 z) t4 S//……我们可以通过这个指针paccMainWindow进行操作。
9 A9 Q' J4 o7 V$ F9 D1 q4 |+ E& npaccMainWindow-&gt;Release();
8 k7 R& M. g; _  E        }
1 x$ t0 n/ a" [# F. ?  o/ j}    </FONT></TD></TR></TABLE></P>' ?7 w5 G0 {3 W
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
0 h( \, J2 O9 T; K    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……1 m" I8 {! y+ p3 g5 X) |6 k
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>* u  S8 ~5 x- F* K7 T
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>: x/ f0 J7 L" r( X+ t- V* b, F; }' U
<>图二</P>& o! V6 b4 a4 P7 R9 S
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
6 D0 i7 V3 a5 Z* Q* G<>8 g7 W6 i( G. L9 c& q
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 v9 y; q, `# j+ `% t4 y. c1 `* Q5 a- w3 K
<TR>( O& ~" Q- S8 E& B
<TD><FONT color=#00309c>Name = "打开(O):"
  ]' ~7 }0 i( L% d) o& uRole = "可编辑文字"
  p: c" N# B% F: hWindow className = "Edit"  </FONT></TD></TR></TABLE></P>, x7 J5 @/ `. r: W" e
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
9 _, v! P- [' p# R9 b, X    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。% Y* e) P! W( K
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>6 M* D5 v1 X+ R7 b# V5 q+ l' E* ]( [
<>
$ a. Z# }. [  s( G1 h+ p: s7 e( r+ k<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
8 J, q' {% F7 H
3 `2 r0 E( {' Z6 g- ]( P" S<TR>
: ]' `" L: l  `# a3 ]7 g' z; j6 p<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
* T( G9 _/ ^0 `" eVARIANT varControl;    //子ID。$ ?3 q  `: Y' N0 U
1 N2 @3 L0 K# g$ V; I! x; q
FindChild( paccMainWindow, 2 U9 ]2 i4 B9 _5 z7 c/ A
           "打开(O):", 9 k9 {% H) k' T
           "可编辑文字",
) j% I6 F/ l! t" O           "Edit",
# R9 d0 D5 V3 e           &amp;paccControl, $ H7 \6 x9 ^" ^
           &amp;varControl ) </FONT></TD></TR></TABLE></P>1 W$ e7 f5 z1 R/ {# E
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
3 Y0 P+ \+ E- a1 M第二、三、四个参数分别是名字、角色、类。
! R4 g* z4 i2 j. n3 @/ [后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>, B+ w$ n/ ~0 U* b1 H' A
<>
% [# v" ?- ^" |* }3 G<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; P* ~: n& h' u) C/ u& \
) p/ u5 o- ^7 Z3 B; I2 l$ X% E2 g) }<TR>
2 o' |0 O. V  n<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, 1 l! c& i# F' {) a+ e6 s
                         LPSTR szName, LPSTR szRole, 5 o% N2 \( u! N
                         LPSTR szClass,
" a# _) k/ ~4 q                         IAccessible** paccChild, 3 |1 g0 C8 N# i
                         VARIANT* pvarChild)
, K+ w/ u- {% t9 n! z{
/ ~* h  B) `1 y8 l" E( WHRESULT hr;6 A& ?& b- T6 {7 G6 L: p- i
long numChildren;  V3 H4 z3 p3 c8 e5 a
unsigned long numFetched;! j) n, G* x& Z* v' t1 L
VARIANT varChild;
+ Q: C! p  }( m/ |int index;
0 {. P# f4 S0 ~" Y9 T4 LIAccessible* pCAcc = NULL;
: E3 W4 S% R+ P; D  c2 QIEnumVARIANT* pEnum = NULL;
% Y  G" ^" T7 \& W6 a2 w$ s3 G9 uIDispatch* pDisp = NULL;& Y4 a- l+ {, a. W
BOOL found = false;
- a* M/ _* j* }% g5 p0 ^! P$ d% zchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
, O& E1 k$ h3 ]! x2 \0 \" {4 Z7 r7 [2 Q) F: n/ w' b
//得到父亲支持的IEnumVARIANT接口0 i+ b8 O, i. v# w9 M: [
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);* ~3 z0 e6 Z7 M6 q- V4 I" m. N% j0 O

. ~1 L3 l/ z/ W! Eif(pEnum)# W, C/ H7 b9 U$ ?5 b+ y
pEnum -&gt; Reset();
+ {1 g/ w* R+ A  o6 Q
; g- q! k+ \* B; R1 }2 F//取得父亲拥有的可访问的子的数目
$ m6 a/ O  k# b8 f" `2 }* w; k7 h6 jpaccParent -&gt; get_accChildCount(&amp;numChildren);$ u4 U- E1 e: }% e1 X4 V8 O

  {4 V* V) g$ r7 f$ C- P1 L& G//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。, R( E0 J& q2 W" @
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)6 M: z. W4 C: U, `2 D
{8 D- l5 H3 l; C/ M
pCAcc = NULL;
, ]! i' i1 I( \0 j5 \! @9 K$ r# J! z0 R+ s// 如果支持IEnumVARIANT接口,得到下一个子ID
8 s  |' A3 }: q) M3 O//以及其对应的 IDispatch 接口
& r8 O% }* T% J+ @9 j% p: nif (pEnum)
& J3 D  d0 s/ W7 I; n% H) `hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
- f. x1 X+ v* W8 h9 Nelse
" q6 G4 G' T6 E4 M- l* e{/ P  `4 G8 z+ y7 L7 i# }) I6 w: ]
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
" o' C9 u# ?& m( R# ?& ?varChild.vt = VT_I4;
6 x( q6 b- C) U% o' g' ^varChild.lVal = index;6 Q. A$ L! B9 E% }! P
}
; b5 q: A$ \3 `1 y* I
7 E6 W+ d% o! h5 U8 m// 找到此子ID对应的 IDispatch 接口
7 Z# ?" e- D5 p& p- I  f6 cif (varChild.vt == VT_I4)
) Q1 z9 V- j+ P, |1 c# s' c{
4 I4 l8 `' p2 I9 X//通过子ID序号得到对应的 IDispatch 接口. C3 ]( L$ o( U( x6 z) |
pDisp = NULL;
/ K- q2 B: v: \" _' e+ ihr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
0 W, P  m; T( {$ [4 R7 |}
% A2 p. l7 a' S2 X0 m" gelse6 J2 @" ?8 Q% [$ Z3 r6 d
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口4 n; R& u( P9 h  P3 R
pDisp = varChild.pdispVal;" j  w8 t8 h; j/ f/ P
$ p+ F! y; V" D/ \+ x' A' W
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc  x& h4 E! H- c* K0 h
if (pDisp)) w6 ]2 g  S, ?* y3 v' A
{' z2 \! R2 ^0 c1 W3 y
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
1 h% d! k9 y$ ^* o2 |hr = pDisp-&gt;Release();! N+ g' x% ^" y/ [8 }) d
}
& D& C9 K6 @3 k5 O* B; y# u1 Y* \1 \. _  k
// Get information about the child
9 q; Z$ u) a1 h# C4 Oif(pCAcc)
% T9 @9 m+ R- j3 Z/ v- |- {{
0 l% u& X8 A6 H: w4 y! v7 g! c( E3 M//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF) ]  P5 }$ z+ p. n
VariantInit(&amp;varChild);0 }) F5 C$ K3 i$ i) ]0 d5 M7 ~0 Q
varChild.vt = VT_I4;, p' c# C7 M& h+ \, p$ \
varChild.lVal = CHILDID_SELF;
3 i" }2 x8 v2 M( T
' P! U. w1 |( ]( t/ \8 Q1 {6 S*paccChild = pCAcc;: }% ]; ]. G% J9 g. r1 j
}3 K$ S7 S" h; w( i0 _0 G7 A+ ^2 A& G6 y
else2 T: C9 @% U  Y0 p; X
//如果子不支持IAccessible 接口! G- e8 i: d' S0 H5 M& R
*paccChild = paccParent;
) F5 p& i" j$ {" Q# i/ E/ P9 M( v: H$ q' A3 A: |/ M* l, a
//跳过了有不可访问状态的元素# O7 M& [1 u0 Y( r" F
GetObjectState(*paccChild, # Y" Y" w! h* s  |8 W; `" F
               &amp;varChild, 4 L- v% B. A6 f0 h8 ^; r
               szObjState,
; F& y  H' j, D( m2 c3 x& ~1 l               sizeof(szObjState));
( ~+ ^! |/ Q3 z/ l% _; Lif(NULL != strstr(szObjState, "unavailable"))# V; B* k2 c) x9 S) y- l, V
{$ Z3 o! v2 u2 j9 _% q( ?6 u0 i
if(pCAcc)
& `6 U5 m6 P1 j' d* V2 M; E2 bpCAcc-&gt;Release();! D: D3 w. r4 ]4 w/ P- Q2 l
continue;2 s+ h, ?1 O( N4 v4 E
}7 M; n$ h* i+ E) r5 ^) N
//通过get_accName得到Name
8 u' @) F: A% ^! vGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));; N8 ^- D/ _$ y/ S9 O4 G# x# W
//通过get_accRole得到Role
, l: D! O5 Y+ h" KGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));- q; |: O$ @2 |( t
//通过WindowFromAccessibleObject和GetClassName得到Class% Q$ h# B3 T" k
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
( A! ?: R6 U. S( ]& x) ]/ f  c//以上实现代码比较简单,大家自己看代码吧。
. {+ O/ _# \+ H) ~* S8 l' B( [* Q, h5 y
//如果这些参数与输入相符或输入为NULL
" |" O4 Y) h7 I+ ]9 {% L& x" Hif ((!szName || ! x1 {5 Q+ t% D2 ~
     !strcmp(szName, szObjName)) &amp;&amp;
) b  ?6 g, l" i9 P4 s& {     (!szRole ||
9 m; t' s* R, E6 m0 |& S! x1 W5 r      !strcmp(szRole, szObjRole)) &amp;&amp; ( |+ p( R1 w3 M) A7 w
     (!szClass ||
8 G8 d, c! |+ D' c( k( |, K* M      !strcmp(szClass, szObjClass)))
' i9 r- x. L8 Q9 f7 r' F1 |{
! M9 p9 J+ \% N/ o! n, @' sfound = true;
! b, X$ N) ]8 S9 T) Y( G9 y*pvarChild = varChild;: U' H" k& c! Q$ {& m3 t, ~
break;5 R6 }4 z2 J; s) q% U
}
! n5 Q/ B" K( E/ `( N: Yif(!found &amp;&amp; pCAcc)
2 R& _& N* _' ~/ }% W! t- W{
+ a& W1 m% g3 V/ b// 以这次得到的子接口为父递归调用8 G  c2 y# O" \2 s
found = FindChild(pCAcc,
( U' r# L- u9 S/ X/ a; x                  szName,
6 S- T4 _  }  m) s                  szRole, 5 A, V! o# ^7 y! c0 ~3 Z: |+ j
                  szClass, ; a0 C* d1 @7 }% S$ t, {
                  paccChild,
9 l- E8 Q+ e7 n                  pvarChild);
3 z* `5 f; [6 q! @- K+ {5 C8 L$ |if(*paccChild != pCAcc)
+ S2 p8 d, T$ X" u0 S9 W/ |pCAcc-&gt;Release();/ ?: L! [6 }! I
}
, P' w; H: \. v) E( C* k* {* G}//End for
! `; @. p* l$ L5 B
/ |& x* Q# T# L0 K// Clean up
' L. z; w+ g* \! Zif(pEnum)/ i# F$ D! @, V( M
pEnum -&gt; Release();% Z1 q6 K' p; g/ @4 v! b: `4 D* s
; q- b7 D  N5 g: g# b: X3 _" h# C
return found;
( S6 t% V4 {; D}) U& y. Q6 [% e/ A# C8 C" t

: u0 R$ c3 g! m) R% j+ \// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
6 G' q3 @* B# K3 p% Z' {//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。) k$ [# u4 I1 r. b, c  f
//将这些或数转换成相应的用逗号分割的状态字符串。
( `% j# n7 x) y9 r% oUINT GetObjectState(IAccessible* pacc,
& S5 R% g% b1 J8 [% i: I$ h                    VARIANT* pvarChild, ; }* T2 C( c+ U+ s
                    LPTSTR lpszState,
0 G8 m) u2 J' B$ N. k( T- X& m0 N                    UINT cchState). j; j" X# U  `$ o5 ~
{$ D! Q& Y2 M! {- m5 K
    HRESULT hr;
' y/ l! T& N0 p0 o" _    VARIANT varRetVal;
! f$ L$ A' D2 @, S0 J: [) b9 d  x/ w& ?* k. N
    *lpszState = 0;6 v- @/ m' w5 h- \: M' {: C

+ c: E: J0 Y# ~$ `' R  }9 F    VariantInit(&amp;varRetVal);3 s4 H+ g4 y$ e7 f/ J+ A

5 H! Z% x; y" u' e% r1 D3 w: g    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
# {' X! m; m  l, S; i# n
; g5 V# C. n+ t( Dif (!SUCCEEDED(hr))
( f8 y& v6 K  e% U2 c/ E- r! m        return(0);- k/ h, Z  ^7 R& |

. w7 [+ J2 F1 }DWORD dwStateBit;
0 s! i6 H& Q6 n' pint cChars = 0;% J7 L3 ?; i# Y/ {  J
    if (varRetVal.vt == VT_I4)+ q/ k% y0 H* `" e2 h
{, M3 u3 b) l* J4 q& `
// 根据返回的状态值生成以逗号连接的字符串。
2 p! A7 r! T1 L8 J$ _# l9 v        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; 7 p3 g; K. u7 H( ^' s. C. j& {3 I
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
& P/ M$ S& u: m2 ~' V" r               dwStateBit &lt;&lt;= 1)7 u/ q+ J' @# M9 c
        {
% E- n! j8 H- ~1 [4 ^# O            if (varRetVal.lVal &amp; dwStateBit)
: t; X1 w$ k7 Q4 P            {4 @# x: R: X1 V
                cChars += GetStateText(dwStateBit,
) T7 n, h3 a" e8 z. `1 p( V0 V' [                                       lpszState + cChars, 2 t! E* j3 T+ P* c) w+ E# v4 E& F
                                       cchState - cChars);; y/ h1 Z1 R6 E- U3 L
*(lpszState + cChars++) = '','';
1 J% \. Q8 k6 X3 W9 G" S            }
; T; L; S3 Z0 K8 I% e        }
4 h* I* U- w3 Q: p0 sif(cChars &gt; 1)- \* z( v' L) `, B/ o0 }
*(lpszState + cChars - 1) = ''\0'';
9 @; T- W+ |( }7 \; x$ Z+ V    }0 G* [/ x' c1 L  O
    else if (varRetVal.vt == VT_BSTR)
" _+ D0 \0 {; V6 m7 B    {
( m8 w* @4 y" H0 l7 Q! b        WideCharToMultiByte(CP_ACP, ) {2 l, r* w  @3 t/ w8 `
                            0,
' `; @" K# J" f7 |& B                            varRetVal.bstrVal,
+ K% Y/ v1 f3 ]                            -1,
% r+ p3 W. x4 e$ S                            lpszState,; g/ V& F0 x/ O) R% P" T4 ~
                            cchState,
: o; t7 K. G1 N9 @) j+ Q0 E4 O7 ~4 B                            NULL, 8 ~% Z0 ?7 n, t; O9 e
                            NULL);3 J, X5 I* q+ a& \- E9 x& C* r
    }, [2 i7 n& e+ x9 G9 S5 X% h
( \9 l7 `, ?8 H( U" S5 N" k8 s
    VariantClear(&amp;varRetVal);
4 B) @2 f1 E8 b, N, e! P9 ^2 @7 U; Y! n
    return(lstrlen(lpszState));
0 _- j1 \4 O8 b, ^}</FONT></TD></TR></TABLE></P>
1 z/ a" o: Z9 l* G9 A* @; }<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:); u: B% @0 S" i' \/ k

/ ]" g3 j# B  r四、在 IAccessible 接口上执行动作
8 M+ V, A  z8 F    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
+ ^3 W' ?$ E9 T- K    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
. I" N9 }- T2 k' ~: S7 }; S    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
' o9 S: @. ~; J<>
* V3 O; c2 `( ?4 k<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* f8 u7 e/ i' {: U

# _1 \7 _! _- g- J5 Z* \4 l<TR>
1 ~9 G/ a6 R- W6 n* x0 p2 D<TD><FONT color=#00309c>//在文本输入框输入"regedit"; d: W$ ?1 A; N; d5 U3 V! h' |4 ]
if(1 == FindChild (paccMainWindow, "打开(O):", 0 n' }! J4 J" L" `; P' |( z! u
                   "可编辑文字", + S; I3 g! X" P0 d
                   "Edit", , c- Y6 U6 S6 p/ @7 @$ p. g" O' R
                   &amp;paccControl,
- h2 N( x" U/ ^' ^* A                   &amp;varControl))* n' x9 ~5 h) x$ p) l( i( J
{
) Z2 |; h6 w2 h/ [4 U* `1 d; N! H7 A//在这里修改文本编辑框的值
# R% T; |- f* ~9 k* H+ q. @) C( v0 hhr = paccControl-&gt;put_accValue(varControl, " g2 J% C5 h: s) Q% K
                                  CComBSTR("regedit"));
" a, \: A: O& I) @/ s. A1 LpaccControl-&gt;Release();9 a. U* D6 ?1 \. r$ E/ I2 Y
VariantClear(&amp;varControl);
. K# `" [# Q- Z5 D! @! [0 o3 `6 |}$ p# N7 z5 z. ^

# Y+ M$ l+ Z* q0 y4 f, r// 找到确定按钮,并执行默认动作。# [1 w9 F$ H. i- `- P
if(1 == FindChild (paccMainWindow,
9 t9 |* [9 b7 v+ S$ }! o* u                   "确定", . M* w  q! u6 L: @9 G3 {
                   "按下按钮",
; q% H/ ]  h9 L* I                   "Button",
) S9 t2 P. A' W5 x# U% a) T5 ]                   &amp;paccControl, + |# W1 c- N" U) `+ e: ]' Z
                   &amp;varControl))
: l0 ^, ]& d: g& v0 T{
) A4 K) E7 r5 ?& e7 ]//这里执行按钮的默认动作,即"按下这个按钮"
' [$ I& A) l$ X4 d& }  A/ H# ~7 khr = paccControl-&gt;accDoDefaultAction(varControl);
( E9 E# k+ i9 v0 kpaccControl-&gt;Release();
7 {7 ]% f1 s( g" o2 WVariantClear(&amp;varControl);7 s+ l* t" r8 v6 o3 d+ I5 |  K
}</FONT></TD></TR></TABLE></P>
8 ?! W( o' L* k" n<>现在,你会发现已经成功启动了注册表编辑器!!
1 R# z/ H! O, ^* g* F</P>1 Y. f& P3 O6 t2 @/ K# Y% S
<>五、模拟键盘和鼠标输入3 p- ?7 J' }7 ^
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。6 D: u' F9 }% y& `. L1 _
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。5 ]! c6 e! o2 |* o$ F
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
  ^+ L% @4 y, b9 q; ~下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>* y2 K) r4 c9 x* U0 m  |; ?, h! ~6 X
<>: B  u- j7 o7 N: B- @% b
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
  |% S' ~; M: n2 a; M) D* N% f
<TR>
6 O! p1 B8 }, R- Z' t$ B/ v<TD><FONT color=#00309c>INPUT input[4];
/ A- F3 O2 N+ [3 ]6 z, F+ [% `memset(input, 0, sizeof(input));" [- r$ z' f9 T1 Y

) O7 A- a) {9 o/ }) ~$ Y* V& x4 C( U//设置模拟键盘输入& c0 a8 C- Z% A- W& C8 T
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;. o4 ~1 w4 T7 f/ d1 Q
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
5 s/ }7 @4 K; i# v5 linput[1].ki.wVk  = input[3].ki.wVk = VK_F4;) h4 a0 I; z; ~  u; j# N
2 }9 ^9 a- ?0 Y8 e! k) L
// 释放按键,这非常重要
4 C8 c4 ?/ R' s/ j  Ninput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
( m. ]; y3 B' p, w$ U5 N9 r: X( i' ]% Z. R  {
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
" J4 F) M7 Q$ f/ L<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)+ M/ B. Y+ ?( F9 g$ t6 Y' k
</P>4 S& S0 Y4 Q; Z1 P( `
<>六、监视WinEvents
' b  N2 K. M0 O. S2 K3 W; g    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。% Z" f% X4 B+ T' m
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。+ P% t8 O; a6 X" C* a
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>( k; N. q+ X; J  W! u' d8 u* D
<>
0 S6 k3 U+ k) x$ W! L( D<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
/ n% X) L7 _+ F6 q) h. r; w' e2 T* k% C  g  A8 H; X
<TR>9 @+ _5 k) ]4 U# X$ E
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))& B) [& @/ A( D; R- v# B2 I
{
3 r. E( m) r. f$ p+ PhEventHook = SetWinEventHook(. o+ G7 j8 D! G9 b# o
EVENT_MIN, // eventMin ID
: ]- d7 b) T0 \) v" f2 H/ BEVENT_MAX, // eventMax ID9 p) v" h* O/ A& A# ^7 \8 H
NULL, // always NULL for outprocess hook
5 V6 L8 X6 R0 ^' t* D" q! `WinCreateNotifyProc, // call back function2 J/ \5 G, w0 l7 s
0, // idProcess7 H5 ~2 i  N3 Q) _7 I/ N
0, // idThread 8 f" P7 ~- W+ }/ E/ t
         // always the same for outproc hook  p0 f7 V, b; I
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
5 T" m' B6 I# @/ q}  </FONT></TD></TR></TABLE></P>
/ T# {/ o3 c& i. }" v% Z: a) `
' C0 D, g+ q. r: |<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
/ e( V7 D- R" s+ D9 z. w3 N下面是回调函数:</P>: q9 ^: C2 _2 j, v* n. r5 |
<>
  `. }# b4 j  E/ _<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>2 ~5 T& ]% y& i. X

8 P3 j) A2 e" L3 R" w$ P<TR>
7 a& t8 T8 q2 Y( i' Q<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(* q. Q3 {* }. U2 v: \
HWINEVENTHOOK  hEvent,1 U9 i7 |1 t* R9 m0 U5 t2 G
DWORD   event,5 J/ a+ G5 Y' }1 V& ~: n
HWND    hwndMsg,0 r* P: b1 M  w1 u  t1 y( D4 N7 G
LONG    idObject,
" g1 `5 o8 {7 f3 V6 t# i& L. ?LONG    idChild,, S  c( Y% _- V+ y- S
DWORD   idThread,
! [0 r6 I1 v! K- xDWORD   dwmsEventTime
9 w+ }0 Z; m; j2 \! @, N)
& j; M- \  ]% R! X% A3 K1 A{
' V. d1 p% ]- h8 Q8 \. v
5 w- t! U! V) s, ?! _7 j& Qif( event != EVENT_OBJECT_CREATE)! Q3 ^. h7 G: O  j0 _2 d; W+ z
return;& d& }9 c' f5 `0 K

2 E3 {8 C7 t! ?. |1 ]$ p; ~  Dchar bufferName[256];
7 S  t# o4 f% eIAccessible *pacc=NULL;
0 K: G3 E% u1 x% PVARIANT varChild;! X) t! @6 ?( [
    VariantInit(&amp;varChild);
' V$ K5 g. f# e3 k0 I. u//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
- M- U+ L  ~& _: P3 u: S# K5 ~3 k3 XHRESULT hr= AccessibleObjectFromEvent(hwndMsg,
0 @% F/ c+ |; S' @6 G                                      idObject, % l; i+ d" P) n: ~/ |
                                      idChild,
% Y( f# ~  |( i' P                                      &amp;pacc,
* Z; F- D7 [) x3 Q/ H                                      &amp;varChild);* j6 }: H  y# m. J9 T9 k% s. W

" N/ s0 B( v7 Q! ~if(!SUCCEEDED(hr))
& a; \$ R) L) B" n& P{* r% }7 s* S5 X  k5 ^# o1 }" K
VariantClear(&amp;varChild);
+ k! U, j) z+ \3 q- q9 H% C% nreturn;7 b6 [0 B* D) V0 T/ Y
}. ^; }2 K9 @3 q; a3 H2 [- g9 ^
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
6 o! K. o, I5 Q) pGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));; e4 ?7 }# Q! o9 i4 h
if(strstr(bufferName, szMainTitle))
" U8 r" ?2 [8 O8 \; k) vPostThreadMessage(GetCurrentThreadId(),
* P8 z/ t/ G* t4 o1 l8 N) ~                  WM_TARGET_WINDOW_FOUND, 8 D% _: B" f- p& p. y' j, `
                  0, " t8 x7 J) _) [; M
                  0);1 j( d8 s- C  d% q/ X: N
) b& F6 O, q: [0 {) X6 _
return;6 ]1 T1 o+ S# N3 H7 _/ O+ |
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
9 h1 \1 F2 D# s5 G5 r$ B( x" {' f) Q# e
附录:
5 f2 c% Q/ K& ?9 q& _) ^( p0 F3 [' ^' K; T
关于IAccessible 接口/子ID对:$ H4 M0 y9 Q- Y8 l( U) ]
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。* Q% T# @' a7 B" E2 n- U
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!0 l+ J! o  D  I, u1 D3 ^% r
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
% e: g8 e+ T- ~2 ^2 I9 j& p7 ~- k    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
/ D! w+ w5 X; {, q( k' L6 [    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
' g. a9 _5 U' b8 p  x
( x) k; N: c5 ^5 F2 S6 _5 E) [注:; h& S8 I# o6 ]: D1 U% m2 ~
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
! ^1 w7 D8 Z# U, U- R    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
3 t) _# r5 q% A5 G4 r" Z
$ n& W; A) R) W& c参考资料:
- S) y6 j. K* ^, r% G8 {<UL>
) l& n+ F1 b$ W; [8 j& |. K<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 s, F# v1 t' s
<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-9 17:07 , Processed in 0.517202 second(s), 58 queries .

回顶部