QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5692|回复: 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>2 H! v" A9 M) y8 `: {
! U" e; }$ B; T3 r! p! u
<TR>
2 {, [  e4 [% J<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>0 _8 R$ z4 e6 K- @
原 作 者:崔传凯; F# D" U7 j* L6 z& ^: m
原 出 处:vckbase; K1 I( m" D' r: i
发 布 者:loose_went" P. }2 c5 |/ ]/ G- n. i9 a
发布类型:转载* P. {& C; q" n
发布日期:2004-11-12
! d4 p- J) @4 `5 q, l* L7 S今日浏览:6
" }, N' n3 r& V6 V: n9 K4 c! d总 浏 览:234
) G$ C  l& o" c. v</TD>* K: ~/ b, Q3 ~/ j
<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>; [# @- \& w+ h; E" i4 i! x
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>, z& u# \1 Z. }0 r, i( v7 i

( F2 L& ]" b7 N3 J) i<TR>
5 B1 h5 _: l' x; J3 d1 W. v<TD vAlign=top align=left bgColor=#ffffff>% g4 `  U+ p9 K; F" {( E+ x  \

- |# M) a: K/ i: w: i* d<>
7 S/ a: h" J- p" u<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
+ {6 V0 _9 H  F$ J& \: R( [" H7 O  v: c, k# H. `0 m1 z0 O
<TR>, N+ v3 E# t$ H4 y" Z- S- g7 t. y
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the + i9 C5 G5 u1 H; V2 k8 ?
way accessibility aids work with applications running on Microsoft Windows?. It
* ]$ a  X  _2 ~provides dynamic-link libraries that are incorporated into the operating system
) Q; g- Q( {7 bas well as a COM interface and application programming elements that provide # X. c& `9 c' Z
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
8 B, b& U; b% v  W. A<>一、基础, q/ \5 a$ w8 c0 W: f4 m
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
' G$ H8 u7 E: q8 Q) y4 b/ [; z    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。' k; m& |$ j8 m0 [% N) I
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
2 o) Y( ]$ G& m% X1 X8 V3 P" c<>二、Active Accessibility 原理
0 {% c9 i7 S' P. j) c- Q. y    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
4 }5 Q: V/ v4 n+ p    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
) x/ C" M$ m; s* L    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。8 g/ W1 [. u9 C! Y6 w/ y
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
: C3 R# ]6 r% R) U& T    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
2 r7 z2 `/ h& _<>三、如何得到 IAccessible 接口指针& r, ^9 Q- ^* B5 U: n
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
, C; ~5 V$ \* I0 T5 O7 W4 v/ K: Q    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。: M; j+ H% F. j2 K' K) t/ c
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。1 N$ s$ k) W7 K6 g" B
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。  c/ k' z# T8 c0 K( P( d; H# c- [
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
* b) w( I: O. p* _    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
5 J7 T% d: e( R0 h1 {下面结合代码介绍一下它的用法。
# {7 Z9 k0 K$ @) K我们来得到下面运行窗口的 IAccessible 接口指针。
9 b5 V9 [& I7 S% u* f) a
- S- p/ P! N: R7 A<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>* A$ a$ Z3 D/ f+ O
<>图一</P>& i$ o/ K, k  G. p2 z! C6 {" O6 }
<>
) Q  M/ |; u. T4 h9 C8 @! p2 T0 A<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
& R6 p! c. ?$ Y! e6 b1 A* E) I8 g' _  N
<TR>7 Y6 ^& W+ h( T2 ]
<TD><FONT color=#00309c>HWND hWndMainWindow;
2 `7 N8 x4 Z5 I- ~% AIAccessible *paccMainWindow = NULL;. D3 o# S( V, {
HRESULT hr;
; N. F8 r( I2 s0 S- y1 P% y//得到标题为"运行"的窗口的句柄
" ?# K- [# Q7 wif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
. L3 V7 h, p7 q" e, C# ?( x3 Q{) s( w; h$ f& N8 @* f
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);# G. k! o+ C1 j+ g* L  W
}
8 ]) y; ]) z, ?+ z2 aelse
( D6 g8 H+ t, `9 {( ]" k- |1 z{
, @: d! s* o; a& ]//通过窗口句柄得到窗口的 IAccessible 接口指针。* B, q* g# G$ J) {4 A1 R
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, . N( W% ~8 P+ E) H# e; V
                                            OBJID_WINDOW,
' _! m" ?3 U# ~% _  |" V                                            IID_IAccessible,) F/ N0 ]* u, [, H0 q
                                            (void**)&amp;paccMainWindow)))$ g3 m9 A1 A9 @  A3 N
{
# a2 i# F/ U, @5 z2 k& O& n) }//……我们可以通过这个指针paccMainWindow进行操作。" m, S  }) k/ m" }2 p0 }  p
paccMainWindow-&gt;Release();
+ P. u' t" E+ y  Y' A4 ~        }
& X" p, H# Q4 r$ V1 G. ?}    </FONT></TD></TR></TABLE></P>
) j9 H0 Y8 {! x<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!" U7 T8 ?$ b' e4 e4 n( \
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……$ e: E9 r! W/ x4 X. X/ \: h
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>, \: c5 l8 y7 ?, L+ @+ Z
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>! E2 R' D2 l+ U2 L8 f  J! i
<>图二</P>
2 ~2 e: J: _& d: T9 T0 P<>我们现在主要关注的信息是:Name、Role、Window className。</P>7 ]! C8 q9 \, ?1 @6 ]
<>
+ x* O) z+ Y1 I3 ]7 g: w+ O, L6 Q: P<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>4 g7 X# s% I2 f1 c7 D# N' E: f$ J+ Y
3 p* t. w. a4 `* J! a
<TR>0 q0 p0 `* _! e" @$ k
<TD><FONT color=#00309c>Name = "打开(O):"
: k  h9 T7 p8 f& u+ b$ W+ eRole = "可编辑文字"
5 w9 \* w( l" q( LWindow className = "Edit"  </FONT></TD></TR></TABLE></P>
% `* i, S! O/ `<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。5 X+ Z6 p! `0 Z) t+ ]4 b
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。, ]' Z/ I; ^, ?9 S1 A: G- X$ c
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>" {4 t! K$ h: L! ?% {  B+ e) A1 ?
<>  g5 U  Q: t+ L; m/ r% [& u7 `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
& S8 [' ?  p( ?9 c9 ]/ I  f
, X  Y' y+ c3 ~# O7 [7 p<TR>
9 u$ m; m6 B6 p/ {# {7 ~7 |<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口7 m" C7 O0 A; `+ ~# Z- U% C
VARIANT varControl;    //子ID。0 l& J3 y1 r5 R0 h
- J8 t# |4 G* f
FindChild( paccMainWindow, + m, Z0 s. b6 ]: t5 V) ^- i
           "打开(O):", % ]# u( x6 ]6 U
           "可编辑文字", ; D! {8 K* D. ]% [9 A& @, @( ]; d
           "Edit", ' |9 Q3 I$ H4 t7 q1 S( c8 k+ o" X6 t& I
           &amp;paccControl,
& Q8 N; _; }9 p4 f. ^0 b$ v0 o  i           &amp;varControl ) </FONT></TD></TR></TABLE></P>
2 w  _6 ~$ Q/ ]0 n0 H<>第一个参数是先前得到的窗口 IAccessible 接口指针。$ E4 ]+ z3 ], ]) i
第二、三、四个参数分别是名字、角色、类。
. ?3 D3 y) v4 ?! ^7 N) x% B2 L) C0 W后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
  l/ K3 Y& n2 M<>
