QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5406|回复: 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>
8 Q+ f, Z! i" Y) X  L
0 \, o  T" ~7 h) r. x( F<TR>
! |. @7 c) h" s& h: N7 v- b% W<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>) s# L  H( X( R9 R8 U
原 作 者:崔传凯
2 W2 I) z! a# M' i8 h# B8 {$ o原 出 处:vckbase
1 j1 F0 i' B4 U- k/ h发 布 者:loose_went) |3 M* x& |6 a4 ?- x. S+ r5 x
发布类型:转载
# _* B) s( W7 N* z2 ^0 w发布日期:2004-11-129 f! y( Z0 y" z3 v% p. P" n* y
今日浏览:6/ R3 O* L9 |3 y  s
总 浏 览:234; M4 O1 ~& k/ M0 t1 W
</TD>  I; S! L5 o4 v8 L3 z) v
<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>& c  J, r& l; c1 M* T
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>  n4 U* L, m2 A2 s$ {
! ?0 u- H5 F2 f: |1 A6 C% }
<TR>: d2 K# C* t3 ~) \. N
<TD vAlign=top align=left bgColor=#ffffff>
3 @: v! W5 N% X! @
" j( l, U  B: j+ N6 @<>
# S; Y/ D, }& y- K6 w& \$ `, R" g/ H' }<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
! n6 l+ o9 S% h( c. @- t
! ]' z) W! T, H9 h<TR>( E+ h' r# l7 G, B2 Q6 c% d5 n
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
, d$ ?, ^: s! Qway accessibility aids work with applications running on Microsoft Windows?. It
$ L5 x, @( D+ E" \- G/ t- Bprovides dynamic-link libraries that are incorporated into the operating system
8 V& b3 ?: x* P4 Y4 g$ w! `9 H1 @as well as a COM interface and application programming elements that provide
' _6 }( N5 n' p& r+ kreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
) c/ Q& j- ~2 f0 h1 I- a& E<>一、基础- S4 m# {4 S+ s( a! ]2 C
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
  Y$ a% e  |5 {5 ^& @' o4 b    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。) u# w/ V0 ~4 k( P
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>- y4 V( A, t3 B& E5 \! c
<>二、Active Accessibility 原理
2 z0 h4 B2 e5 @& H% R$ |/ D, e    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。& ~) @: n2 F/ X3 |6 o
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。! D/ e5 y8 t: n; [7 ]
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
: A& \: [# o. x' _    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
7 t3 c3 y; S% k9 w8 Q* o) F. o    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
2 ~0 u  V, B* w* J4 ^9 v3 q<>三、如何得到 IAccessible 接口指针
- x8 v" k! @0 T& N3 R1 l. F    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
& p9 r0 l5 a; {. y    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。2 f. I- g7 x, B* {& P
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
* j$ y2 j2 ~6 ]; M& O    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
3 [; H( _  u% H. _* ^" s) Y: ~4 t    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
7 C& t( Q' g- C8 S1 v7 |    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
; |( f4 d1 c  K下面结合代码介绍一下它的用法。
  ?; v$ v- A' |5 i( d& o我们来得到下面运行窗口的 IAccessible 接口指针。0 P+ {: v( c7 n( ~: h! ^

- K) Y+ G" J: F7 m1 [8 h6 ^<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
# R: m5 A$ L% _: w8 e$ k0 X<>图一</P>
; O0 |2 N, Q7 A1 L& n* R! Y4 A) ^<>
' |$ h) c  g5 f1 P& m<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>& u1 J) t) V) y! C! @

. P* m+ N( H% K5 r6 e( R# @<TR>( ]& \4 c1 c, f  X2 s! g
<TD><FONT color=#00309c>HWND hWndMainWindow;
9 I8 s5 f0 ]( k2 E/ SIAccessible *paccMainWindow = NULL;
# U8 g( V! q8 eHRESULT hr;
/ D# N" S! @7 ^//得到标题为"运行"的窗口的句柄9 }2 i& q& `2 F8 a5 e$ Q$ J# Q
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))1 C# s$ B5 j# Y
{
5 r9 m7 J' z5 v  {9 o7 LMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);3 f3 g# c1 c6 ]7 A8 n7 y* a& `0 Z
}
' a) f, |$ O8 ^, x' Pelse0 W/ R. s& e$ w9 N# {! g0 y( R
{
6 K* d# h- W# y& p3 _; {  e) Y3 y% ~//通过窗口句柄得到窗口的 IAccessible 接口指针。: r! z6 [& k; j6 J2 m' p
if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, ! p4 H9 C7 P% N! K
                                            OBJID_WINDOW,
' i0 J; Z; z# K0 ?5 Q                                            IID_IAccessible,
& V4 A& ]( u5 u! X0 p0 J                                            (void**)&amp;paccMainWindow)))
0 g  I* f" ?; n3 @* J' y{
3 e- _9 L# J1 f( h//……我们可以通过这个指针paccMainWindow进行操作。' N; d- f# k0 m% r
paccMainWindow-&gt;Release();. o" `3 U  j" u6 ^4 [
        }. x% ^- W2 |4 Z5 t+ G. S9 B5 Z5 F
}    </FONT></TD></TR></TABLE></P>  \5 j6 a: t: d
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
1 P8 Y  i) n( c! G    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……% G' G5 o# B$ T" a! F# [& k4 ~
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>3 e1 z1 _. M- p  i1 B! k' }3 Q! h
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
( D" d2 l' a% e% I<>图二</P>
- F5 L# V; ^+ ?<>我们现在主要关注的信息是:Name、Role、Window className。</P>0 b  F4 h3 J: T. l2 w" n4 ~9 d
<>+ u7 _/ F/ z" U2 N
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ a3 w7 i+ W# Z- R
7 c5 u9 T) r- _/ F4 N
<TR>( [7 o4 q7 i/ p6 b# H
<TD><FONT color=#00309c>Name = "打开(O):"1 \% l0 ~; t: ^$ U' J  ?) m
Role = "可编辑文字"7 f/ I* O9 [+ O; \3 r) j$ y
Window className = "Edit"  </FONT></TD></TR></TABLE></P>% r, F' J+ V  C
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。( j2 n1 V3 l) z# s# F  a6 m
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
2 K+ \* ]- k* L% I) `2 ~下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
; \+ p* u# y4 Q0 U3 z8 c<>. W: N$ ^  F! O3 d
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' b  ~8 A2 ]" l# b: S
& j! X- X4 j" h3 n" b# J
<TR>7 y) i. N5 G, C, H! U5 ~0 K
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
- F3 B* R$ ?- a  Y( m2 A$ R$ Q4 AVARIANT varControl;    //子ID。
; T' U6 _5 t! s; b7 \8 f: w; U' A. c. D0 t! B% \: Z4 {7 z& k- I/ e
FindChild( paccMainWindow,
3 n' U. b" [5 k/ W9 u0 Y  B" q           "打开(O):",
4 M8 Y) j  k' U: P/ _7 `) e" P* S1 d           "可编辑文字", - |* ?1 W5 j, x! I( ]( e% T& D
           "Edit",
