QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5668|回复: 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>+ `- f) K% r" W; G0 X
0 i& M( P8 C; n4 u9 D
<TR># M- Y  U" E6 Z& B7 q
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>; a' G' c  x& e, T) J' f, y8 B
原 作 者:崔传凯' N2 o* a4 P# s- n
原 出 处:vckbase
& y7 k' i& G5 f5 W- w发 布 者:loose_went# V- j* w: t& n' e0 N5 m
发布类型:转载
( L. D2 ~% [3 x. f2 q& s4 D发布日期:2004-11-12
! z3 J' T+ U; V9 A3 F今日浏览:6
) t7 w9 r* b! x& r6 Q4 o" }; ~& \总 浏 览:234, @  s+ q$ j; e# N' ]
</TD>
! t1 ?+ r& V0 b4 H<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>" T2 _8 _* E4 @# x) P
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
: }* R( S5 `8 H/ a7 e! k% V8 d. o/ c  O
<TR>" B9 R; x+ x# F2 A+ y+ |
<TD vAlign=top align=left bgColor=#ffffff>: g3 ]1 h9 A* q! S- M; ~

; g( q) g; ^) C<>5 ~- V; U7 o2 n- |; }/ ^4 ~1 z
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; T" {5 A: ]9 x! Y! W9 n
7 I/ V" L3 C" v  P- N  R0 e
<TR>2 ?& f6 G# S% a1 h* v0 J) z0 o
<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' C; p2 G& h
way accessibility aids work with applications running on Microsoft Windows?. It
, W0 t- D  k& C7 y/ q( wprovides dynamic-link libraries that are incorporated into the operating system
# m; }2 g) a, ?. x1 L% x  n$ {as well as a COM interface and application programming elements that provide ! c0 Z# F; H9 E, H
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>7 U- n' U/ U  [& Z
<>一、基础4 [( o$ C6 O) J( j! E1 c3 ?
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
" d  Y: W9 v5 _/ r% g! O8 O' O    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
- ]& V/ J- f$ l) k9 Y7 Q    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>1 Y3 p2 c8 Z) S8 A2 c; {. k- e
<>二、Active Accessibility 原理& @# Z, f, {. K8 l: d
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
; Q: L6 j- J. T1 @2 c    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。# z5 Y3 O% V, ~% O' G, }
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
8 A2 K/ `5 T1 R6 G7 _    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
' i" r+ h+ N7 v% _( i    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>) |) T# W; ]' C$ |+ r7 l3 V/ s  `
<>三、如何得到 IAccessible 接口指针
5 E. [' v2 F5 |    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。6 ?/ w( N1 t% @7 G- Z3 j
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
/ p5 c5 o& ^& N    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
8 q0 ?/ F9 ^5 t' }/ P0 H3 D    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
" u) f7 w# i( s- [' A" C) O$ f- N    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。$ l) N; o: O3 o3 i
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。6 P8 M; T& J( x6 O, k
下面结合代码介绍一下它的用法。0 _& \6 `6 e. z9 B# {- e
我们来得到下面运行窗口的 IAccessible 接口指针。. ~" o4 `8 ]) U) K
! H0 \$ `: c) K7 \3 g- `7 W
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>2 O$ ~( O: ?; v9 S: c7 u9 T
<>图一</P>
. x# a9 r4 e( O5 Y& L) H0 V2 K<>$ h% s: C& ?) L% D8 {; ~7 L
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>5 d: Q3 S( h$ f" F7 y
! ~/ U% Q( p! R( [
<TR>
6 M$ e& w8 I& X, b9 d  d<TD><FONT color=#00309c>HWND hWndMainWindow;
, r, X+ j, @4 G+ h  RIAccessible *paccMainWindow = NULL;
8 M7 Y, C! y  V$ \% F8 cHRESULT hr;
9 u- D: A- \2 L- Z//得到标题为"运行"的窗口的句柄  ]7 g* `5 A( R3 O, F$ N
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行"))), D/ }4 ~# D& Q7 m& V
{7 S% Q4 m% Y% t' F9 `
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);' j: o" k% j% b0 f9 ]- u5 h0 d
}) v) s- I7 A- O
else# p) S, m" M/ S" k. ?, N
{" b2 S* v3 g/ e/ s1 n" r- ^( \
//通过窗口句柄得到窗口的 IAccessible 接口指针。
; Y2 v3 W, Z5 Y% Gif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, 4 `- _( l6 _0 B$ n( v# c3 A* i
                                            OBJID_WINDOW,
" L. }8 D. x5 s- C                                            IID_IAccessible,9 L6 Z% [* T+ O
                                            (void**)&amp;paccMainWindow)))
1 p3 I* M6 V1 [) _7 N& J, w) f{5 H3 u2 x0 K, A# d2 k
//……我们可以通过这个指针paccMainWindow进行操作。7 g; t! p/ ]! Z* }, [
paccMainWindow-&gt;Release();" I. W& f0 p4 L7 q
        }
! \! Y0 p0 G. e' P, {- o}    </FONT></TD></TR></TABLE></P>$ L  x! L" P- o0 y
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!$ I4 f# m7 p- w! ?/ E
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
6 D, W' s# q) I* d4 b    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>' d$ P" [! H1 z: n
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
  e2 p  J1 [$ Q2 o  ~0 T9 Y<>图二</P>
5 W: B/ @" Y( ^2 `( j<>我们现在主要关注的信息是:Name、Role、Window className。</P>2 D8 H  Z* W4 R
<>
# _: D2 P( l8 W2 e! h<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>9 v% d( v0 X( V# n$ c6 X! U" m8 `
9 C4 C3 I9 R8 }+ Z# e) p
<TR>
' A  }% V: T( A<TD><FONT color=#00309c>Name = "打开(O):"- _* t1 k7 `- l6 E& r- N. Q
Role = "可编辑文字"7 y9 x  p: I2 w1 N' X/ E* c( f8 G
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
0 Y- z  I6 L- ~1 o4 s- }; Y* \: @<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。" T8 n' Z9 o' j2 n( D/ e
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
4 |) E3 r3 ?& y9 p下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
. K1 m! a# X0 l; u7 O4 f<>
) a/ P, @% |/ b  d8 C<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0># t; F% N: ?' d. U

6 u: A/ k) v, p; V! O9 }<TR>( M$ R/ k/ ?2 A4 H& j1 G0 I
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
9 d' i0 H4 Q; K) O/ DVARIANT varControl;    //子ID。0 X$ ]* `# i  r

/ W. D3 e& `+ K0 c* L* H  b* HFindChild( paccMainWindow,
# F/ ^, y$ N* H" ]* {  q! ~           "打开(O):", . y0 P5 d/ L( _( ~" `
           "可编辑文字", # s/ ]$ [' Z. W* X
           "Edit", 4 }  b) V0 O2 U% n
           &amp;paccControl, - Y" V) H% I* M6 w1 M
           &amp;varControl ) </FONT></TD></TR></TABLE></P>5 p+ @! V- q% c) n" l