3 N7 F, C3 M3 o( m2 a% E<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; ~( _3 ?# s* z/ ?9 c! [# q: |
. C/ B- N+ k9 Y0 g1 U
<TR>
9 z7 O4 j3 K' c+ y<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
  Q/ _1 \- U2 q( [* a; G, B                         LPSTR szName, LPSTR szRole, # S. c" w: ]7 i9 _# V% T) G; j5 K
                         LPSTR szClass,
8 a2 k: u2 d% ]5 p+ D& ~+ q- H                         IAccessible** paccChild,
2 u0 x) a) q& a' d% a                         VARIANT* pvarChild)+ F5 F9 {4 J( S# H# t) w
{' ^5 V4 X6 Y5 M  _9 E. }
HRESULT hr;
- k6 b6 P  h' e) c$ p3 llong numChildren;
  o. E5 t# U; D8 L4 Sunsigned long numFetched;
) F, G( h  F& VVARIANT varChild;
$ s* q! X, s( ?$ hint index;9 Q; h) p, C$ K
IAccessible* pCAcc = NULL;
  X. {0 F5 L7 F) oIEnumVARIANT* pEnum = NULL;) u& ^8 U0 Y6 N  O2 o
IDispatch* pDisp = NULL;
1 N8 }( P( [# E* ]# mBOOL found = false;5 j  K* X" {& x2 _# \$ J4 {6 n
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];1 C4 e8 Q) x) ~. u' C9 K& Z; D