% u+ t, y0 u: ?' P$ N) j           &amp;paccControl, ; x5 H8 g8 a4 Q7 u5 o  V
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
% d6 b6 b0 t4 T+ a. |* o3 G<>第一个参数是先前得到的窗口 IAccessible 接口指针。
0 ?% O' [6 [- N! z8 U5 G第二、三、四个参数分别是名字、角色、类。8 U5 f* L) e* [% j2 d
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
0 ]! J5 T+ D0 \( s; U: n  H<>
6 l4 X3 i% i3 H1 c( x0 u<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
9 G) c5 `) g! G8 W( y; T2 j9 y/ i
<TR>- Z) a9 g5 S# o( @5 a4 u
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
$ d9 c% r' @& s$ X/ a                         LPSTR szName, LPSTR szRole, : w7 h6 W2 Y% p; T6 j) @
                         LPSTR szClass,
( T$ o8 Q- \8 d% n                         IAccessible** paccChild,
+ H& N# H% O) `0 K                         VARIANT* pvarChild)
* A" n2 X: R0 l3 ~0 o$ ^{
1 h: n& ^1 ^, B$ j# T( nHRESULT hr;0 |! X" S6 s; {$ @" j) [  a3 U
long numChildren;
! j3 |0 ^5 f  \$ E1 R4 H+ a+ C/ Gunsigned long numFetched;/ n8 z( |: m! E" Y5 D4 d
VARIANT varChild;7 r  |( X3 `$ s" R; h; K: Q5 {5 v
int index;
/ w7 C$ X8 F" i; U+ a$ zIAccessible* pCAcc = NULL;
$ @" ?0 c# O' k7 }' {7 jIEnumVARIANT* pEnum = NULL;3 I7 Q2 p0 X- C- [$ d
IDispatch* pDisp = NULL;* L, F3 E: W  e. R
BOOL found = false;
( Y. ]7 W" D* h- T3 X# C& \5 Tchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
& Q) p/ r/ i: _! A7 ~' p" F- \1 r) T. _' s
//得到父亲支持的IEnumVARIANT接口# ~1 l$ X) t& C9 c# H7 V# x
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);* G( I8 L' |% B+ C: j% Y2 S$ }

( Q9 o$ h5 `) V, s$ Z, W+ P4 Nif(pEnum)
1 O7 M" O! _8 i- e5 {+ zpEnum -&gt; Reset();9 E* K$ u* L" X6 y
2 f8 V+ E3 J2 R2 ?3 I  N
//取得父亲拥有的可访问的子的数目
: N5 @! D1 i0 V  BpaccParent -&gt; get_accChildCount(&amp;numChildren);2 V8 ^4 |# m( X: |

1 b/ ~$ x! ^0 G  s//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
6 O8 V6 ^* w+ dfor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++): z0 ~: r1 i1 m- Z4 ^5 y
{4 b# r9 T# X! M; l4 d7 o
pCAcc = NULL;
5 e' C0 A0 F/ ?1 a& @& u// 如果支持IEnumVARIANT接口,得到下一个子ID$ ~4 o% H. [  Y
//以及其对应的 IDispatch 接口4 a" R+ \& _- E/ E0 O* F
if (pEnum). x& }4 L4 p; s3 @) }$ q0 J/ a3 I
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
! o! l+ t) ]% A4 jelse/ O% c9 @+ b7 S  r9 ^% x" j9 w
{) i  X  f; O& [, V4 c* y
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号- P+ S* r- m# V- Y1 A: F9 c, F
varChild.vt = VT_I4;
- s4 O* L5 y2 Y8 rvarChild.lVal = index;, v; R! I5 W# r9 d' |1 [
}3 |# g& e5 A7 [  L8 X/ S+ w
* `- l! o# W. w/ F$ Z7 `# K/ T, |
// 找到此子ID对应的 IDispatch 接口; R  X7 T( P) i- Q# x5 Z* m
if (varChild.vt == VT_I4)
+ y0 j/ m; o8 ]6 b0 u{
" e4 |4 F2 N8 H//通过子ID序号得到对应的 IDispatch 接口
7 _7 i5 U$ b/ T7 u& e& upDisp = NULL;4 L  k* D! I4 a0 p6 w& p/ R3 D7 o. `
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
7 k3 O$ I. U  [3 Q}" [$ f" _3 \! i2 r1 m1 y" z
else
' P- W3 ]# f* [2 R//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口% H0 E1 ^9 v& E. ?/ k& j* o
pDisp = varChild.pdispVal;7 U9 z; |. e' v  r6 w$ V

* v1 r7 O& E  M7 T// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
' o; i, v* ]+ M9 B2 R1 I3 `if (pDisp)
! J. }% U2 G  j' T' X4 N; O- T4 _{: s1 ]% h1 P' W# F) _8 b
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);2 U. i' n# z0 ~9 Q* {. U
hr = pDisp-&gt;Release();
" N, c% J% [3 n}6 x% v1 \6 |3 _+ A% W8 H1 j