<>第一个参数是先前得到的窗口 IAccessible 接口指针。* [  e7 K! t  @' v+ m/ f
第二、三、四个参数分别是名字、角色、类。
8 u! I- s  G( W- h) b后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>- F  N2 N! ?% B3 L5 O5 S. E
<>+ U" M' x) c( Z& @; k
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; [+ |% k( |; j  J( L! i- u5 ~9 b$ V" X+ s; i7 a" j; W
<TR>" l/ ~3 S0 Z2 S9 R( m: R' c8 y
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
/ s: n0 ?6 U2 V7 N                         LPSTR szName, LPSTR szRole, 1 n# p2 f/ \) _. d
                         LPSTR szClass, 0 Q: D/ P% Q: s2 z4 f" w: Y) r, d
                         IAccessible** paccChild,
; |# {* _3 Z" t0 F                         VARIANT* pvarChild)* L: }% U0 q2 f& E
{
' V$ S3 i! K( S+ G; w% }HRESULT hr;% u  V+ E" Z* v, }. R# x
long numChildren;% v/ N2 b" \+ Y: o( j
unsigned long numFetched;
3 k$ Q' i/ S4 o) D7 EVARIANT varChild;7 Y7 \* _7 ~/ u( n( Y5 b; [
int index;
# P" ]8 G1 M% i% p) t. f" L% LIAccessible* pCAcc = NULL;
* `4 B9 G9 ]1 D! I# W6 Z, o- nIEnumVARIANT* pEnum = NULL;. A# a4 [" F, d% P8 f8 U
IDispatch* pDisp = NULL;+ j/ d' O  ~* ?$ Z/ k
BOOL found = false;
( B- s2 K$ ^9 t/ T0 }char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];, y5 f( u/ x  }
; ^) p# B. L8 Z
//得到父亲支持的IEnumVARIANT接口% r+ O/ H/ J9 C. r+ h
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);* H6 C$ a. }! B+ s

* E, C0 b' G5 `6 U8 y! F) {! u9 zif(pEnum)
, O: c* O" C, _7 A7 g9 k8 r4 CpEnum -&gt; Reset();
  H/ V* w5 H) ^; H6 [; o0 `& K4 t8 K! j5 `0 ^) U% e9 a. I
//取得父亲拥有的可访问的子的数目
" J) b" p. @0 t- y8 ^2 _, ppaccParent -&gt; get_accChildCount(&amp;numChildren);" J# F3 T; d2 r2 C+ L5 A

/ S2 q% f0 O1 u//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。; C5 F$ `9 M" a0 J% D% u' l( p
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)  c' t9 x4 q% o6 ?" Z1 U# F
{
8 y3 i6 G8 b( y3 PpCAcc = NULL; 2 Y8 Y( D4 x; c! E2 `4 J8 C
// 如果支持IEnumVARIANT接口,得到下一个子ID. T1 Y) j) A9 m3 p8 }+ W5 A4 O2 s
//以及其对应的 IDispatch 接口+ n$ \  }2 _  [) n1 p
if (pEnum)
1 ]; J) C3 n7 B% ]# {1 nhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched);
( B8 E) V  ?+ @6 [+ R  b& telse
8 l0 l: L: H6 c1 n{
  c4 F/ j3 E' ^//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号( d2 G) @7 {& I1 q9 L( G* }& J6 `
varChild.vt = VT_I4;1 p0 p4 L( R. W* D. E
varChild.lVal = index;" w. [% S0 g& x, P! f) p
}; Y* u4 R/ q8 X- t4 ]
( y+ i5 x' o( U
// 找到此子ID对应的 IDispatch 接口( S( m# Q. F. y5 C9 v6 P% _
if (varChild.vt == VT_I4)$ a+ f" K' p1 a, _/ p8 l4 }& E
{
1 o) U  t% N* s- }5 p" W% a//通过子ID序号得到对应的 IDispatch 接口! J) M% |% d9 p- u
pDisp = NULL;
4 R5 A8 F  P9 v' Rhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
) \5 x$ J: Q# D5 h/ K}
, _9 B! b! }  ~& A% u) u* Melse( e9 ^$ D. j! X/ ^( `" V! H
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口3 M5 r* W) s2 l. z, V8 k
pDisp = varChild.pdispVal;
) W; t) O3 l, ?% N6 Y
5 O* S) f8 p. J: M% O6 B- P, X& P// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc" [' a5 Y( J' ]5 g! Z
if (pDisp)9 N; `( b+ z8 v2 t, D0 B
{/ N  r; R7 S- r7 }
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
/ j; O- o7 w$ Y& l" ^( \hr = pDisp-&gt;Release();( q7 @. F% O; I7 t( i
}
" c/ m* h4 ^6 }. \
; e8 X, N0 ]$ e$ O2 q7 P// Get information about the child
# w" T- W& C& P1 uif(pCAcc). C& z$ h" s0 ^, K8 a) u+ f
{) s2 N7 k+ x( d" H# Z
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF8 D5 R2 q( h. N
VariantInit(&amp;varChild);
5 r4 C( @. y# y! E# m* k* \; p, vvarChild.vt = VT_I4;/ z" l3 h! g3 j; a/ m1 \
varChild.lVal = CHILDID_SELF;
/ e; j$ i% \: {* y
7 r( {, o+ b, m- l- o; z, N*paccChild = pCAcc;
) ^" a/ r* p1 C% a}
4 M, v; |% E) b6 a# a; Gelse
" F$ C0 w8 g/ p9 d  q3 f) M//如果子不支持IAccessible 接口
/ }, E, D1 G* y5 u% m2 D& o*paccChild = paccParent;5 t, [# o( B. A) S5 b: h

1 f8 ^. X% m6 }8 m2 x. _//跳过了有不可访问状态的元素+ c, C9 x+ z1 n* l
GetObjectState(*paccChild,
" v9 [9 z( A+ n. K0 S5 j0 s* z               &amp;varChild, & j; g# S+ P1 e
               szObjState,
' C7 ^1 _; q0 i( W) u( L' K/ X               sizeof(szObjState));
. L: Z' i- [4 H- R$ Tif(NULL != strstr(szObjState, "unavailable"))! }( h8 B* ]8 ~' c3 Q. t" K& H
{8 J2 Y' J( e0 p; l' {8 u
if(pCAcc). d% S1 @& E- A
pCAcc-&gt;Release();
6 l, E6 C& b' bcontinue;- s( s# u$ y: C5 r
}. x- h. M3 r6 h7 ~
//通过get_accName得到Name
4 t, p; |& V- I4 bGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
4 O) k3 y! [8 Y3 [4 ]7 U+ m//通过get_accRole得到Role/ z5 ~: M. l* t  }; `( U& m1 @
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));7 S4 M2 @- G/ Z+ P( |1 m
//通过WindowFromAccessibleObject和GetClassName得到Class  m5 D+ }% x) W4 T7 H! h
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
5 ]1 O6 R$ t# a( G' f+ o//以上实现代码比较简单,大家自己看代码吧。
5 G' H/ N7 m5 C" y2 c3 H* n1 X+ U$ U% q; Y; w9 ^3 f% D& {# N
//如果这些参数与输入相符或输入为NULL: Y! l" h  C0 d3 [
if ((!szName ||
4 V+ |; M' t1 y0 j5 @     !strcmp(szName, szObjName)) &amp;&amp; - S8 s; P- x* V; R6 L, z
     (!szRole ||
7 J  N  n8 O# c      !strcmp(szRole, szObjRole)) &amp;&amp;
/ p9 W$ j( h7 ]* Q1 Y: O     (!szClass || / l6 ^* n' ~& ]* L. X- ]/ {
      !strcmp(szClass, szObjClass)))( {& h+ R7 y4 i8 {2 Z9 A
{
# y( r5 t, Z4 V' c; Q* efound = true;
  D0 K3 J' G0 t*pvarChild = varChild;
5 Z0 M( D8 A2 g2 q1 d4 @9 H$ M/ zbreak;! b* a7 i9 J: N4 L$ W# S7 t- \
}
; p% l( _& W) W0 Q  k4 r4 J9 @if(!found &amp;&amp; pCAcc)5 {  p% O& H5 }; L# u. ~
{
+ F7 ~# `( ~% i: X// 以这次得到的子接口为父递归调用
% }' @# r. u; s) k* i  G- hfound = FindChild(pCAcc,
) s: Y9 i% h6 R& L0 S                  szName,
! `5 S. d* v1 T% Z# E                  szRole, ! U: |  p  K/ w$ ?  v+ O- U
                  szClass, 4 k3 |! I, N' T. S) O1 s0 {% k' x
                  paccChild,
2 V" V' F# F/ t6 R' s! I                  pvarChild);) G9 h" i' [2 D
if(*paccChild != pCAcc)& |- O" q$ h% x# {6 Y: w, P
pCAcc-&gt;Release();, a: y: o* T! w: i3 }
}$ W& C1 W9 E9 m/ X; I4 Y
}//End for
( f$ l4 L, c- g4 F" @, V+ Q5 E6 X2 F# f+ Q3 r/ Z. F8 l5 u
// Clean up
, s' z3 P+ p) v5 l( nif(pEnum)( @- J7 \% S9 l- l6 N, L
pEnum -&gt; Release();9 A) |, W! p0 u7 R) E4 U4 Z
/ [3 t( c! H# ]8 N
return found;8 J7 Q, ]: O% v1 O* j
}( {0 ]4 v/ K& W) y/ }! D
8 d. o) }% I1 V' a
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
) g- G( @& \7 F+ ?/ w/ M+ M. z//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
2 z% h2 ~. f4 n5 M8 n7 i$ k//将这些或数转换成相应的用逗号分割的状态字符串。
, ~) F0 ~/ n. }; `% mUINT GetObjectState(IAccessible* pacc,
1 V, W- T$ }" ?/ R0 e; n# F: t                    VARIANT* pvarChild,
+ ?9 _1 Q& g( s+ ^5 z                    LPTSTR lpszState,
# M: X1 F% z+ g/ J- ~0 g                    UINT cchState)
; X2 h: f! \+ K& K8 X0 z{$ w4 C% }9 ?3 _, L- d. e: T
    HRESULT hr;
: E6 E) R% d6 ]* r/ E5 @7 Y! p5 F    VARIANT varRetVal;) C" u7 C: g, u/ V1 ^
8 i; x0 L! }0 n  j
    *lpszState = 0;& a8 K3 p5 \9 h. \( _

+ y6 n7 |2 ?7 P4 o: m" ^    VariantInit(&amp;varRetVal);2 P- N0 A  ]. Q2 ~

% y7 g; w$ b* x* e    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);$ I6 S. }  m) ~! z; T
) j& _$ x# E+ Y: q  O  W$ A/ a
if (!SUCCEEDED(hr))
3 ]+ X3 O1 ^) S        return(0);
4 c5 C0 D3 w. P1 B8 y6 t
$ }2 U% X$ ~7 m  y* MDWORD dwStateBit;
0 L9 }8 i$ |* L" E; ?int cChars = 0;8 V& A$ U+ w- R9 M* ?
    if (varRetVal.vt == VT_I4)! z1 J) a& a) g6 l/ A- U
{0 g8 k7 m6 R, Y% r
// 根据返回的状态值生成以逗号连接的字符串。
/ O& X8 ^4 P. `        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; $ q2 Q% I& V# W
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
( t+ P' x8 [* ^, A& a8 L               dwStateBit &lt;&lt;= 1)6 O6 j9 c  x4 i# G1 N
        {
/ E$ {  W  L. _; V            if (varRetVal.lVal &amp; dwStateBit)
% W! _, i2 y. r) ~) x# w            {2 T  {# o$ J/ G* j. [0 O4 [. J
                cChars += GetStateText(dwStateBit,
5 J, j* P) y' t( D* Y3 D* M3 v                                       lpszState + cChars,
  g8 u: l% F- y. N                                       cchState - cChars);
6 ?: c/ q3 N: g9 k5 w4 g9 O2 a# V3 F7 T*(lpszState + cChars++) = '','';" K7 c7 _- g$ J1 G0 P
            }
) n/ }" k5 m! ^. X9 j5 n; f        }
- I. N' S. t2 H) w+ |8 tif(cChars &gt; 1)
: Q; ^( ^- j( _* l- g4 R*(lpszState + cChars - 1) = ''\0'';
1 o* b0 l- {% A1 P, t! c. n; k* o    }7 A' R9 q4 T* S' O
    else if (varRetVal.vt == VT_BSTR)$ Q9 V  @# Z, i# J8 M3 b
    {
* u( ^' H! R3 _! @: C# n        WideCharToMultiByte(CP_ACP,   A! A' X+ p+ c1 \: {! }8 g
                            0, $ Y7 l2 [5 @1 ?' q0 E9 S) G! }
                            varRetVal.bstrVal, 8 s( N& I+ A3 b6 v
                            -1, * h& B# Y, I/ M* @" Q
                            lpszState,4 M+ f/ {9 V" }! K
                            cchState, ' b# R, q7 ]! I
                            NULL,
$ F7 U, N% a! n/ q                            NULL);: h" N- `3 d0 Z) L) I
    }/ F& Q$ P6 u. P, k1 P3 P  G

  _4 b: G, G5 D: e  G. G    VariantClear(&amp;varRetVal);7 ~" ?# F( K4 ]1 X+ h; {: n. z