3 y+ E6 P" e; s5 B- [//得到父亲支持的IEnumVARIANT接口) [# E5 ]1 B2 m" s- u
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);3 L/ D, I5 A% Z0 ]% l6 z
) m2 S( C6 p$ i8 T' P: \6 O7 S8 i
if(pEnum)
* Q3 u4 z3 a4 r( @pEnum -&gt; Reset();3 R0 I  L8 Z0 _+ S9 Q3 G

6 A* E. x2 J  S" J% j//取得父亲拥有的可访问的子的数目
$ f8 a) |* P, j% Q  IpaccParent -&gt; get_accChildCount(&amp;numChildren);
/ f1 V& V; T# X( F
! n7 ?" Y8 n: O2 ~3 H) V//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。* b8 \& M- I7 d/ Z
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
8 x8 Y  Y& B1 ]{, P" }. M3 q* a& U  W
pCAcc = NULL;
, q! @* g% X* R$ r4 L, {( j: z// 如果支持IEnumVARIANT接口,得到下一个子ID1 m! W/ h2 s( F: U& q5 O+ o
//以及其对应的 IDispatch 接口  }9 }0 N4 b2 z% P
if (pEnum)
' E% x3 z5 b8 H( _7 thr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
: g" A. {( A. melse& s0 g& i- ]# l$ G* n( e0 S
{" A  Z* _) G* c; m
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
- l% k& o: V5 R$ Y1 x* nvarChild.vt = VT_I4;
( v& k7 s8 O* D& G' ^1 JvarChild.lVal = index;5 X9 D( M0 d4 N4 g
}
. x' U& J- w* s& P! U7 e8 J. f5 K  a7 r, `0 Z
// 找到此子ID对应的 IDispatch 接口
! ?9 |4 m3 `% [) v9 @  |if (varChild.vt == VT_I4)
+ ?0 W1 a8 W' Q7 X3 U8 ~# b4 ?{
" N  v  R% A/ Y0 `//通过子ID序号得到对应的 IDispatch 接口7 z9 v3 ]0 k, N/ M( I
pDisp = NULL;' l- v3 c/ j4 j) g% a% m
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
% o; u" z; C7 r}
3 F3 [3 J$ C2 ]7 Q( |+ {) K; ^else
4 j! S! A6 R; F1 i+ Y2 {) L//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
. ^! t6 n0 {2 j% BpDisp = varChild.pdispVal;0 u8 O6 i: `& d$ f  u, d

( Q- V/ y! z8 ^// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
  c" R9 U0 _  @* y/ A/ u. kif (pDisp)
+ _2 w6 ?" c8 ~. {{: m) ^5 w+ Z0 u+ q8 v6 ^0 a
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
! t5 w7 o0 K' H0 s6 T1 F  ^2 Dhr = pDisp-&gt;Release();
9 x  p9 A0 K8 z* [7 |5 P}
" p5 y) ~+ k5 v( t, V. B, U
% v5 L! L) x8 X: k// Get information about the child
* ]1 P9 W: J, Y5 C) Zif(pCAcc). i1 L1 q5 v5 _% a
{
5 `2 G5 k1 e  S, n5 J//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
% \$ |" B1 k" y) W( fVariantInit(&amp;varChild);
$ z4 B- U. F9 S/ NvarChild.vt = VT_I4;
% l2 {. K4 z# t( avarChild.lVal = CHILDID_SELF;
7 j( H, n; |" ~9 d; ?. q; v" Z! W9 p- d8 M# X- R8 m  ?
*paccChild = pCAcc;
4 i- {8 p# N  |! ^# ~}: s2 F. _7 j: Y6 k: D) G& u# @
else
+ w! W: X9 K0 c4 _//如果子不支持IAccessible 接口1 h( }& [5 H3 U! v5 n- Y. R
*paccChild = paccParent;
7 L; I$ l8 f3 j; P5 J' a6 Z' ]) N
//跳过了有不可访问状态的元素
. k" R# d4 h# E3 ^8 L3 `. A$ k6 LGetObjectState(*paccChild, # P2 b# [, l+ D6 Q9 k
               &amp;varChild, % s9 V! v7 o! @% t1 h
               szObjState,
, Z, c% f+ X' n7 k- l               sizeof(szObjState));
) ~- ^: `! L! y/ J9 H* Yif(NULL != strstr(szObjState, "unavailable"))9 R4 V5 x1 n& `
{* ?2 ]! u* W( m( b1 G
if(pCAcc)
0 D; c; A! ^/ M, A: ~, NpCAcc-&gt;Release();, z1 P2 m& t% \, a% o' |( C
continue;3 Q; m8 g1 F0 Y/ H
}0 U- ^4 n3 s+ V+ }
//通过get_accName得到Name
% F3 \2 _/ g: X, b' pGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
4 M: c( P) C" b//通过get_accRole得到Role
' |. l0 Z; S% d( u8 h# oGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));- ?; Z9 d# ~* r
//通过WindowFromAccessibleObject和GetClassName得到Class
  w8 Z. L3 ]! P3 J/ O  _GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
4 m7 d9 i0 y7 U//以上实现代码比较简单,大家自己看代码吧。- B- k$ h$ s% C) X4 u. E$ ?
8 i+ g8 }' h. q. H4 |
//如果这些参数与输入相符或输入为NULL
% ]9 Y! @9 ?5 y' Fif ((!szName ||
! N/ V, \% U6 d3 Z% e1 E     !strcmp(szName, szObjName)) &amp;&amp; 0 q5 S/ K9 g9 G2 e9 q8 I( @5 _
     (!szRole ||
' ]' ?" ^0 m* x) D      !strcmp(szRole, szObjRole)) &amp;&amp; " ]+ g) r3 r: @
     (!szClass ||
0 b/ \" N3 J3 ~      !strcmp(szClass, szObjClass)))
& N4 T( Y$ N" Q/ X1 ]3 R{" o. b) }' u0 G4 @  F
found = true;+ K& U( u: v  t) [0 ?5 H2 i2 V4 S& t
*pvarChild = varChild;0 N0 T- C! n) n) Z0 u* H. z
break;, U" J$ G# w; T; e& ?
}
% @8 _2 X) K0 i+ Y0 S% s" K  Xif(!found &amp;&amp; pCAcc)
! z$ p( l8 T  K, B; d3 V8 W{
% ^: _7 R4 C- x) h4 N// 以这次得到的子接口为父递归调用7 K  j) _  G7 n/ N5 s4 h5 N  j
found = FindChild(pCAcc,
2 M1 X/ b- `! y% D: g( t                  szName, 7 H: I. T4 i! k! z8 R9 X/ B/ T
                  szRole, 0 s; _+ G  H' ]7 C" l& l1 |
                  szClass,
, ^( H: v. O" u. g" ]                  paccChild,
5 G' i3 n; E# N; |  Y" Y                  pvarChild);
1 k% {3 S  s" b- kif(*paccChild != pCAcc)# z  S1 _/ ]8 @! v
pCAcc-&gt;Release();" Q( z" [; ?6 ?3 i
}) Z; }) `9 H7 _; \4 l. ]! _1 c1 Q
}//End for
2 w  a8 {) E  l2 g5 m/ b2 H2 c- Y+ g/ Z1 v
// Clean up, p/ _  t/ ]; P$ ~, F) W( k+ S
if(pEnum)
3 t7 q  b% o3 w" k6 H5 A4 v& UpEnum -&gt; Release();
) }; L1 e2 B) k/ \- L
- u4 H$ y: Q' B2 x4 ?return found;! b) e  V5 J/ D+ Z( V
}
( W2 ]1 B* m4 ~, T; ]
2 C+ ^; Q/ q2 S# ^0 z$ q- Y// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,3 C/ k0 v2 l' }4 E, [8 [
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
2 e! G& q: M$ o# f, V% G, p//将这些或数转换成相应的用逗号分割的状态字符串。
1 A6 L/ c5 C6 D9 |$ k" N/ LUINT GetObjectState(IAccessible* pacc, 7 n/ t/ v8 V6 ]: z2 m
                    VARIANT* pvarChild, ; m! u9 t/ z% K0 D6 q3 q
                    LPTSTR lpszState, 9 e9 A% ]' Z( Y% g1 G
                    UINT cchState)