4 A& e- ?/ H3 S' y" d/ v1 ~// Get information about the child
, _9 N. j% K; ?. S: Aif(pCAcc)3 _+ \: G0 N# L. _1 o
{- D' ~! b1 T: U2 G) E  A
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
$ O( R1 h- R/ g# Y! u3 [5 RVariantInit(&amp;varChild);
" G6 x" o# E6 `0 }/ rvarChild.vt = VT_I4;
' M# }6 d+ x; U5 XvarChild.lVal = CHILDID_SELF;
2 m3 L1 c0 F+ [& W# u9 u# V$ ]) W% A! \6 \$ B
*paccChild = pCAcc;& ]* r: p. j" _7 o9 S# t3 d
}
! T& z  |, k* i, H4 j2 C& Lelse
; D. ~) S+ F8 o6 \! s8 a//如果子不支持IAccessible 接口, V- `, f# n( g: v9 G
*paccChild = paccParent;  i2 z1 M! z0 ?

/ j8 s( F! F) z//跳过了有不可访问状态的元素
- V0 g# C5 o' w. t! b) a! G, iGetObjectState(*paccChild,
7 B/ Z' V3 {5 \5 j               &amp;varChild, ' B2 Q1 A- f9 a+ Z, e6 i4 D8 S
               szObjState, * C, b' b3 u1 M* n% e0 w) A( ~
               sizeof(szObjState));
