QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5366|回复: 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>
% z- a2 c  c1 ^0 G: c, I8 b% \+ r' b* S! i
<TR>
) o. |8 }4 Y6 g. @<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
% F: P* O/ O! b  s: L1 U: R原 作 者:崔传凯
) N6 d  v/ |: l" f; a( M, g, I原 出 处:vckbase
+ B5 D$ x) b! g* t, x发 布 者:loose_went! C2 A6 }5 h$ o, N! V6 u
发布类型:转载5 Q' H" r3 Z2 u2 U- H, X
发布日期:2004-11-12" Y9 [6 v4 ^0 z
今日浏览:6
+ z9 J4 K/ z6 w6 N& Z3 ^总 浏 览:234
: K* U) Y' D+ V) U1 c, g& o</TD>
3 ]( m* \! R5 t% j  z/ T7 ]<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE>' g& A* B, O8 B8 Q& {0 U' l
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>6 V2 F& ]2 f" V: F8 F8 S1 A$ H  \
# V+ c1 D* n& Y0 M, E  L
<TR>" y9 O+ `8 y6 R1 I- x
<TD vAlign=top align=left bgColor=#ffffff>1 ]7 _0 d% ?3 b' ^
0 ?/ r! G7 l  x& E2 [" H
<>
5 J- e/ W( H; V: s: A) b( s<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; g: {/ n$ S2 d0 d* K. _
; R. `6 Q, j3 k+ |9 U* l' @
<TR>
0 r) F' l- L3 B' P4 R<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
% Q: D! v- f, y3 oway accessibility aids work with applications running on Microsoft Windows?. It $ W! R3 D6 g, v% J" D# s5 j
provides dynamic-link libraries that are incorporated into the operating system
& M3 m8 I) e3 U2 v( _' X& t, |as well as a COM interface and application programming elements that provide
3 W  O8 x; i; M: `5 r: j2 Qreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
5 R3 [; h* Y3 x4 E3 N3 U<>一、基础
4 n; A" Y2 r' K7 u. B, `& p# S4 F    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。$ D$ i/ h5 [0 N; |
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
* M6 [# S8 |% f, u) v1 X    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>6 F' y! [' U* N
<>二、Active Accessibility 原理
% m  U; h, f. |" D5 O8 c5 e# @    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。, ?5 w& }2 v2 x, O
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。* \: b% a! ]/ }& f5 y' |( l0 Y6 k2 H
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
5 C% Z* F7 [4 d- x    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。. T3 ]  k( F4 O; q4 N( U2 j  x
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>8 f; ^* C( t8 w9 p1 I# l, e& P8 ]
<>三、如何得到 IAccessible 接口指针$ E( K- S& e: K+ \- z
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。* J' \: _: e* P) g0 ^
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。: C6 a) p2 G& c5 F5 \
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。8 w7 y5 @% p) T  a$ J% }# v' ^
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
) B, r/ f1 p- A5 k- M    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
( y% G3 E  i* x* ^    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。- r* U0 j& c8 Y; g# [" j
下面结合代码介绍一下它的用法。0 r$ [3 y8 F  L& m4 h3 A: n8 N! V3 E
我们来得到下面运行窗口的 IAccessible 接口指针。+ }+ a' t2 ~" Y9 i; q
, P5 ]2 |" L9 i2 g# {4 i  ^
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>! N3 c: k; t' o& L7 H; Q" I  |
<>图一</P>
/ B& }4 U  [" ?( p<>: r  I, z5 m$ P& T3 O- R
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) m6 t" s1 h% I4 Q6 _

0 M4 |4 g9 y# L6 f' J1 `4 {- \0 S<TR>2 T& V+ h; ]3 m$ E$ O
<TD><FONT color=#00309c>HWND hWndMainWindow;; P# |. F* @% @: L' `. e
IAccessible *paccMainWindow = NULL;  m) X4 R7 K+ v0 |- g
HRESULT hr;
5 _* u8 ^0 A7 c2 t//得到标题为"运行"的窗口的句柄4 y3 ^" z' R/ Y' m( j
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))" T  N; f3 h; S% K! m2 ]/ v
{4 B" i" V, j. I+ Y$ T
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
- |& e/ u) G! ~8 |}
4 F* n- R0 H7 k3 I% n  Qelse' M+ o9 m& `. ?! \
{* ~  @0 m- q0 C; N
//通过窗口句柄得到窗口的 IAccessible 接口指针。
$ ~; Y' f5 ]; r( H5 ?! Uif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,   Q) v0 `9 f, B' K, @4 _' B. z4 R/ X
                                            OBJID_WINDOW,