* U. N% ~9 s& e{
# d5 D( @3 J9 `8 i: ]0 K    HRESULT hr;+ L* O" D; K! R0 Z
    VARIANT varRetVal;$ Q% e1 s' O" p$ o

7 J: p+ E) p! [3 n8 b    *lpszState = 0;6 m+ W  P$ Q; e; w% X& V! O( o
/ y6 B! R5 ]3 w1 t2 @! t  Y8 ]; |; b
    VariantInit(&amp;varRetVal);) S8 Z9 D9 v( ~; W7 J

1 w# {: _( b  w# B. O% s    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);( C9 G" A  ~; v

4 T& G( s3 x* T  p# _% E0 Hif (!SUCCEEDED(hr))
; k! `/ `7 O9 B        return(0);2 ]: d# x- L6 G! b* O9 }- |

' k$ m. l0 z: ?2 d  v( V. x# ODWORD dwStateBit;# ~- B8 M; _* E2 f# D) j
int cChars = 0;+ z& k+ E8 M; Q% Y8 @
    if (varRetVal.vt == VT_I4)
7 H- B# H7 X+ n+ ^{
. i) ]; z8 x3 Q! G// 根据返回的状态值生成以逗号连接的字符串。
" L, A0 u5 ?: H1 m" |8 C0 I5 v        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
2 }& v) H/ d( S               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
0 p6 i. k0 ^  I+ o               dwStateBit &lt;&lt;= 1)
3 J; |4 R& `5 o1 T, A- q6 t  o' |        {
' P2 K, c2 m# {            if (varRetVal.lVal &amp; dwStateBit)
4 n8 P1 d/ D. k' P- t4 ^            {
% B# s" a- S4 M7 i8 R( f. _' B9 E                cChars += GetStateText(dwStateBit, ; e# v3 n8 m2 `7 {
                                       lpszState + cChars, 5 \. [# v( d4 C( ~; p* ]9 L
                                       cchState - cChars);
/ |1 C6 |6 i$ n- |' N" G4 ]1 A*(lpszState + cChars++) = '','';) ^) C$ X0 w7 m3 k
            }
  V! y4 c6 G! r& w! U7 w        }
