QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5767|回复: 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>% ^; r9 j4 N" `

, n: X; I; M& t1 s. X1 Z1 `<TR>5 E8 P3 e3 _2 j( ]$ _! |: x# q
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>% _$ K: Q( h7 _
原 作 者:崔传凯; o* ?8 T; _$ T$ e8 A; s1 t
原 出 处:vckbase9 U$ q. N! Z" I' p( \/ x/ i9 c
发 布 者:loose_went
5 u$ U' e3 t5 w" r' I3 H发布类型:转载
7 }2 f& z, Q5 ~, v9 H发布日期:2004-11-12$ N* F. C8 {& K8 V6 I( Z/ u
今日浏览:6& _' D! J2 c, V2 {. w. g
总 浏 览:234
' M4 m2 w* B: L9 }" B</TD>
! w4 I! p9 F' \7 A% |# p) c<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE>% P6 D: ]6 R# D+ d) J* n6 h
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
! S: n4 W! _! b7 }
. [2 J" f; u( m- p, F' w& c& A<TR>
7 @# M  Z% G% v<TD vAlign=top align=left bgColor=#ffffff>% E2 R/ O& h  H. }9 g" T
% l7 f# ]* [. E" n, Z" G8 b' ~
<>: e) @  k3 I- V' F
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>1 C2 c: E6 f3 U, @8 F# R

% C3 O7 Q" s) K2 f<TR>
; ^- f  W6 c" ]5 P- Q: @4 W<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
, W% i. r9 c# q, \* Oway accessibility aids work with applications running on Microsoft Windows?. It % H* v/ x, M, j. L; h
provides dynamic-link libraries that are incorporated into the operating system % y+ ?* s8 ?4 K5 d$ ?& H$ U9 A0 m
as well as a COM interface and application programming elements that provide ( X. C! o/ b# _9 F: {
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>3 a4 a! k/ I4 R2 Z
<>一、基础0 u) f2 a2 T* `( j
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
4 g; `' p) I& @) q) S: S3 {    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
3 D0 C; F" P, `% w7 a* \/ G    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>1 z, r1 x9 u' ?- H" l+ V$ x
<>二、Active Accessibility 原理( l& g4 [; b! Z) O8 ^
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
3 z! Z# p' c( U& v, e, M    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
% y6 {( k" ~# H1 r    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。/ \9 l: |. _9 }
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
4 r; ~( o. _7 s6 d    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
; h. X& I# g  W0 L, d$ t<>三、如何得到 IAccessible 接口指针, u: v; V4 S* ~# q
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。5 h6 ?  z, a) L# B5 _# I! x
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
. s. O# f: [  h: }- `* V    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。3 Q2 M  N% ?0 b8 h4 b' t% s
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
6 K  B6 T0 P2 G2 w  I% h    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
& d; Q; f( i4 k3 L- k9 \    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
7 F" h* k! \- E# D2 [' _! H下面结合代码介绍一下它的用法。
$ h& Z9 x* p8 B; o5 g我们来得到下面运行窗口的 IAccessible 接口指针。
  s/ ]# ^8 H. T7 d* E: R* B: r5 g4 U+ u( d7 g& q: T. e
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
) K2 F, x* x2 ?- e  G- H2 F<>图一</P>' S6 L, g7 @  J. n# V" t, w
<>
1 ~1 n: Q; V* T<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
: Z" w  j+ x0 ?% ^+ ]4 l* E$ w: ]  {' z( l9 M0 u
<TR>
3 J1 K; L/ J" M& i<TD><FONT color=#00309c>HWND hWndMainWindow;' M& [4 Y& ^/ v, Z' w
IAccessible *paccMainWindow = NULL;
' i9 I* E/ Z0 k2 `! S4 QHRESULT hr;' M+ ]" z: f9 o# N$ H" X
//得到标题为"运行"的窗口的句柄
" W0 F' M+ \7 ^1 Y# A/ X: ~6 j( L+ Dif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))* G5 `) G5 Q( Z. Y3 l
{
4 Y/ a* q4 p$ d7 G& Q/ zMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
! ^1 ~( j( e) G2 V; M}& [6 q( w; W8 Y: {+ K" ?7 q& ]3 D
else
3 c/ @; j* X3 W% [{
( c; ?/ Z$ {3 {4 X1 E0 V//通过窗口句柄得到窗口的 IAccessible 接口指针。
+ v0 d( q/ I& B- I: M) M; f" Yif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
4 I) K2 u8 [  o$ |% |- i                                            OBJID_WINDOW,
5 U% N% A$ Q& q& T                                            IID_IAccessible,4 V" e8 d5 i% E% V
                                            (void**)&amp;paccMainWindow)))
$ e! l0 J- h3 E1 C9 z{; o# c+ ~% J8 _' y/ S; L- y2 z$ D5 I
//……我们可以通过这个指针paccMainWindow进行操作。8 I& c# [# v$ x4 T# D
paccMainWindow-&gt;Release();4 K* W5 G8 K5 D! i  p/ C& V
        }: S, F4 z& g0 G7 n
}    </FONT></TD></TR></TABLE></P>( `, p% S) j7 B8 P% d8 W1 J
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!0 E4 G7 d7 R! {5 y" \2 }# D
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……! J. h0 J# ?% @! @
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
! k; I/ o4 {' ^" e8 B<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>3 S, N4 I; [  Q" L+ ^# y
<>图二</P>
* t2 ^# C0 k  e+ w<>我们现在主要关注的信息是:Name、Role、Window className。</P>7 ~9 h2 ^" M$ C" ^! k/ z
<>
* i3 T" _* k- K/ E, W! I5 Z( ]4 d<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>$ @. ?" p* |8 K! p% c& `
/ N  v( z8 x9 {) G$ [
<TR>
6 o" E3 m/ ]# m6 M/ B- A" n! b<TD><FONT color=#00309c>Name = "打开(O):": z5 b* I2 a' X2 |
Role = "可编辑文字") W7 z, S0 s; o1 R/ b" ^* W
Window className = "Edit"  </FONT></TD></TR></TABLE></P>+ r, w+ B1 [1 z8 Y; @+ C; g1 m3 o
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
" t1 u! ?: s5 g( Y7 Q+ ^    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
, s' t4 ]3 Z) t8 v下面我们开始取文本输入框的 IAccessible 接口指针。 </P>2 X" ~% w/ K, o7 e+ }! n8 [3 E
<>
& ~2 P5 Z4 b3 J<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ v* v) P) H* Y3 Y9 g3 ^. N

6 |" m" h" `0 s9 I" g2 f/ h2 k$ P<TR>- b) Y. |* r2 ?7 ~
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
/ K4 a7 D  ], t4 MVARIANT varControl;    //子ID。
4 U/ {9 L  ?2 X" S) G: ^1 h
& {  [* H4 N' I3 u5 ^' _( w9 ~& ^FindChild( paccMainWindow, 0 n* [# t8 ]9 j" G3 o
           "打开(O):", / q% A' V' t6 D3 H$ L9 ?" ?! X
           "可编辑文字",
, T) z( k9 x6 @) j) H) }( _( N0 I+ f           "Edit",
' E) r( _& [1 d- e           &amp;paccControl,
2 b, J: I: Y4 t% b9 I" Y           &amp;varControl ) </FONT></TD></TR></TABLE></P>
3 W: B7 k& l% B0 Z, n2 J<>第一个参数是先前得到的窗口 IAccessible 接口指针。
5 X/ ^8 m$ M( }/ u第二、三、四个参数分别是名字、角色、类。
5 ?! l$ R: p- ?6 P1 T/ h后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>( _8 M5 f7 `9 y8 w" Q
<>
* g! q7 o# \5 F- x! m# h) b1 H% h<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>5 D) K6 y) k) y  P" M' E# L: N
3 n% i  \7 R4 o% _- w$ A& S
<TR>& S* d2 L! t' d/ s+ }
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
$ T+ v" _+ b" L. x( Q' c1 z                         LPSTR szName, LPSTR szRole,
0 T0 w  \# ~* x. y# f8 V                         LPSTR szClass, # x3 e: `# b! \6 C2 I7 H! l" f
                         IAccessible** paccChild, & ^* P- `; u$ v- o2 x+ F& n0 P
                         VARIANT* pvarChild)
# M0 B* o! T- _$ c) S( W{
: ?7 ^/ y- S. _8 v' z1 m, KHRESULT hr;
  y/ e) R( G* r$ o3 plong numChildren;1 D" z4 n8 O, E3 A8 s
unsigned long numFetched;  T7 e* V' Y9 H% o+ }
VARIANT varChild;
# W. [$ S3 V- t8 R0 v# @. Yint index;
4 L$ H( C3 E4 x! _/ _' [0 y/ WIAccessible* pCAcc = NULL;$ T0 z7 h$ U3 _8 a  P; v! Q
IEnumVARIANT* pEnum = NULL;2 p  Q) U  R2 i4 C/ Z: d
IDispatch* pDisp = NULL;
$ d' ^* V$ s  F, uBOOL found = false;
6 f2 i* U  X! E; wchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
# o$ g0 F  s' Z& H; t2 j  p# t. b5 t; w) C# n& |
//得到父亲支持的IEnumVARIANT接口
: y/ y/ V) @! U) w4 `! i& F9 yhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);6 A8 k1 _1 I* |, A1 }& Z
6 G$ D4 t" K/ ]% S# v# @
if(pEnum)
& c2 C  u, P% ^! t4 YpEnum -&gt; Reset();, }% i/ w3 X9 ], Z- O

0 J! ^6 n- x5 O$ y//取得父亲拥有的可访问的子的数目
+ S1 w/ B1 H6 ]3 lpaccParent -&gt; get_accChildCount(&amp;numChildren);
, P6 c' n$ f* W: A' j
2 K" V- A* n  F, `/ Q) F//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。4 e1 d3 v9 q& w# r
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
, ]/ \& e0 b: w3 e, N' K: `{
- x! T1 p9 i4 H4 _3 W! opCAcc = NULL;
7 o* ^9 ~7 O7 Q* n% g6 _* t// 如果支持IEnumVARIANT接口,得到下一个子ID9 I) K2 A6 v5 e; M* X, F8 K' o/ a
//以及其对应的 IDispatch 接口
$ C2 y- K5 X1 bif (pEnum)
2 l0 F# U' A/ r9 O& t1 o9 Ohr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
0 u# }5 E# W7 f& R4 uelse) j3 O# M' ?  b' ?8 z" Y
{$ u" f9 P7 c* H7 l5 @4 w$ N
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
8 g% {7 B* v" w$ J: Z6 CvarChild.vt = VT_I4;  o6 j6 @  N( T  a# w
varChild.lVal = index;
! a/ i( i% X$ W3 A}( D/ x( Z( H' B/ j0 J' W9 Q

+ j! Q! t0 l& Z2 H  g// 找到此子ID对应的 IDispatch 接口
" |/ }' w" {7 r! k' xif (varChild.vt == VT_I4)' k) ?3 f" P4 r" i
{5 `5 t7 n4 i( W/ p
//通过子ID序号得到对应的 IDispatch 接口& d/ t+ B& r- v& W" z! q
pDisp = NULL;
, U! N: u5 E: x( ihr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
* H$ w% z4 y3 I+ Z- s& o7 |* _; w5 Q}6 h9 c1 v- D1 ^8 i  B; U1 }
else5 M0 V6 M' N  p3 k0 a2 q3 I
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
9 v. W! D8 _0 d3 LpDisp = varChild.pdispVal;
; q6 S. X. h& T& a- k* i3 B& }
8 T5 F) f! G. N- [6 e// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
; b: i* {' v: @: j" n7 l: Jif (pDisp)
0 |1 N" K3 u/ c- J6 a  S{
9 v2 |4 d* Z0 N9 m4 v3 o. @* nhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);6 H$ V9 k. J; {! L6 y
hr = pDisp-&gt;Release();
" S. k/ M) D  |) I. r}
* b# I: C6 Y# q- q) z. l6 D/ u4 c+ d5 t( |$ i
// Get information about the child
3 f$ o" l- i; h7 f* Iif(pCAcc)
  O( ?6 P& \+ m8 a8 b{
8 y$ ?, v  M  P) _( ~//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
: b: q% k0 X& j8 X7 L0 C/ }VariantInit(&amp;varChild);, g! `+ S8 g# M8 _
varChild.vt = VT_I4;
$ V4 z- u" x% d6 w" bvarChild.lVal = CHILDID_SELF;
0 J! I6 k& F9 @9 T
! e% `  T+ D) K* H) z' }*paccChild = pCAcc;2 v( ?4 S8 s: D5 p' J/ v
}' p' u: O  F6 ~! M  J& t
else1 T) z2 v8 R2 _4 y3 M$ F
//如果子不支持IAccessible 接口: M, ?) G5 d4 l9 I) h
*paccChild = paccParent;. k# T- O  i6 E- q3 v

, |# |) ?+ a2 x& R3 S//跳过了有不可访问状态的元素
& z4 u- c* x; {. _GetObjectState(*paccChild,
* Y) c* P1 x5 h1 h" L: A               &amp;varChild,
4 h! U1 s2 q: h- x' x               szObjState, . @% m  c: Y0 W0 G
               sizeof(szObjState));
& R$ W' a. R) M9 ^: _$ c+ V- t. vif(NULL != strstr(szObjState, "unavailable"))( [2 W5 W4 q. T& @/ w# g8 i
{
  S6 n9 x/ h9 E& Uif(pCAcc)
- }% k5 x) r2 vpCAcc-&gt;Release();: n  L, A; ~: z+ [, s& j/ t
continue;' N( k' l1 f5 P; \
}
/ ?; i0 t6 g+ C1 O3 c( m7 B//通过get_accName得到Name
% x: Y5 H7 S6 sGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));8 {; ]6 H6 p) U9 y
//通过get_accRole得到Role6 y/ H; V! u9 {7 s6 x" t8 \3 c$ W
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));/ m# r% Q2 Y/ Y- K- u
//通过WindowFromAccessibleObject和GetClassName得到Class
: ]. l3 X* c& ^# gGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));. h/ i6 e/ s$ Q- P
//以上实现代码比较简单,大家自己看代码吧。7 G- r4 w" H! i! L: i1 `+ U* w
9 Q- s+ P! o7 C0 P! K! t
//如果这些参数与输入相符或输入为NULL
3 Y" u; l9 H6 y6 k2 |8 Cif ((!szName ||
; I- `! b& ~4 x7 ^! B; z0 h- b     !strcmp(szName, szObjName)) &amp;&amp;
2 E0 M" S" r5 S( A     (!szRole ||
( F8 ?' n6 ~$ Q* i9 A6 F      !strcmp(szRole, szObjRole)) &amp;&amp;
1 e( E* \9 b- A     (!szClass || 6 ~% Y6 L: L" M& |/ P8 h: H- U: T
      !strcmp(szClass, szObjClass)))
+ L' @/ M" e$ ^: }5 p1 S; f9 ]/ L% f{
& c9 r% S  W* t1 C4 g% T1 \' Sfound = true;
+ F0 s6 X2 T0 y. M& ?( Q3 x*pvarChild = varChild;* I, f* f* ~. a1 T) N$ ?0 |( Q
break;0 o3 E1 w" }% ^7 T4 @+ M
}
0 o* l9 \3 g( W1 `$ a& ~5 m& w4 ^if(!found &amp;&amp; pCAcc)" M, x6 R; z9 k+ {
{) p1 T3 s" T1 H$ Y/ E$ {' U
// 以这次得到的子接口为父递归调用
0 b* T" x. D& {- ?+ M& J, x$ ufound = FindChild(pCAcc, 8 q8 O6 @# g3 J7 k+ K
                  szName, 2 c/ N" ]  z/ c) m/ H6 N
                  szRole,
3 S7 O' J$ W. ~$ j( d) X! T- w                  szClass,
6 Q* x, X6 [! ?' n4 q( S* B                  paccChild,
6 p1 }/ P1 N& j( L                  pvarChild);
' R" W, b2 t& m; J" Lif(*paccChild != pCAcc)) `! z6 G. T7 X! u2 z; f
pCAcc-&gt;Release();
0 d$ a  q! V! o5 Z}
4 ~4 R7 ^0 x4 Q}//End for! N3 }1 o8 E/ \0 D% Y/ U; k5 [
* d6 E1 W" Z' l" x
// Clean up; w+ v: D+ t$ s. m# A4 r* I
if(pEnum)' n9 F, b6 S, x' t# z: I4 E. N
pEnum -&gt; Release();% D3 W: {& Z# u

, H, e6 V; ^4 I$ }* Y, t( Breturn found;; T2 P# u: t  o* v; n" p; [9 p1 y0 v
}
- |( M4 ]; V( M5 g4 X; ]& K. q# y
( q% Y2 V- E  ], X5 Z/ @- Z// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,! M/ s( t6 B) K# W$ ~6 H0 o, C
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
& @0 e% O" Q+ ^//将这些或数转换成相应的用逗号分割的状态字符串。
* q4 }$ J4 h- p: u9 tUINT GetObjectState(IAccessible* pacc,
7 N. l, ~" i# U3 u$ Q                    VARIANT* pvarChild,
1 y7 Y" U# W0 o                    LPTSTR lpszState, 8 h$ ~( Y7 J( @) `6 Z! S0 G
                    UINT cchState)- j% M% T" J( @/ X
{
/ ^3 e9 J) t" c0 _, o    HRESULT hr;
# v1 z# z" o: k" K( e5 v8 u% z    VARIANT varRetVal;1 G  c: U# t8 H! b

( M/ c2 s! K3 {" q    *lpszState = 0;$ G/ f7 c0 y7 }+ ]* j0 q. b
+ k( p  m" b/ ?2 J/ A) P, F: l
    VariantInit(&amp;varRetVal);
0 h! U& m) D/ h& |( W, i1 U
4 t: x( m$ H0 Y0 M4 g1 z. c2 F) J. a    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);0 }3 G9 n( D2 C7 M; i8 B3 b
+ v: i- d6 A6 c4 s: T" }& G
if (!SUCCEEDED(hr))
! g' {  X+ J. \7 `0 h, W$ X" w        return(0);) s( P3 J1 U" f" d( U

* ^+ N$ S; b+ H( cDWORD dwStateBit;
" ]# \  v/ X" W% T% ~! F4 g8 Fint cChars = 0;
+ t1 ^/ W% F; v# V5 ]8 b    if (varRetVal.vt == VT_I4)/ y" @, W9 T% B5 e9 h
{
+ x; B" M# Z! V# ~5 Q+ b4 z// 根据返回的状态值生成以逗号连接的字符串。
. Y( J& D: B; D; l% ^3 O0 T        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
! i: {- r& x# q4 d8 a9 f3 \  [               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;   z0 _( i" q3 Z) P' ]
               dwStateBit &lt;&lt;= 1)
# ?" x; A( K0 T7 @6 r+ o        {: j7 Q) w( |& ~, P+ R$ R3 \( U
            if (varRetVal.lVal &amp; dwStateBit)
3 i, ]  J  ^1 @. z" b5 m$ \            {
- j6 G4 }4 F0 {/ b$ [                cChars += GetStateText(dwStateBit, 7 [6 _- d: t+ B' d
                                       lpszState + cChars, - s9 N  e: Z* ?
                                       cchState - cChars);
# z" [  z/ g7 y, `*(lpszState + cChars++) = '','';
% ~0 B$ I; @+ z8 Q7 n7 c- b. w. n" u            }( @; Z; P. ?6 ?2 b2 e3 s; I  a
        }; @8 V$ `; _+ \8 F0 F
if(cChars &gt; 1)& a& S1 f( Y: |# s) X) W1 X" G
*(lpszState + cChars - 1) = ''\0'';: e8 C& L) U) i+ A- r2 g0 L; K- l
    }# ^& m: K. o1 Y- C% d8 [
    else if (varRetVal.vt == VT_BSTR)
+ F3 o; k& f: v: q0 c% K2 L    {
; W9 N5 C! H+ ^) A/ f        WideCharToMultiByte(CP_ACP,
6 g5 r1 W! Z4 H                            0, 6 A, _% h. e2 ^6 ?# p, J5 X" W
                            varRetVal.bstrVal, $ R/ g8 c) p+ S% a5 g5 m$ e/ }
                            -1, ; H! q# d9 e" |+ C" X% ?1 b
                            lpszState,
0 Z! X' [1 \4 S% l# B                            cchState, $ K! q& K4 k  n2 U, I. O' _
                            NULL, , A: w( Y# c2 Z8 |1 t/ J
                            NULL);8 K' P' y+ N3 ^. Z3 g- P2 A* R* i% `
    }
& d+ P; z$ {3 f
5 F- Z8 z$ A' l7 V    VariantClear(&amp;varRetVal);+ F# O+ Y; [, I1 b- m
3 R, H0 `/ R8 z! O' w5 u/ x, W
    return(lstrlen(lpszState));/ T' M9 h# L- m/ l
}</FONT></TD></TR></TABLE></P>
# z" }- ?& ?9 M. \<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
1 W& ^0 s6 d2 D; @9 Y- p3 Q" x& Q* q3 _. p/ ~1 V8 x# ?' J
四、在 IAccessible 接口上执行动作- ~2 G; |/ z+ ~
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。' v$ P/ u  H% A4 t' o* J: ^; B, [
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
3 j5 ~- e9 w! Y" @    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
1 K; E( x$ n( N& d- ~2 C7 p<>) |& b9 p% Y9 }% v0 V6 e/ h4 C
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
2 f: _# O1 @6 B% n
! p  K) l0 o* M4 _<TR>
3 ~' C! t4 M8 O3 z, v<TD><FONT color=#00309c>//在文本输入框输入"regedit"
/ [! Z  E; A3 |( Qif(1 == FindChild (paccMainWindow, "打开(O):", . z' @) z$ b0 g  x/ d+ g( t
                   "可编辑文字", 6 i, N; z2 G5 U& ]
                   "Edit", 4 B+ h: {  O- W) t
                   &amp;paccControl,
, o4 K8 k1 `3 I/ k9 l$ ^& R8 B# o                   &amp;varControl))
  H$ p; g  M- j7 e3 }6 \{
; P8 ]# Y! ~: b4 F; T! d//在这里修改文本编辑框的值
& y$ K; Z  u1 B% |) ?; ]hr = paccControl-&gt;put_accValue(varControl, ) ~- E" E) I1 h8 r7 x
                                  CComBSTR("regedit"));9 o3 c: V7 N7 \+ N
paccControl-&gt;Release();3 J; H0 a, a: i/ i! i& T
VariantClear(&amp;varControl);! }4 m3 j+ ]; R) a0 W
}5 I- L, A: S! ?' `
, }. T. y$ E! C! w9 }
// 找到确定按钮,并执行默认动作。4 j4 J! }0 p" k* Y
if(1 == FindChild (paccMainWindow,
# k! a# r  t' o! k" T( ^                   "确定",
% q  B( t% d0 q# }9 _0 V# o                   "按下按钮",
: {1 ?* c3 w( m' G4 G' C                   "Button",
- r/ ]8 R. A. T7 b' f% e" A. c, K                   &amp;paccControl,   v, {$ Y/ ], p* m! v& y+ A  O0 I
                   &amp;varControl))
* m# K2 `7 @4 ~1 F5 Q* _{
  V1 A% R& S; u; }3 g- L//这里执行按钮的默认动作,即"按下这个按钮"# U+ @8 v) T% ^" N; @- l$ ^$ V+ a
hr = paccControl-&gt;accDoDefaultAction(varControl);
# X: {0 u. K  [. B& o! V; y) u7 e' mpaccControl-&gt;Release();+ g- W- i& C* ~& q" b
VariantClear(&amp;varControl);
7 I0 G" [. }2 Q9 w! _}</FONT></TD></TR></TABLE></P>. h1 j; ^9 X7 @+ D# G6 @' e
<>现在,你会发现已经成功启动了注册表编辑器!!
+ d2 P, D7 J( e9 K* z# |</P>
1 p% V8 c9 g9 R$ R- V9 a+ ~<>五、模拟键盘和鼠标输入# ?3 U7 J! H, ?2 j! i
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
3 s& e8 j; a1 a0 t: r0 f    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。/ m5 d( k( U1 q% T6 d! K  q7 ]
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
5 Y( A7 c7 Y$ ?) w6 |1 [$ t下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>4 t* t9 g1 R0 T  R. [8 t0 P9 E
<>
% E: V3 S& ]( X# w5 ?6 y<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>6 `. t1 B! V$ M" N
, G/ @  d4 y  s! t
<TR>) R* Z8 s" i6 y/ Y- y4 \/ o: m
<TD><FONT color=#00309c>INPUT input[4]; 4 l0 E3 @0 m- w- o8 Z: ^6 w9 H
memset(input, 0, sizeof(input));
7 N7 j. A- S7 ?2 N% `
8 R% h8 t, i; y: q& A//设置模拟键盘输入% m0 E" c& X9 T2 \* l
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;8 I1 S/ o  y! p3 c! j) U3 u1 ?
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
- u$ b7 x6 G* V! _/ k7 Minput[1].ki.wVk  = input[3].ki.wVk = VK_F4;& C4 B9 U' t' e: h1 n
; R3 u& C! @. }7 a
// 释放按键,这非常重要' z- Q6 F" L* A& u$ X6 |! o, r
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;" M/ \9 h2 D, }  P; U
# J& U, I' x% g5 d
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
* L/ d1 x$ k+ ^$ `  |<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
6 `/ `9 }  y/ F; @. l. L* [( A</P>8 l+ m+ A" d. G/ e) Y2 F2 z  B
<>六、监视WinEvents! D' j8 m4 K2 @6 y( A6 A
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。' Z  C/ ]' I" {4 T3 |0 D0 q
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。4 B, o3 W7 }& p) w* b( J8 u/ b
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
5 t2 ?0 I8 J5 L8 P7 c3 |<>
' q" W# J1 {, Z/ F8 {<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
* |- n+ Z! Z0 D3 i# `2 g. j
1 U8 G5 B+ V5 C+ f7 [. n<TR>: r4 B6 K2 p$ k" Y
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
* ?0 t( |* I' c6 c6 h{
2 w; e' Z9 D/ r2 h% l0 j7 ShEventHook = SetWinEventHook(
2 d( P' p* {2 M, }& {7 |EVENT_MIN, // eventMin ID
5 F1 d3 q9 b' L: S2 ^+ B* Z7 f' ZEVENT_MAX, // eventMax ID' p7 W7 a2 v, j5 j9 c
NULL, // always NULL for outprocess hook
) h  i4 p" E9 L4 t, PWinCreateNotifyProc, // call back function- L3 J% n. l1 w
0, // idProcess
. Z$ V8 ^2 s  T4 ?; R2 j/ w0, // idThread ) d" X5 ]4 ~- D/ m8 f6 P  T
         // always the same for outproc hook# H5 h. H5 u0 m6 T
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);$ T. z0 @9 O/ I8 R
}  </FONT></TD></TR></TABLE></P>, s  k7 L! ~! R+ Z& a
5 @0 m; d3 |+ I# I7 q" f
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
; N& t8 l1 \6 d5 T6 M6 }下面是回调函数:</P>3 e- t, f& ^& T3 F0 p% M3 A
<>
5 C0 r4 z/ q1 v, C( j7 a0 n& a1 t<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& Q. s! s. J$ w. l  P  W( e
$ A* w9 f7 l- w' @4 M* v; B
<TR>0 u+ r7 s. x8 b$ c
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(4 m  {* M5 K+ M$ Q, `
HWINEVENTHOOK  hEvent,
& j/ `- ?+ o  K  l3 F. r7 f$ Y. cDWORD   event,
3 I- |* P: \  H) bHWND    hwndMsg,
6 Q1 ]; p9 J6 q  |7 aLONG    idObject,
. Y- Q  z! p5 |& b7 }LONG    idChild,
& U/ n4 c+ T) i9 i) U3 K  P. cDWORD   idThread,) E8 D/ \' o! ]: p( S% F- `9 r
DWORD   dwmsEventTime
. _+ l& }7 |; \  K)& j& |2 j& O1 M# C
{
) @4 V' b0 c3 p; T# I0 Q$ s7 G
- K3 S2 F- [4 Y$ M" Lif( event != EVENT_OBJECT_CREATE)9 A4 e! c2 |3 W/ M
return;
) [$ e5 b8 v  m
5 @5 [! a% l0 h, Lchar bufferName[256];7 l8 y6 N9 i& i" {7 }8 e$ \( G
IAccessible *pacc=NULL;
2 P9 Z  a) o4 g5 v" IVARIANT varChild;
1 x; ]. z# P' l3 @    VariantInit(&amp;varChild);* h; I; t+ y  ~/ d2 M
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
5 B$ Z' N$ M* s  ~HRESULT hr= AccessibleObjectFromEvent(hwndMsg, " b3 r. s! q# [' q6 b5 G& n
                                      idObject, ) W6 y- a. `0 O0 @7 [1 {3 R/ [3 H
                                      idChild,
; v, u2 K) N$ ]9 u) X, B1 y7 j                                      &amp;pacc,
) R. o" x" q( m                                      &amp;varChild);
- E* x# Y8 n, e5 g6 b' L+ e
$ b9 q1 B: w: v% Pif(!SUCCEEDED(hr))/ S$ u/ L0 E! x/ R
{% r: [8 V, y) b9 k* V" B: t
VariantClear(&amp;varChild);! _+ \+ \# i2 B2 ^6 ]( j
return;
: j4 {# u9 C0 y6 P! V0 N7 h1 t}7 \6 k9 e; h5 y$ t& _
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。  u; |  M. O. I9 k- {/ N
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
& c/ [6 s0 Z$ k) ?; `- gif(strstr(bufferName, szMainTitle))/ c  B! Y+ R: D, T$ m. Q1 w
PostThreadMessage(GetCurrentThreadId(), ! D/ M6 l. B3 d2 b) h5 c$ ~0 y
                  WM_TARGET_WINDOW_FOUND, 8 v0 w! s7 w, U( g
                  0, 4 {0 j- X; b3 t6 R# @5 X
                  0);4 y2 P1 Y9 N2 D; p3 J1 F" \
5 H0 ?! p' v* H
return;
/ q- m* i. Q/ ?! s}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
! u. m4 P7 U1 Y2 [5 M( p* O
7 a. x6 J. W7 P. H8 L附录:/ K+ q+ E; j* t8 L5 N. N9 A

3 K3 o3 J# [* p关于IAccessible 接口/子ID对:" L+ p  k0 W( n" V. |/ i, A
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
; S+ J' i, o; u" C4 g6 u* V9 G. Z    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
! E7 W; E8 E! T$ }    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
5 M: ]( Y7 o8 C! y    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
) G% W& u1 b( D6 y    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!0 j3 H9 s9 K7 P
' X8 B2 i5 ^0 R( t2 l
注:2 ^7 W2 J: F) n; e$ m/ s
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
# T! C3 a3 L3 L+ ]8 x% f    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
: T! q5 Q5 `) L5 ~4 `
, \2 ?4 ~7 Y1 l参考资料: 0 d7 ]. M* J( k; s; h+ `2 q
<UL>5 o% V! f, G3 W2 G& o, 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
* {/ n0 R, Z0 P/ O3 a- Q$ b3 w<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-10-8 06:28 , Processed in 0.418760 second(s), 58 queries .

回顶部