& _5 j7 b* h1 h( P. \* N
    return(lstrlen(lpszState));, ?1 l7 d1 V! a" l. v1 v
}</FONT></TD></TR></TABLE></P>8 c0 \. C" `! _/ Y
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
* f+ u! e9 p9 O; {) u
' h$ a  Q1 F2 b- C6 F- {* M四、在 IAccessible 接口上执行动作" a0 _6 [5 f( X/ o& S$ i
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。0 M* D8 G- B% c2 R5 r4 F; d, b
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
6 U6 s+ W- Q1 ]- _* T3 F2 }    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>% J( X/ p* k3 I! X
<>
  n% F0 K# H, p1 l0 p8 z<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
+ w* ~9 ?+ d6 Q" @( T6 ~2 A+ _6 M3 U4 o
<TR>& u. o9 `# \( s6 h6 W9 M2 z  `3 }
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
6 b( B) @, w, v9 S# S' A# j5 n& \if(1 == FindChild (paccMainWindow, "打开(O):", - n+ p4 e$ g- Q1 E
                   "可编辑文字",
* x; E! [6 J8 a5 R: J                   "Edit",
( u) \- m2 L4 u0 c                   &amp;paccControl, 6 ]3 _" }: G! H2 q; p+ V  L
                   &amp;varControl))