/ b: }7 r/ G0 X- V4 Xif(cChars &gt; 1)
7 B2 f' ~5 a8 ]% L*(lpszState + cChars - 1) = ''\0'';! Z- H; q4 C, p9 x6 `
    }2 |' b2 ]+ z2 P3 j4 v
    else if (varRetVal.vt == VT_BSTR)
9 N9 [2 f0 R* d& P( a; d    {+ c. z/ \( m9 O1 G7 P+ `7 \/ F
        WideCharToMultiByte(CP_ACP,
6 \$ k7 S3 v* O5 T  c                            0,
3 e* Q/ n; `, c2 [$ u$ v7 t                            varRetVal.bstrVal,
1 B2 w: ~7 _4 i8 d                            -1,
# Q( W, l) j( Z1 S3 @                            lpszState," _8 G, c! ]" ^& W+ x+ T
                            cchState, : ^9 |8 l$ B, i! U0 N6 P
                            NULL, 5 A  R7 p0 E3 P3 }9 G  q
                            NULL);- Q, E+ g/ ~7 i% m; w
    }  Q3 M6 H9 _+ X0 l# z) d
. m4 b, {; l0 M& E8 }
    VariantClear(&amp;varRetVal);! k) J+ b+ S- b- a

* }! G# I& }  X) J( T. y    return(lstrlen(lpszState));8 Z4 k8 ]; b9 p* ]6 V- q
}</FONT></TD></TR></TABLE></P>' w  W5 ~3 n3 O( ]5 W
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)7 s5 G& d. m# v& V5 h1 o) z3 ^7 q