5 u" l$ u6 k' C9 x; Xif(NULL != strstr(szObjState, "unavailable"))
& _. A/ J- t4 `{
+ C3 K" E6 @# U" f, T) eif(pCAcc); v9 X' H' W2 s/ M6 N& H1 M
pCAcc-&gt;Release();" G5 o% a. Z* \) R  W% m" D" a
continue;
( g/ m. p% u/ Z! c5 n1 B}" Q8 E! a' k' O
//通过get_accName得到Name
) K- D& G) X# \GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
- K( ^" @7 E* o- g//通过get_accRole得到Role
1 W. F5 ]) f) @* Q( _GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
4 N* B4 D/ ?# }. s) S//通过WindowFromAccessibleObject和GetClassName得到Class
1 ^: |* G% @/ KGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
. h1 G$ A5 ~8 z# E//以上实现代码比较简单,大家自己看代码吧。% f4 a2 h( o' S
0 g/ G3 Q3 s, I, e" L1 P3 o0 w
//如果这些参数与输入相符或输入为NULL! ~! C) L% w( Q, {0 H4 U4 L3 j. b
if ((!szName || 1 v. e& b4 b) I  a5 n
     !strcmp(szName, szObjName)) &amp;&amp;
( R& |/ i7 [- j7 L# @0 C     (!szRole ||
+ p( Y. f8 P9 A  o& o+ K      !strcmp(szRole, szObjRole)) &amp;&amp;
6 r  Q' h% K+ o) i$ j( U* R1 N: T     (!szClass || 2 |0 x) T% H+ [. Z: T8 Y$ V3 {
      !strcmp(szClass, szObjClass)))
1 Z' r% U: s9 F) y& U. b8 G# d( X7 b( A{
' M' J% _4 ~& G# a6 X$ M- [found = true;5 a* k# l5 x, r" E! ~3 R7 G
*pvarChild = varChild;
& t2 r( A0 d5 _" ~0 pbreak;
$ m) E7 Q0 @/ Y8 j}
: h$ x2 W& D0 gif(!found &amp;&amp; pCAcc)" S+ v. \1 y5 i6 W  k2 ~
{; \, }- P, K' y3 v3 i% L9 P/ n
// 以这次得到的子接口为父递归调用6 y( X# X, ^. ~1 S; o$ {' C7 Z
found = FindChild(pCAcc, , I& J& ], q7 H" H9 t. b2 D  b2 @9 w
                  szName,
/ C/ I% C! X, A4 l0 p! e                  szRole,   N7 W. s+ e( ?( V, h! [' k
                  szClass, 1 F  z5 q' ]* u0 o% f  }0 C
                  paccChild, 4 N7 O4 V+ p: H0 u
                  pvarChild);
; P; _+ U8 f% S& @% F* kif(*paccChild != pCAcc)
7 G% q8 G1 v- q8 n) ~pCAcc-&gt;Release();+ I% K  o) t. `& h8 `
}
( L9 P8 s" ^9 x}//End for
# E' }; V+ P. u0 `! ~) K
( S: U& L8 ?% f0 I- y// Clean up
$ R; P9 _! T# ~" p  aif(pEnum)
% s2 Z- f8 S" K+ x. v& ^; vpEnum -&gt; Release();: P9 I  [2 Z" P; d- g) E
5 E6 x8 r' B  [/ v* W1 x4 z
return found;: B9 ^) p) S& u7 I5 A  T0 X. @
}. J5 }) G9 r' w0 \# J/ B