% f6 i8 I! ?" B" n" ~7 l% @                                            IID_IAccessible,5 I% M; q: Z" u- O% G
                                            (void**)&amp;paccMainWindow)))4 Q( L6 ~* ]% t  d$ I! C+ y
{* R# R5 W5 o: L: c8 d0 N, t( W
//……我们可以通过这个指针paccMainWindow进行操作。" s; z# Q4 t% d" y' {7 l
paccMainWindow-&gt;Release();* M7 U: m  y7 y/ W7 l
        }
: v* ]4 l- g1 l- ~# Y  o}    </FONT></TD></TR></TABLE></P>
3 X. S! ]5 j+ Z9 ]4 J, \+ v<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
2 @0 s8 [5 Q, M- l5 k    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
/ z% u% }8 n1 C% k6 }. \. S    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
! n1 L2 R! |6 g% M: P5 m* f<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
& ]; J; n7 B3 t<>图二</P>
: n* r8 o% C3 z% y8 D2 W# a<>我们现在主要关注的信息是:Name、Role、Window className。</P>
# j) K% l$ ?/ O. `( }" N<>* q) d2 y* b! O! o: ~
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 L$ r8 L8 H+ j( r
# g) ]2 ~6 Q% A' V3 m0 X! v) X/ S
<TR>8 E$ K2 z6 X5 S7 r3 S% n; T
<TD><FONT color=#00309c>Name = "打开(O):"
5 V1 e" d# z4 v* R  ARole = "可编辑文字"4 L: R! f9 A# a1 Z# Q# v
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
) u; ^* U8 v& w( i" Y9 F<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。5 _6 R0 t' ^! q4 n
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
8 f6 U$ @* M  M6 D- p5 A. ^下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
) C% v! m) j0 f) W<>; M! k6 f) t6 |
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
. C1 @! \) V, M: j  p( I2 S9 `3 c) ?0 q1 O$ {
<TR>& s  t! z3 z- w
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
+ P/ \2 G, A9 W& M8 v2 RVARIANT varControl;    //子ID。
' o! a& _- R5 T9 I  Z2 D. Y/ Z4 Q0 r; L9 u6 B$ l0 H
FindChild( paccMainWindow, , K4 r: {3 w1 |! q4 G" a: r
           "打开(O):",
% R; ~% @  ]' _- L5 O2 i( v           "可编辑文字",
( P5 a7 {4 X  U9 c: ?* S- L           "Edit",
6 |' m& `6 Y( k1 G, F2 V' r; G           &amp;paccControl, 0 P6 c5 U! W, u  {! M1 `
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
! a9 x/ |  k. k3 Z9 Y: l  R6 Z<>第一个参数是先前得到的窗口 IAccessible 接口指针。
$ O& t0 F8 Y% Y! L第二、三、四个参数分别是名字、角色、类。
( j% ?& x& Y% a$ c0 I后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>/ Q. ^& _/ d, r
<>
# \2 ]3 U( |1 Z% ~; w<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
  w; `1 c0 ~# W* m! M  e7 z  A9 @: Y* U- V' e
<TR>1 B. U) w/ `- N
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
8 l4 M. c8 q1 u/ w3 s0 ?( g% x: h                         LPSTR szName, LPSTR szRole, + L8 T* ~* ~; z
                         LPSTR szClass, + r; ~9 q. N6 K  x9 j8 k! W6 h
                         IAccessible** paccChild, ) H  c2 X! J2 @+ z$ F* ?. C: A
                         VARIANT* pvarChild)
! ]/ o( i1 r* N8 e/ w  h& E6 U( l{
# v. o: [1 G% ?; t  a8 j0 j  \HRESULT hr;
/ k1 p7 g" x& i4 r9 H8 Klong numChildren;5 S4 ^# m8 E: E
unsigned long numFetched;# u$ t9 e. [# D0 Q+ F# u) o# g+ S' Z' F
VARIANT varChild;& e2 o9 N0 E& W( T3 j
int index;: A9 E! E! l8 u& Y$ C
IAccessible* pCAcc = NULL;- ~7 C' r& g& e/ i; X1 Q
IEnumVARIANT* pEnum = NULL;' s2 O" n/ V! M0 Y. m" d
IDispatch* pDisp = NULL;
6 t, S/ Q! m# E# \( \7 d! gBOOL found = false;
6 p1 }& c3 e0 H7 b. q6 Uchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
6 S  F7 z) \$ j
) `6 f8 B' ?1 r' \& q7 ^8 a//得到父亲支持的IEnumVARIANT接口
2 c+ ^% S8 g5 {  K. khr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
8 |  q* B) w: A9 P3 f4 l
1 @( c; y2 O2 v& ~4 ?. }' mif(pEnum)
6 U. W+ A- w) G' z& G1 qpEnum -&gt; Reset();) _5 J- ~; K/ Y2 E. r$ k! t( J

1 n1 |7 W7 m9 j$ V//取得父亲拥有的可访问的子的数目' V, I; H# s& z, x! D  ^
paccParent -&gt; get_accChildCount(&amp;numChildren);
' n" O) L! h: N" \; v/ @, c
: p" T9 K, I; @) J/ i//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
9 L5 m: k* S8 D- N5 _% Efor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
' Y: S7 ~, |; ?0 N& v{
. ?0 x& U% E# |4 ?- vpCAcc = NULL;
; T& ?5 c# a% w/ o& u// 如果支持IEnumVARIANT接口,得到下一个子ID* D7 o8 o6 W7 S! }) V6 Y. `3 }
//以及其对应的 IDispatch 接口
% N) k2 }& c& u1 v4 R! tif (pEnum)# I$ N; ?. G* q  w
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); * D  o3 m/ L! E! }4 z+ o+ w& h
else
% Q3 L* m0 q5 G9 Q+ |) t{
* c; `& P7 o( U! K//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
( v7 s0 F$ N5 P$ i  a6 b1 ~# uvarChild.vt = VT_I4;0 n" X4 g6 v8 e# }* `, s4 f5 f
varChild.lVal = index;  E* ^; o0 e+ {! J4 T& E
}1 n8 H$ i& t; i/ K+ w3 b% r' {

% a: s' Z" J0 M// 找到此子ID对应的 IDispatch 接口* l6 w( [" f" G0 s4 j- A- L2 @5 J
if (varChild.vt == VT_I4)
7 \3 L6 M0 V! b. ~. z# z3 i{
3 W8 C! M* m/ S$ @//通过子ID序号得到对应的 IDispatch 接口( m/ ]' @  [5 S' `
pDisp = NULL;
+ x) L% J  M/ o4 J" Jhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
5 `* G; W: S; h- Z( l( k) `8 c# j}
0 a  X* k: i% y- w, r4 xelse, Z5 [* \6 L  M& X. H3 u  o: a5 v
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口( @0 v/ \; S) J6 B+ O
pDisp = varChild.pdispVal;) M3 m5 Q% c5 I! J" ~) u% [  X
. h/ l3 X* N; J% J
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc9 P& D, ?- o) t/ H) ]: T8 C$ v
if (pDisp)
+ {: V* n9 n9 W0 v5 s/ \{' V$ p/ _( v1 c: h5 O. X
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);! q2 ^1 x* o9 [9 i( B
hr = pDisp-&gt;Release();
8 ^+ L7 q- [3 Y( `1 E}
' m! l& g+ ?, k$ R1 x3 d; x3 Q4 U+ ~4 A+ {
// Get information about the child
, O! [5 s) _6 [% q, Y; w8 P! \if(pCAcc): e( `# ]9 X5 P0 l( i# s" l
{
- H5 Y8 e# ?% R" z0 j! a* f3 d//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF2 ~1 f$ d2 f. d% f" w% u( ~
VariantInit(&amp;varChild);
: I1 y3 C( d9 m% q& N2 e% W& PvarChild.vt = VT_I4;
, B9 a; V1 ~: V. \5 G; a) nvarChild.lVal = CHILDID_SELF;- @0 g% W* R2 h$ J  [& x, T

/ a) S7 t( v7 c. A, i6 D*paccChild = pCAcc;
( |( i. a5 y6 [/ o: }4 i}& Z' i4 Y6 l# S* A- n, Y
else
8 z' o) i3 ~0 r) r: f- r- X//如果子不支持IAccessible 接口" ]$ _  l+ E9 Q
*paccChild = paccParent;4 E+ Q6 o7 Z8 C, y0 v+ x+ ?

4 G; X2 q+ g5 y7 j) i( z9 x  H//跳过了有不可访问状态的元素# h! o8 l; }* W) \( s
GetObjectState(*paccChild, & B5 q6 J4 a  `  n* e
               &amp;varChild, " r: |  {# R  s3 x
               szObjState,
: H( X- B, a! d1 f0 H: b; M$ H9 z               sizeof(szObjState));# F/ k/ m8 |3 ?" ^' K. K: p
if(NULL != strstr(szObjState, "unavailable"))' w( U* b3 z2 G& _) z' N3 c+ b+ B
{8 D* l; R6 d, p7 ^4 h% h/ S
if(pCAcc)1 o$ O: C' z3 c) D/ Y) m
pCAcc-&gt;Release();8 \  R" @: l6 O/ k( ?7 i8 `" u; M7 c
continue;' V- A4 ?3 C/ m. @2 P
}8 b/ H; I! s6 h$ ?& {( y
//通过get_accName得到Name
6 U) x7 d# z$ _6 |8 ^1 XGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));; J9 C) z0 }# B# o# t
//通过get_accRole得到Role) i0 h( c1 }' {
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
  \* k! l& x# y5 u//通过WindowFromAccessibleObject和GetClassName得到Class0 Q9 [) m" G+ y8 W
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
/ L* l% Z  [  q) S$ Y//以上实现代码比较简单,大家自己看代码吧。
8 m( X# F0 V/ d# u- V3 y( W3 u" N# s. E( `5 O5 w& N
//如果这些参数与输入相符或输入为NULL2 T0 A6 @, B: j$ V+ o* p) Z8 i  L
if ((!szName || : {* [- \& I$ h& ~4 z9 S0 L
     !strcmp(szName, szObjName)) &amp;&amp; 3 k4 ?) H. i8 a& K5 @
     (!szRole || 6 d' Z- U3 X" }/ e2 ]8 Q% V( d! `
      !strcmp(szRole, szObjRole)) &amp;&amp;
/ u3 M/ O3 Z2 i0 O& A$ u     (!szClass ||
" {$ l; I. @; b( {$ u2 w/ h  p& q      !strcmp(szClass, szObjClass)))) W, g3 o8 h, v) ]9 H
{! Y( j  n- G9 S; V
found = true;% y5 h1 a) j+ ]2 ?# E) O
*pvarChild = varChild;: \  n" ~* w3 I; p7 p' r
break;; O" z+ q& S9 E( f3 m5 |/ z# F
}
1 }% P; ~& H9 M8 T5 jif(!found &amp;&amp; pCAcc)0 g1 H7 T! @: [# q4 M0 `
{
* b, o% X5 ?/ U8 A- f// 以这次得到的子接口为父递归调用. x: R+ N6 e( v' u) ]
found = FindChild(pCAcc, , l- V1 }! e/ g  I: C* \- J7 Z) d
                  szName, - z" ^# K7 j9 ~# Q: ~! p1 F
                  szRole,
6 X3 q. a% S, ^                  szClass,
4 x: K! K2 `3 I+ S                  paccChild,
. u9 t* z# }$ T* x& K                  pvarChild);+ c+ B  g! X0 o, x
if(*paccChild != pCAcc)& e6 n1 {5 @' {" t" d* u! U7 S
pCAcc-&gt;Release();
5 Q4 p6 t8 N3 D& N& R: E  D}
; {* P  ^# @* ^- D. k. s}//End for7 t* e" s, ?; k  _- J
3 c5 x" `2 Q" ^
// Clean up, E& f: U1 U: ]( l
if(pEnum)* u4 o' O2 G" C, o
pEnum -&gt; Release();
4 Y+ ?; y2 J9 T% D1 j+ x: n1 d" _, z! ^! B) B
return found;
  ~3 c* }3 K- w}. w2 j+ C4 J, e" O& s! q

! ?$ L3 s& R9 g0 E5 n// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,1 K* j5 c2 ^% P. A- O6 i, D3 \
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
9 W' Z1 Q9 q0 J0 a$ r  m//将这些或数转换成相应的用逗号分割的状态字符串。. P" y/ G# w% m, L
UINT GetObjectState(IAccessible* pacc, * M+ t5 x3 ~# d) t4 v9 [. A0 B
                    VARIANT* pvarChild,
: O5 f# Y2 Z- ]2 ?                    LPTSTR lpszState,
3 c/ n( {3 `( ~/ A1 v' x                    UINT cchState)5 _1 H1 |, n* W1 m. D
{* R2 E4 I! }2 G
    HRESULT hr;/ k) J+ ?2 Y$ x! Z; D0 X
    VARIANT varRetVal;5 N& F  ~4 A3 k9 g4 W
' H/ m0 |, W' }" q! i6 F
    *lpszState = 0;
. G+ `; D8 U& ~% ?0 ?1 F; G/ _, B
! I6 G  `# l+ H3 d+ v. X    VariantInit(&amp;varRetVal);
- U1 M; G& X  X6 c# `, a/ n6 q1 K7 A* O" o5 c4 O% U' z% n
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
( y% E6 u. U: ~6 {; i; P4 C2 l
0 O' i+ m+ N) _" M8 W1 T- p8 X: {if (!SUCCEEDED(hr))& x1 \* S; K! w; ]1 a+ \1 s
        return(0);. C( ^1 P+ R9 r9 l1 j8 X

  M& `0 M+ Q) H8 U8 ~: b; _. F3 fDWORD dwStateBit;
* h8 k& ]' ~9 @3 [int cChars = 0;
! i1 W- q: D: c/ }- L1 p    if (varRetVal.vt == VT_I4)
) e2 p- a* k6 s' Z" o{6 H9 ^7 W3 [6 j5 _* k
// 根据返回的状态值生成以逗号连接的字符串。
. }! g. ^) r! r) i3 w. O( a6 J" U& [+ h        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
6 I  }2 r* f' ~               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
# T# w8 ?7 z3 b# W               dwStateBit &lt;&lt;= 1), i7 a$ \8 s# w8 g" z7 {
        {+ b$ w5 S+ |. U
            if (varRetVal.lVal &amp; dwStateBit)
- I7 p0 ^" c5 A& T1 j            {
- S& ^4 W2 y; k1 G: d6 k$ i1 s0 V* c                cChars += GetStateText(dwStateBit, * D9 D3 P, J" k& H* Z0 h
                                       lpszState + cChars,
+ L: O+ f8 n/ |3 b6 a+ S$ Q                                       cchState - cChars);: G" H- d" H  r1 Q! l1 b
*(lpszState + cChars++) = '','';- ?+ E) o+ q* R2 d( h* Z* p8 C6 C
            }5 p8 U) `2 |1 F: J6 O- ^( u4 `
        }
( }( G3 g, N' L, rif(cChars &gt; 1)
% f/ E) b- @7 B$ @& |) J$ R*(lpszState + cChars - 1) = ''\0'';
+ m+ B% t% ~5 ]5 p    }
7 V$ `0 i" c+ [  ?8 V    else if (varRetVal.vt == VT_BSTR)
. F- h, `* L) Y0 Y    {
0 ~  O% C8 Z0 {+ X8 V) |6 C        WideCharToMultiByte(CP_ACP, . ^; h( q) L4 v
                            0, : a3 b/ K. Y( V# `) E/ q, N
                            varRetVal.bstrVal,
9 W8 F6 J( W; {3 j                            -1, 9 n- i$ d0 U7 M) N- L! z, k
                            lpszState,
0 x3 \6 J, I) H4 d) Z. _  o) c. i9 c                            cchState, 3 G" ?' V) \- m
                            NULL,
) I' U$ D# q7 m* O# h                            NULL);
) T# ?5 w. z  u. O    }
5 u" h6 ?0 f; {! ^4 }
" b4 ]! V) b+ I. x! Q- I    VariantClear(&amp;varRetVal);) h* B* s& }+ n7 ]- ?

+ c1 a' b0 F3 v% N4 p    return(lstrlen(lpszState));
: W1 S* ^0 B1 t& D}</FONT></TD></TR></TABLE></P>
6 ], {6 R' a1 A1 F<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
; c8 u' u& j. x4 f$ p1 l8 ?& I& m& A6 g+ C
四、在 IAccessible 接口上执行动作
& F. B5 A# u! |  B0 q4 O, O    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。" I, E1 }7 P( q2 _6 b1 p
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
) j2 x& [- V' l$ r8 Q0 V" x    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
- q. \8 ~/ c* x+ Z0 u; w<>
2 W5 K9 ^5 A1 h$ C4 {+ T<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) u9 ~# H. }: H6 ?7 R

+ a1 X( J7 a0 w* f% J# ^( j. I<TR>% Q2 u& y$ O0 n. F$ C4 |
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
8 [) A1 n' r  |' E( Fif(1 == FindChild (paccMainWindow, "打开(O):", ( ]6 c$ @! g( \$ G- Q9 B5 G
                   "可编辑文字",
3 x2 [$ z; ]6 v! e6 N                   "Edit",
& |/ Q4 A3 C) a& E                   &amp;paccControl,
, K7 Y* |* [- z5 m4 W                   &amp;varControl))
2 d" A2 w6 L' u  \& K5 W{  B1 B4 G, a5 F6 u  R
//在这里修改文本编辑框的值1 j" ^" a/ k& ^. I
hr = paccControl-&gt;put_accValue(varControl, 0 t- f0 t, m! T( M
                                  CComBSTR("regedit"));2 x$ H  @  A  t: S" @0 A* v9 G$ C
paccControl-&gt;Release();
7 i& y! L8 |( d+ Q1 j& k4 E* M5 aVariantClear(&amp;varControl);7 ~" [7 H) d6 x* p  r1 u
}! O2 P- O/ V, v' D% ]
6 z) T2 j- Q+ ~; s, u
// 找到确定按钮,并执行默认动作。
3 ~) a6 @% B" C; P# @if(1 == FindChild (paccMainWindow,
! t1 \+ I  m2 q# x$ C                   "确定", 7 u5 \( v) y, a& a6 B
                   "按下按钮", ( l( H' t* j2 \; ~8 R+ F. \
                   "Button", 2 k2 W6 V0 k. ^- k9 W8 Y
                   &amp;paccControl, $ E( I+ U& O9 P" p  }, L
                   &amp;varControl))3 }' H6 _4 ]" o  D/ D# r4 C% X
{
/ U3 B8 H5 L, R//这里执行按钮的默认动作,即"按下这个按钮": e1 }0 v* R( O1 j6 N
hr = paccControl-&gt;accDoDefaultAction(varControl);
% p$ d6 T* @: P6 V9 ?) G& GpaccControl-&gt;Release();
' L0 L- a$ I5 g0 X: lVariantClear(&amp;varControl);
, b" k- r9 p+ b  T& z( v}</FONT></TD></TR></TABLE></P>
3 n) e. P3 z- U2 X. p. Q<>现在,你会发现已经成功启动了注册表编辑器!!+ y4 O9 s6 f$ q& V: f. z
</P>" O0 |9 D; N7 g  s
<>五、模拟键盘和鼠标输入
0 A; s! S4 ^$ W* a    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
3 t6 c$ _# z. N    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。4 T+ i& Y+ P  N. V5 p& }, o  _9 g
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
; N4 _' g3 J  t& V% r! b下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
3 ]/ G  {$ U: ]& R, O( l<>
  z% p- s" f1 F6 R2 V9 T<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
0 l2 i8 W, N9 y% s8 R
. r4 ~+ `; C% r3 b6 o<TR>
0 ]# U0 N* z6 S<TD><FONT color=#00309c>INPUT input[4];
4 Y$ U: ?, A* B+ Hmemset(input, 0, sizeof(input));" [) C- M, Y& `2 T9 S
9 Y% H$ E3 o  K" h
//设置模拟键盘输入% W3 d- v9 s7 M! `* o7 r
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;7 g; A9 P2 h7 M: p' H
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;; A& x7 E, Y* w! J: R9 t3 F: c& t0 T
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;7 D6 i' G* k5 M1 I5 U6 S

' C8 y2 W+ H% a4 b// 释放按键,这非常重要
- e* R3 |& S5 I9 @1 jinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
5 b6 R1 s6 O* Z% j; O6 B
9 l. R: w# R  t* Y/ m8 d5 CSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>6 h4 D. f8 b# m# H+ G# A, ]
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
7 |  m; Q* h3 p5 N8 d3 X% y& s: {</P>
" G" |9 o% s% ]5 {' x  @+ w4 S<>六、监视WinEvents
4 ]. I  `+ j1 U# e" [9 [1 d1 {+ P    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
7 q- L/ X6 ?, R1 f8 ~' s. _    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。' o6 [7 C5 e0 f, q4 l+ W( X. ]7 }
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>2 P( h4 N# d! e  z: l' K/ U+ _) `
<>" k. U4 Z0 s3 f) V+ i+ |* g
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>- b# `6 _* j8 f# O! I
5 v2 s/ O4 l: D( n
<TR>
# Z. [; `( M5 M+ s<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
1 h- M2 {& c; s. l5 ?: \) @4 v. e{
. x+ R" X: H1 j  U6 J4 M2 A8 R0 {9 shEventHook = SetWinEventHook() k' M: Q! r' k
EVENT_MIN, // eventMin ID0 g& ]! o# m( R8 r& Q# G0 m
EVENT_MAX, // eventMax ID
5 p# W6 A" l' |0 C% ^- lNULL, // always NULL for outprocess hook: k) c& J5 t* v
WinCreateNotifyProc, // call back function
! I* c3 G" V& r0 d  R" r0, // idProcess
+ ?2 W* f4 h# A" T9 Y# o0, // idThread
: y7 T- o1 t& O5 O1 s( [* G         // always the same for outproc hook8 c$ `3 q2 E* O+ C$ L& |
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
: D5 t) i0 h! f* i5 n% c}  </FONT></TD></TR></TABLE></P>: f* e/ J7 X! t5 `7 u, c9 C7 A
* [+ j9 V! N% M9 X- @# i  q
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。( {; ?8 _; }! g2 G; ?
下面是回调函数:</P>
& \9 \0 l# H, i( X- D<>
. R4 W& s6 Q" f+ G5 w+ D/ J6 E<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 K4 i" u0 ^3 M
/ d2 i# A' b9 ?<TR>
( K! a2 y* P, F' \<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(! {* h  i% Z, y0 x; z/ H
HWINEVENTHOOK  hEvent,
7 A* ?9 {# l4 F9 J7 fDWORD   event,
. X, k: ~! }" i9 k# WHWND    hwndMsg,& I! M+ e! T8 W' I8 z. k
LONG    idObject,3 E5 D1 Y/ n+ c7 ?! W
LONG    idChild,
$ x3 x7 G4 w5 T4 |4 MDWORD   idThread,
' f9 [4 _# h! J9 n3 q6 k! IDWORD   dwmsEventTime, f0 _, f! j5 M7 e
)
, [; S6 @# q, z1 N4 ]# q- i" N; w{
1 Y  Q6 a2 F; d9 ]! g; _
' P/ ^/ f. l1 d( ]if( event != EVENT_OBJECT_CREATE)
/ r2 M# E) V5 y! Q! ^0 greturn;
. q- F- n- [2 }# S& k5 s3 S1 r: ?( K" K$ ~2 b0 k0 l! S
char bufferName[256];: U5 o! C/ M0 h# v; t! j# |6 M
IAccessible *pacc=NULL;1 D) b* m5 L, M  |3 F
VARIANT varChild;
! v' V" G9 q" ]8 K1 a+ C    VariantInit(&amp;varChild);
! V1 H$ X2 \+ T3 F& R: Z" w5 @//得到触发事件的 UI 元素的 IAccessible 接口/子ID对* k/ k: S; }5 M4 _- A5 y& G. n
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
- c9 J4 f. T/ z9 X0 x                                      idObject,
1 h5 @1 ]4 f  t                                      idChild, $ M: d8 i3 W( D# K! X' |
                                      &amp;pacc,
' J8 t" [( w' G& ?  C/ o: G                                      &amp;varChild);
  D: j! d) X$ M9 }, q1 I' ~2 E, t
' C) _+ u- `! L. S4 D+ Iif(!SUCCEEDED(hr)): j" s1 y, s- o! G" t" _+ g( g4 i# r5 Y
{. U6 C4 D( b, C, i3 W
VariantClear(&amp;varChild);
) U( A! P  h$ h6 qreturn;" |, `! d9 U# O" k4 n% j; V3 a
}, N+ g. _* [" Q3 y% R; Y, N
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
( D! B! O# O5 B1 Q. r" q- hGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));/ I: a* _+ u( C9 k2 v+ M
if(strstr(bufferName, szMainTitle))
0 n: g  L( `% ]0 L( {% UPostThreadMessage(GetCurrentThreadId(), 3 s2 u# S3 e+ m( h6 G  q+ U4 K
                  WM_TARGET_WINDOW_FOUND,
/ m" v! ]% I1 _0 w: G1 Z                  0,
0 n3 ?; ^" F" ~( V* V                  0);
5 `6 C$ Y$ e) f0 u; g) J* x. p5 {' a& t) X7 H
return;% x% r, k5 ?* [( k+ M
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。1 p. g# e' ?+ y' w# |( n$ D
2 P6 u5 s7 }6 x+ r
附录:
$ e. t8 @, c; h  K% m1 Z( f( l/ ?" {: P+ ?3 ]! O4 [
关于IAccessible 接口/子ID对:6 P/ |) h' A! r8 y- W' z+ m
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。+ Z. _0 b/ y$ W  z5 [
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
  _+ T$ I5 W7 r; Y    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
2 x  g' v  x9 N& J3 a    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。7 S/ {0 r6 j: W. ~; w
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!1 e. h5 h# S9 s6 P' q

: v# R' S) ~# Y+ I" H: j注:$ F/ j& ?! T5 ]
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)1 \) T5 e" i* y+ G+ o9 @
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。  t1 c- i) }; B4 [) X6 O  Q

& Z1 n# x: h3 I1 O$ R/ ~2 S参考资料: ! t1 u* `; x! a: J# Z) @
<UL>
- w( N$ Q( \6 U* ]7 w+ p8 ]9 L<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
! w; a% @' l+ d) j! Q7 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, 2025-8-23 22:16 , Processed in 0.742368 second(s), 57 queries .

回顶部