3 T: B" [' ]- @8 G% a% V8 y四、在 IAccessible 接口上执行动作  x) n" i; o4 ?
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
1 e# c' H# I# J0 Z    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。+ D4 g7 C/ [! P' O" [5 D) }
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
* N1 j  V( j" Z% p<>
9 o9 C4 F4 {7 ]* w8 u5 h<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* \* y& I+ O( x" R% R
- O3 ]  B9 O9 V1 s9 m4 Y# i
<TR>
6 w7 Q! N# Y/ d+ d5 G<TD><FONT color=#00309c>//在文本输入框输入"regedit"
6 u( T. K" C# U' |9 H+ Nif(1 == FindChild (paccMainWindow, "打开(O):", - [; R( y# ?$ k3 ~2 |' A
                   "可编辑文字",
9 s' Q8 n7 Q; Q+ b, b$ o" `7 J! W, B                   "Edit", ! s; \" s( h. @1 D
                   &amp;paccControl, % P0 F: \9 l+ `2 r3 L
                   &amp;varControl))
% d  W' Y5 `3 |1 X" L: q  C3 G{
/ n8 H$ ?4 T# D" W//在这里修改文本编辑框的值
2 T( J- |% I4 x3 Ihr = paccControl-&gt;put_accValue(varControl,
" o! T. Z# X8 h% |: E                                  CComBSTR("regedit"));
9 p/ s6 S0 R+ p+ x/ Y9 TpaccControl-&gt;Release();
2 J- \- L7 H$ EVariantClear(&amp;varControl);2 Z- F, H. W2 _. {
}
: W' ^; @9 m5 D8 b
" ^. o1 A6 |; k) n9 w' E// 找到确定按钮,并执行默认动作。
( d9 y- z9 V' O- Nif(1 == FindChild (paccMainWindow,
0 \( a! l! u) G  W3 x( o0 H- B                   "确定", 0 N, }  r8 |; s5 Z5 s
                   "按下按钮", 5 _4 W: O$ ~, b2 S
                   "Button",
% m4 l& x9 E. A) n/ o2 J- g, X, T                   &amp;paccControl,
" }, p1 Z: n7 ]                   &amp;varControl))
, F# [2 a% j! s0 ~) z) \{
$ H1 s4 T$ C! g' z" v//这里执行按钮的默认动作,即"按下这个按钮"
) y3 l5 K" W( o9 s; whr = paccControl-&gt;accDoDefaultAction(varControl);& a/ F& G3 m$ ?& P( E2 v
paccControl-&gt;Release();
# `2 E/ K9 X) f/ J- U# nVariantClear(&amp;varControl);3 ^+ a& e5 ~3 a( E8 y
}</FONT></TD></TR></TABLE></P>1 R2 N& Z. u. l9 U2 L
<>现在,你会发现已经成功启动了注册表编辑器!!
5 Z8 o9 k$ e, |; u4 d. C5 d2 e4 }</P>7 b% n! M5 F/ D' A' G
<>五、模拟键盘和鼠标输入9 ?+ ]& ~* g6 @3 z' k
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。" s6 x1 ]' |8 s9 e+ n) a
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
" B; l! }1 Y1 |! g) E1 t, D    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
+ Y5 z; b+ Q' U, i% |) j下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>$ O  E1 Q7 n+ {+ ?4 [2 F5 N
<>7 f" j5 O1 S9 D* A; Z& Q6 [
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
7 b- q1 y# @' b/ ^' U/ P! C
1 U) `) r- T  e- p3 n<TR>
% M  Z" t6 T; }$ x! B6 Y<TD><FONT color=#00309c>INPUT input[4];
7 |% Y$ r0 d$ `! s, \memset(input, 0, sizeof(input));
4 I) E. s+ d* U& t- h( b, I' D( o) x7 q
//设置模拟键盘输入
; T, ]. ?0 l! y7 g8 Q3 m$ R+ R% tinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;' ^- l% h) P$ u) M9 J9 F+ p- [
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;0 d/ A( y7 X  O9 V. q
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;
) Y4 e; r! Y; ^( k$ Q# v
7 U6 l- `. E& {8 f// 释放按键,这非常重要* Y! z& K, i7 s3 f$ a8 Z
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;9 c. k" V- m3 h. i9 @+ Q
4 p6 O: d8 F' L
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>) i, H" T- G" o+ V. E
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)5 J0 j( [0 o! k9 T0 r+ _) b4 ~; Z
</P>+ z8 Q" z' ~% z' m9 c; `
<>六、监视WinEvents, H/ n/ e; U" ~
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
. r/ A5 `1 I4 F$ d( E    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。3 H  |9 @! t) C( E
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
, e0 D9 [6 o' Y( u& ~<>
$ N0 v) Z' G( o' U* E<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ O" P9 F2 o) L# ]% `5 |

% d' ~' a% d; }2 y5 d<TR>
; \4 p4 l6 ~8 I2 O. ~<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
( d3 x) |# ?" K' Q. J! P; _7 p{
7 ^7 N( O! [+ S1 S0 S8 n* ohEventHook = SetWinEventHook(
" t8 y3 Y- t; W4 F6 l0 q. v) vEVENT_MIN, // eventMin ID1 x. S$ E  v( k# h# j
EVENT_MAX, // eventMax ID
. f! Y! v) r( R+ x& W! HNULL, // always NULL for outprocess hook
  c. L: q1 i8 F5 wWinCreateNotifyProc, // call back function% ]+ K+ w( V1 N! i! O+ ^% o5 l- y
0, // idProcess/ s' J3 [2 H: i. m2 @& s( g2 j
0, // idThread
% H; ^+ z% o5 M# o4 t) B# N         // always the same for outproc hook$ m- v. n* K) u  ?/ s& X, I" W7 z
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);+ N, @2 Z% n* E
}  </FONT></TD></TR></TABLE></P>. b+ o7 {$ P5 T9 p$ ?* a

6 ^" E5 d; q0 O, t<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
( e; d6 Y: t4 O6 a' A5 k) L. M1 D下面是回调函数:</P>
6 P7 S5 e4 T2 r0 y5 a<>& f+ s+ B( z3 G  U
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>, |* x0 @# }) @$ T
2 O/ D, D7 Q( V# u7 g2 Y
<TR>. |$ M( w$ M$ Y
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(% s% K3 }: N, t1 i
HWINEVENTHOOK  hEvent,1 C9 U& u% l: T( a- \# C* v
DWORD   event,6 V' u, e  x9 H& \3 M
HWND    hwndMsg,' H2 F0 j8 b; c: j- t2 D9 ^1 ?2 a
LONG    idObject,* ~2 w+ L: Q: Z9 A% X4 k
LONG    idChild,* X; I1 m  k" y! m! M
DWORD   idThread,7 m1 i- o- H6 ]: r+ i
DWORD   dwmsEventTime# k8 Q1 B! j4 W' U, @0 `5 C
)
: T: R3 |0 u  x; I$ j7 i" Q; O{' {) q+ S0 u; w. `7 C  d* E

9 A* r) c/ s0 V1 Bif( event != EVENT_OBJECT_CREATE)0 W" ^: J% F5 d9 x; q9 S; O
return;
8 \) h; n$ ?' N( M3 H& R7 o8 k3 C
1 e( T/ J$ f+ b, P6 Kchar bufferName[256];5 Y* U# B: j/ h- Q0 i% J
IAccessible *pacc=NULL;9 b+ y6 ]1 K, r9 [
VARIANT varChild;
! C: x  K* W9 W2 L% h    VariantInit(&amp;varChild);
" @/ n5 x$ `1 y. x$ g  F2 z5 f//得到触发事件的 UI 元素的 IAccessible 接口/子ID对( u# [  [$ c/ |; _0 ]/ D
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
( B/ T4 g  L2 o, D. k                                      idObject,
3 u+ E3 B, u7 v                                      idChild,
% s' S; }: a3 O/ C) a, r( b                                      &amp;pacc, , S- G4 V6 l& E5 h. w' r/ \1 P  ^
                                      &amp;varChild);
+ e* E: R+ T* W3 b5 \% t0 O. M
0 l4 P6 _" Y; |. `: m3 @if(!SUCCEEDED(hr))
" w6 I( n2 T* C8 k' p, M# c& o' _{0 [0 C: Q+ `+ d+ |: Z6 u: F
VariantClear(&amp;varChild);
8 N; F  N' K- }, ~  vreturn;
: X1 `+ H) }6 I& E3 g" g}
6 @2 e6 C# v- q: [2 ~  ?//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
. m0 o8 M2 M! C0 \6 E3 VGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));" A7 E" I7 f, `9 j
if(strstr(bufferName, szMainTitle))/ o9 Z& ?5 o" n' B# @0 q0 l7 r
PostThreadMessage(GetCurrentThreadId(), % U% {! X5 d, k( c/ j
                  WM_TARGET_WINDOW_FOUND,
' l, T( V8 {( h" L4 Y$ b! H5 J                  0, - x8 f* T# r6 D2 b' P) X" e2 i7 `, Y
                  0);+ G2 _- S) s" G9 \' N+ N: g
0 H4 D, g8 p: N3 P" s1 b+ `( @4 e
return;" o2 z! o1 B  ~% V2 \
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
, w$ {  F$ T6 D# k- o. c4 h0 }
3 e1 i, f/ T* ?* a9 [; a附录:0 C  }% C$ R3 T) j. E4 A- [% ?
1 s$ b& ]9 ~7 u* Q2 s
关于IAccessible 接口/子ID对:
6 W6 Z1 @0 f* r1 O0 }# L    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
1 W% c- [/ o: A    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!5 n6 M7 H( o) @8 h' @
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。" C5 L, ^% n) q8 \) c8 v- K- k* E
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
! y, _9 ?1 f$ @' O! M3 l; l    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!" C4 v0 T" ~+ a+ W' z: w6 k  A

% j0 Q2 j) Z) x, }3 R9 b" f注:
6 x: Z5 P; w+ L4 ~9 |0 p; T    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)7 l5 {; ?* R6 f! {( \# h
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。; ^( H( P! b5 r( {! r
/ n8 P5 I( I1 h: \" b& G
参考资料: 7 t7 Q0 ^4 w6 `5 p
<UL>
3 ?7 I1 E9 @* b4 f2 W<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
* I( K: Y, x! {& O<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-6-11 07:45 , Processed in 0.458098 second(s), 58 queries .

回顶部