/ o" a4 x& @# U8 X5 N  ]$ S" i// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,2 e5 i; S, i5 D; T* Z9 b5 f
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
* j' A3 @7 L# M% `- k//将这些或数转换成相应的用逗号分割的状态字符串。
' O8 q+ {! o) [) b5 ^, {- ]; l4 cUINT GetObjectState(IAccessible* pacc,
0 r# @5 }% }# @( v# z8 r                    VARIANT* pvarChild, ' d; F8 I: c# v" f
                    LPTSTR lpszState,
1 G! I0 y& ^$ e1 L* V2 w8 M. @                    UINT cchState)
0 g& o  P- q* ]& o; D* z{. ^) |  j2 m: t8 [4 x1 W* ]8 D, @: p
    HRESULT hr;
' [' x" V1 b6 s# D3 C2 E! {    VARIANT varRetVal;# l% _; w& m% b" P& {2 b" d

6 y% m0 A1 Z' T- d  d    *lpszState = 0;
+ ]/ V: W, f0 y( J, D7 [3 b! d( \: g# V5 i
9 E# \6 @3 I4 Z' ]' c( [    VariantInit(&amp;varRetVal);
& H7 g/ H2 K" E: M" o' R5 L; Q0 u( v/ V( d) n+ S
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
7 G/ x/ h$ u& P# S
8 W( k& y5 i( X; ]6 `if (!SUCCEEDED(hr))' W/ B5 u; w! h' j* H8 n' a
        return(0);
2 ]  x; Q1 c/ i  V. ?( F% j7 d7 k. I. k& B' b
DWORD dwStateBit;
# A+ c( w3 x. K$ Q1 f' ?$ p1 |/ yint cChars = 0;
) d  j  {  u: X% S, S/ ]0 y* Z- W    if (varRetVal.vt == VT_I4)2 D4 A" z( h7 K* w7 `; B) s
{
" ^/ J$ R8 v0 f' T1 E" V// 根据返回的状态值生成以逗号连接的字符串。' C7 Y  G7 k+ w7 p) y3 F" Z- H/ V
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; 6 q1 y* u  R, d, x
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; + d' h, K+ v8 O( l
               dwStateBit &lt;&lt;= 1). B: j5 O8 g& G. J
        {
* d- h! Z% ~: m8 `5 t            if (varRetVal.lVal &amp; dwStateBit)
# F! S$ L: `% _! H            {
' f  L' z' ?9 Z2 J% o                cChars += GetStateText(dwStateBit,
3 S% x8 a( R8 d, g! L                                       lpszState + cChars,
* ?6 ]' k$ a3 T' \                                       cchState - cChars);7 ?' M9 M) |. a, Q0 e, D6 ^$ {# K% i2 O
*(lpszState + cChars++) = '','';
+ C5 J' A/ A5 c! n5 f2 w            }* d4 O$ V1 G: N; U+ q# m
        }
# T9 }) }8 T* d! o8 `% c# O7 |if(cChars &gt; 1)
6 R0 z# E& W$ Q! X9 n( W*(lpszState + cChars - 1) = ''\0'';
6 w4 s6 d8 I6 \$ U5 P1 P    }/ }6 }3 v6 N8 R9 p0 |
    else if (varRetVal.vt == VT_BSTR)2 k6 Z  a2 A; V
    {. ^: |& M0 U+ E  j4 G8 ]! Q1 J
        WideCharToMultiByte(CP_ACP, $ J+ M1 l; B) b3 b
                            0, ! r. P3 D& X0 B' d) x' K9 Q
                            varRetVal.bstrVal, . G9 t2 m- p: {' x
                            -1, 6 E1 ?- M# Z1 p; {
                            lpszState,0 i5 A& x, g( x' {; X
                            cchState, 4 Z: E, X# b6 f: n
                            NULL,
1 p% |2 q$ a8 h7 k# v1 I3 Z3 ^                            NULL);
* p1 ?. U; ?/ }$ L! y+ D. N! d0 t    }
/ y2 g0 @% C# w1 ~( A
' p  W8 N- E' j5 a8 Y0 |    VariantClear(&amp;varRetVal);4 q' q3 l# g" p9 W
6 K5 J5 L+ e$ t+ x, ~
    return(lstrlen(lpszState));