0 c9 }" y9 m2 m- q% e{* p4 `7 M5 n8 Z7 a, o
//在这里修改文本编辑框的值: n) N4 O) p0 |( k7 z/ ~7 }& t# z
hr = paccControl-&gt;put_accValue(varControl, , i2 w( U6 ^% ]
                                  CComBSTR("regedit"));# w  P7 \: u+ a! T! O/ `8 W
paccControl-&gt;Release();! v' A. L! [& |$ w. X
VariantClear(&amp;varControl);) @: o" Z1 _6 S) w
}
# ~4 h, z, Y  h4 ~. j/ n
) G7 j* e6 T6 l: i// 找到确定按钮,并执行默认动作。
6 C! K. N+ {$ S( D+ X1 {5 p& A2 {if(1 == FindChild (paccMainWindow,
! o0 G7 y' W- K                   "确定", , `6 f! R% u! o4 a
                   "按下按钮", 2 f1 T1 r* E, q/ C% a: c+ N% o
                   "Button", . ^4 h; Y! d$ _
                   &amp;paccControl, 8 m6 f' L" Z# n. H
                   &amp;varControl))
+ b) c  d. J+ l{! I5 `9 H; b9 ?5 e8 P* p
//这里执行按钮的默认动作,即"按下这个按钮". ]+ O1 J( J  Q- N
hr = paccControl-&gt;accDoDefaultAction(varControl);" B' m; a" F6 u% N* X! }4 E
paccControl-&gt;Release();
3 h$ F  d3 ~* |& o6 cVariantClear(&amp;varControl);
9 |& f0 S- h: ?+ m% E; D, }, I}</FONT></TD></TR></TABLE></P>
8 f, s; D8 {' y% h+ P<>现在,你会发现已经成功启动了注册表编辑器!!5 \8 g5 e7 M& _/ L
</P># z+ Y% {* O) t9 I+ W2 l- O
<>五、模拟键盘和鼠标输入( @) h) A0 t7 \
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。6 m. m! G3 Q6 H0 R' e; n( P1 ^
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。1 {7 i- Z% z8 q# @8 S/ k5 ~4 [
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
+ j; u1 i+ U) d9 z下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>2 D" m. H* W3 l
<>: v6 N# d6 P. k5 q7 n- z
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
, V& K& G2 _: y$ C5 J$ R. |# i: \$ }: \* x% {! L
<TR>
' \  W" ^5 s6 M% V# O3 W, Y5 ~4 x. w<TD><FONT color=#00309c>INPUT input[4];
6 P, @3 _3 _& Tmemset(input, 0, sizeof(input));) R2 E) p! B" B+ x0 n$ o: x

9 J8 @' p! m2 `% c//设置模拟键盘输入# T: G; r9 o7 E2 X1 K. R5 C
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;, o! n. E/ T0 D4 X* Z1 |2 m8 }
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
- ~% L; U* B* {6 c* yinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
( {# o1 |  [, p# B" ]3 W% P* z4 J; l1 b
// 释放按键,这非常重要
# T* r/ r' y, I0 T9 Z7 {7 Tinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;' J0 Z. C+ M& D# N) a

0 N/ x+ {5 `% A7 ZSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
0 n7 Y$ Y: w# w% Z7 _1 q<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)  k+ N: Y' Q+ u9 D7 f$ e
</P>% a! a* h8 g1 W% n5 ]7 B
<>六、监视WinEvents& J" z8 h9 \8 E3 c% C
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。- }; Y2 }- G0 f( N) [) g
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。& D+ F# M7 }" c2 q0 @/ n
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
3 I2 \( D& ~4 U5 r<>
; |) \! ~7 k+ P0 |<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
- G* y; U& T# W5 S; C1 v: C; E
* ]- u5 j+ |- z3 N- ?<TR>+ W3 q. ]& X, O5 ~9 l
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))# a- c9 H2 Z8 T7 ?( Z3 x
{" T8 A% i  B2 J
hEventHook = SetWinEventHook(2 a" v8 P) O3 Y" a( h8 E
EVENT_MIN, // eventMin ID! R4 S/ _8 |1 t5 b* E0 N. |
EVENT_MAX, // eventMax ID
+ Y/ Q5 ^% {+ B6 aNULL, // always NULL for outprocess hook2 [. c3 E* p+ |) Q( w
WinCreateNotifyProc, // call back function% {4 ]3 |5 |2 a2 |; D
0, // idProcess
, a# `8 W8 M' u# f9 T1 }3 x1 r0, // idThread
* W+ o8 x9 N$ w5 h/ |: z* {         // always the same for outproc hook
4 D0 W4 [* ^: b: \7 V9 QWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
4 a6 z+ _9 g3 I  o! [4 B' M$ `}  </FONT></TD></TR></TABLE></P>' o3 k4 g4 w! S! Z7 i

/ H2 X8 ?& c4 A! ?7 U% {. n) g4 r. F<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
& p( R! C) k% I' `& `' f, v下面是回调函数:</P>+ f5 ~( G: x( l/ A  x" l3 X, J" h
<>- D7 Y7 U: D$ ^$ P% ]2 E
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; k5 `/ y+ A2 H( K: U& f8 \
5 u2 \- R9 _7 k. M) u( D# Y
<TR>1 `4 U$ N% g, w# s( _
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
" E$ _/ s( c( @; `1 D; P3 hHWINEVENTHOOK  hEvent,- y: u5 D" {3 b2 x) |$ P4 u
DWORD   event,+ H0 D7 A9 C. U+ p2 u$ Q1 S% @
HWND    hwndMsg,
5 s5 _  v2 U6 `) oLONG    idObject,5 g" }1 ~/ ~! f( s
LONG    idChild," {  T' }1 ^$ z. f4 F9 X6 I9 [# H
DWORD   idThread,5 o" v/ j; T/ s; M
DWORD   dwmsEventTime" s& y0 h3 w; u+ I! A0 _" }1 p) D
)
% K) |1 Z& h- S9 w2 Q% `{
" ~0 @# a6 P$ S% [
9 B3 m2 d7 e" p5 q7 {( R( Cif( event != EVENT_OBJECT_CREATE)/ f/ z/ M9 \: A
return;
- o. ^' s6 l9 a" x& v. N0 H$ _7 {+ n
char bufferName[256];' C2 w! j* u+ S7 M4 {, D
IAccessible *pacc=NULL;
5 }8 _4 \8 S* L% }: Q2 CVARIANT varChild;4 U5 y/ W' {+ n, G/ o( S+ X) Z
    VariantInit(&amp;varChild);$ u# |7 G' p! e7 I
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
( |' R9 m" o  c4 I" z0 E) hHRESULT hr= AccessibleObjectFromEvent(hwndMsg,
+ E, u* b* i) L1 v( A/ Z" \                                      idObject, ' O4 [7 F0 G: T) z, E: w
                                      idChild,
. a2 P) K; {, B$ d) X* K7 W                                      &amp;pacc, ' W9 |/ t: l; I" H# Q! U
                                      &amp;varChild);2 l+ z0 h, ?, {# ]$ Q2 M( }2 A
9 T$ Z; g! a6 E% z6 L( \# u% J4 \
if(!SUCCEEDED(hr))
3 k, Y# v% I# q  `! c0 N( F! y{
( \( ?1 }% s" {( BVariantClear(&amp;varChild);4 L6 ]9 V0 N, I4 W
return;" a, J- m, t! W# c* j
}5 x6 T! U6 }' y& ^: Q+ L
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。  z2 m+ u, r9 f; K* `% s
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
% i* }. Z* N  i6 N0 l. W2 iif(strstr(bufferName, szMainTitle))2 h4 v6 }' T1 g" p) }+ [: N
PostThreadMessage(GetCurrentThreadId(), : _: O$ `2 y1 U7 v6 P* P
                  WM_TARGET_WINDOW_FOUND,
$ U( o4 c7 @& R5 F& ]# }/ V7 J6 e' s                  0,
. L) I# C, [) i& o7 O; e                  0);; }3 Y; {; U3 T7 c/ D
  ~: H# _# _3 x( `
return;
" N" e# L+ t" [+ H; o/ n) w}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。+ y: @7 f, x* Y: G! u1 ^3 N3 R9 g
" h7 H+ t9 r- [' X7 b8 v6 w1 D- U
附录:
" X- y+ u  Z5 B& s2 Z6 I8 G5 d: ~3 N, u0 F& w  {
关于IAccessible 接口/子ID对:  p4 o: E2 t; k8 }+ E2 k- O
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。6 W7 ~$ G- c7 w8 S8 ^
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!# z1 w5 s% s) X" L4 o& |
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。; C8 K, j) ~. |" D" h: x
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。3 q1 E- m$ k8 O/ z1 e) L7 L. J' ^( {
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
# M! i6 `/ S# v& L9 }
' b$ j' x; j2 i& \! r1 a/ {; E注:% ]$ A# z) V6 }
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
; w* |* _# m+ q    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
1 G  T* m# f9 {, h: N, {
. H  h3 Q  N. Q% ^1 O- p参考资料: 7 m; E1 ]$ f! [( E4 j% p
<UL>
  S* z* u. P  w: g. \! x<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
; g  a. p3 o; p; k/ V8 X) }<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-4-11 17:27 , Processed in 0.443029 second(s), 64 queries .

回顶部