0 u# E1 e7 N7 ^6 O4 t}</FONT></TD></TR></TABLE></P>8 n3 p0 q2 ~) Q' f! R5 Y
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
& N! @* g/ r% G+ c3 B9 @2 o, \; i* X# l& ?* y1 U3 q8 u/ W1 G
四、在 IAccessible 接口上执行动作
2 h. I5 _, Z; a; w6 {5 r    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。% D8 p+ Y# y& R* V
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。# o, H( ]5 o4 Q  A8 g! n
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
! S% ^7 }: O7 N3 U$ e, R<>) U4 K. K9 p0 m. j3 m% t
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' v0 B/ q. d- G6 q# t8 p

$ s: k/ [3 a/ e% C3 @<TR>  p% h8 L$ V' N2 w7 y; ~
<TD><FONT color=#00309c>//在文本输入框输入"regedit"* i. e! U" k+ l! n& J
if(1 == FindChild (paccMainWindow, "打开(O):", 2 E# [/ d8 K7 q5 g9 H" G
                   "可编辑文字", 3 O5 V7 S; P4 G3 c. B
                   "Edit", ; x/ v5 V  ~) U( ]
                   &amp;paccControl, 9 `, A+ u1 e2 q  ~* l5 \
                   &amp;varControl))
, a+ T: Y0 N6 `) ]* A$ M* r{
& x% l6 Q7 |2 x# F5 |4 M9 @* Z//在这里修改文本编辑框的值0 x6 I) l  T) f4 |; R, n3 I5 M
hr = paccControl-&gt;put_accValue(varControl,
7 H2 T, d5 e2 S) E) q. ], |+ G4 n9 ^                                  CComBSTR("regedit"));
& C8 Q/ ~$ j9 C9 ]$ U3 d6 LpaccControl-&gt;Release();0 w( l* A, B' e0 l2 @
VariantClear(&amp;varControl);
) l8 _) i1 L% ^2 {}  z) N9 P& L  j4 G0 _

4 s, e$ R; Y# B% C% k1 r3 B// 找到确定按钮,并执行默认动作。
7 e  W0 @- x; }$ n, I4 }0 [if(1 == FindChild (paccMainWindow, 7 U1 G; B1 s4 \. J- m4 V
                   "确定", 4 i6 h: [7 ?0 ?6 F: ^- _
                   "按下按钮",
! v& f9 u. b* W5 c9 H                   "Button", 5 k# [7 C! v( Q4 l: `/ r# R
                   &amp;paccControl, # e, z3 n4 v; t
                   &amp;varControl))
  e% C2 V6 R2 \9 N{% ?8 c: u; f! x# ]  A9 ~5 ?
//这里执行按钮的默认动作,即"按下这个按钮"
% M6 P/ f% Z3 _6 bhr = paccControl-&gt;accDoDefaultAction(varControl);
$ s) m& {4 T6 c. ppaccControl-&gt;Release();1 S( U2 N, |0 g5 l. _- G
VariantClear(&amp;varControl);6 `# Z/ n. i& V7 _0 s/ Z
}</FONT></TD></TR></TABLE></P>
/ j+ z1 X: K4 S! i# p. K<>现在,你会发现已经成功启动了注册表编辑器!!
) h" m7 ?: y5 _- X9 S</P>
6 z3 |- k  j0 b$ z1 T5 |. \<>五、模拟键盘和鼠标输入4 D4 o/ F6 A% Y$ G& U6 q
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
& s, a; Z8 U. ^5 a5 i' N    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
4 _, I" [4 M, B    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。' n) j$ W  u4 E5 a) l
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>: J0 f' N+ I+ z5 g% [" [6 K9 n8 r
<>
) t7 O! J5 M7 G* I<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>  b# J# o" U, E9 c7 Y1 m" O, A: G

2 s0 ~, r/ P* a2 p, R5 ~<TR>
) r8 W% a) B5 K: \/ ^5 l<TD><FONT color=#00309c>INPUT input[4]; ' @0 k; p  U2 h4 W! o. z# W  z. l
memset(input, 0, sizeof(input));/ C5 U8 P4 r! H" O
' V& R0 [7 r1 f6 u0 [5 ?
//设置模拟键盘输入
! E% B; f* Y! o0 winput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;+ o8 s/ H' B  d; G# h
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
  t6 ?, ~" t1 Ainput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
7 b- w) w4 D1 q" H+ \& }
3 x% Z. U9 M" G+ X: L3 w8 Z5 M' _// 释放按键,这非常重要
, D, C/ @/ u8 \input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;% ?$ e. t' o" A

% P2 K6 T, @5 q: ~SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
: A% n4 U5 k5 @<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
, }5 F9 J) H% G  }$ e3 x! \</P>
+ ~! {3 O/ q  n2 t" A) F! ^- H  u9 O<>六、监视WinEvents
' w6 h' J4 L! H    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
# z8 {, a: Y' k, G9 r, a2 E    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
* x2 M2 U5 ?' h5 r0 i% n0 h4 S8 |    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
8 c0 G% w/ z; u! W, X$ k+ k<>
  P) ^, {% Z: d1 f: B  i<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>" G9 V  G2 E) c  z* A1 F5 {! W0 J! Y
7 i2 T& q+ f/ T( I! ]" x3 S; Z: N7 s5 C
<TR>9 w1 Z3 I! [* R, C5 k" ?
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
. S2 z7 H" [2 F* L* H{$ c/ @! `  R6 d9 ?* @" N* ^/ E  y
hEventHook = SetWinEventHook(
; F7 \. V) H, x* q6 p- YEVENT_MIN, // eventMin ID
9 @) Z6 N4 t4 @: o; V: @; FEVENT_MAX, // eventMax ID/ v- w5 x4 h) P, J) x$ P0 {' O
NULL, // always NULL for outprocess hook- J, S9 w' h, E# X
WinCreateNotifyProc, // call back function, R! P& y- L  f0 i0 Y% q
0, // idProcess
1 P& g; G4 m& T* H6 X1 v$ `0, // idThread
6 \1 t3 }2 x& A- _  X/ C+ b$ M         // always the same for outproc hook
4 e% W6 S+ f& k  ^WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
6 M7 M& W) _6 d& c6 g" L. Q# A  x}  </FONT></TD></TR></TABLE></P># Z3 A: M* S6 k/ L" l" |

6 C0 U( z' F% u. \8 R% g<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
6 G$ I4 B* W/ H下面是回调函数:</P>
) g! P* l2 @3 y6 Z, j$ P) z<>
% ?% C. S6 ]& {! G, r! V! _0 ?<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>3 P% c9 q, {/ ^1 [
2 T+ i# Q, `! q" k* p$ }
<TR>- m3 l* |# b" u" i0 w9 e- b0 Y' n
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(9 ?, ^/ T8 z. q' o7 R
HWINEVENTHOOK  hEvent,( H) Z4 z/ z4 |7 Z- h, o3 E
DWORD   event,
! u7 e1 j7 p' X: oHWND    hwndMsg,
6 n* d. _5 X- S4 x1 }% u7 eLONG    idObject,
% u3 G* v& _5 @( B$ \5 pLONG    idChild,* |( c6 |  r5 i# N/ K0 m
DWORD   idThread,' v" {/ q4 h8 V$ i- L2 e$ v
DWORD   dwmsEventTime9 W' y- I; J7 j0 W, F
)8 U0 H: I! _8 G, ~
{
3 h/ m# }/ B8 c+ f' R+ y  t
6 I7 o$ u8 z$ L$ x1 iif( event != EVENT_OBJECT_CREATE)* [5 s1 G) ^: X
return;
, Y$ g4 k# P5 h4 O
, H* Z* m$ a: j( N' tchar bufferName[256];
( _% }6 }; ~2 n7 v" k6 sIAccessible *pacc=NULL;
) S3 K4 c+ v" Z- F2 T$ eVARIANT varChild;" @: n$ ~; h! I
    VariantInit(&amp;varChild);, q  Q" u0 n6 B3 T2 {# i: I
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对" ]) M! A+ h# K$ ^' V5 I; k
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,   E) x* U, t5 m$ n$ c
                                      idObject, " |- N7 ]. F' }) M  {* W  _( X
                                      idChild, ! V5 N) d. h( F' |. B# b4 I" S
                                      &amp;pacc, 3 m6 l6 D2 ~  o+ n4 R, h6 p/ @
                                      &amp;varChild);
- C- m  R6 R/ X# d9 \' A( b$ n# O3 H. N) s( D& m5 C
if(!SUCCEEDED(hr))
3 Z2 D* w* j. b{
( n! ?  V" _& @$ @3 j8 x# mVariantClear(&amp;varChild);+ m  @5 F7 t, y' ^1 a
return;
6 Q# C1 m2 Y; ~0 \5 F}# v4 J; @7 M3 J' ?. K
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
: f: x: U- D" Z! I" NGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));3 a  J8 I5 X$ V& |' [' n. n, C# V
if(strstr(bufferName, szMainTitle))
, }3 M% A8 f+ }* }9 kPostThreadMessage(GetCurrentThreadId(),
; b" M  x, n2 J$ O5 F9 W' H                  WM_TARGET_WINDOW_FOUND, # c/ I* R5 e' y
                  0,
0 H9 g% y8 s- W                  0);
3 o8 n3 e) f  V. C8 |3 J2 g
& E! _2 T/ k$ |5 Y0 h: m1 b" Xreturn;8 H/ _* p$ z) Z1 |, V; ~
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。" v, b3 R1 n- O. D
8 i* F5 |# U* G6 F
附录:4 C8 A( c9 ^" O  R) S; S0 e

: N3 x4 ]# O& K1 o- e) Z  D关于IAccessible 接口/子ID对:
3 z* W: w/ u* `    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。0 _9 ]3 e/ K  @: I! g- U
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
# e+ ^4 H2 W- }$ Z! S    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
2 ?3 l2 L; k' c& r& i# a    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
& g( s) k$ c5 P# j    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
7 d$ Z- _: b4 g6 q: P+ b. b0 e* _4 t' l' W5 h
注:7 |# `4 s, C  e5 x
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)7 c- `4 o8 @  W  Y8 ?6 h
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
7 M; Z! p9 _9 M, m, N8 T6 Y; k( x/ K( U
参考资料:
2 W5 O: t# _. s: d) C' Q' P<UL>' J2 R) H' ^/ G: r
<LI>1、 Dmitri Klementiev写的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》及其源程序。http://msdn.microsoft.com/msdnmag/issues/0400/aaccess/default.aspx
' V  D" Y5 [6 n; D! N- 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, 2025-9-10 22:51 , Processed in 1.089020 second(s), 58 queries .

回顶部