QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5768|回复: 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>& k, f0 s& D2 v" G; `% Q

( T5 ~, r' b* j& ]<TR>
1 j, B5 [; A! r/ n  b<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
2 b# y4 u8 N# U$ |' U( Z原 作 者:崔传凯9 h. q& l4 R0 ~% b
原 出 处:vckbase) C5 {% R# v5 S
发 布 者:loose_went
9 \2 v* ^& g5 y# ^% ]发布类型:转载
* F* `- Q+ _0 M+ E发布日期:2004-11-12
# A7 j% ]+ h9 P. |今日浏览:6- L9 T$ J; P) S) u6 b
总 浏 览:234
% J8 [- C# d6 H& D; ~, Q</TD>- @6 g7 r& i4 Z
<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>7 X, T4 C9 R5 O( j$ X/ Z. Y* c, ?
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>0 e3 g3 e' x$ L: v5 \8 e4 ]
( P7 E9 v; L3 E$ v$ u# |
<TR>1 N: T5 c' O# ]9 u  S
<TD vAlign=top align=left bgColor=#ffffff>
9 o5 ]. j5 {2 X; @% `! Q  h; J  [
<>2 e$ V7 d' f; T% r9 n: g, X
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>8 A* r6 J7 F3 v1 N! u& z" ^

! G( F& e( {: f. w7 H. h- H9 f<TR>
: e- d: _! S& M) E8 m4 S) l/ G<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 5 l0 @8 L: N2 I8 @$ R" T& ~5 |
way accessibility aids work with applications running on Microsoft Windows?. It . }* ]0 E" t- F/ Q6 v4 C
provides dynamic-link libraries that are incorporated into the operating system 6 @9 {2 v1 j# m! x
as well as a COM interface and application programming elements that provide 4 p1 c9 W" Q) @- I+ R
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
& z' E9 g+ L& z: {' h: n, c0 A<>一、基础" B5 F0 k7 N/ d- c6 X" \
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。; ]' {. n, f' H
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
( p5 _9 ?% a* U% b* w7 X4 q    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
# e0 G$ q0 L% r* p5 x& g<>二、Active Accessibility 原理( h2 g0 M/ U4 @& Q5 g
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
1 [5 X+ p2 E! y: c5 F    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
* P' I3 H/ m2 \  i! r    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。' P9 U$ e9 G% u2 e0 J
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。. ]! E6 b2 t9 z9 K8 G$ Q
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>" o$ r; p* T# c/ D5 I
<>三、如何得到 IAccessible 接口指针
3 @# y  V, @% K- D, f4 |    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
3 C' I; }6 m4 ]$ |: E: N    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。3 T& B/ B5 K; o! ?% V) w
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
# Z( L& j: P: O7 Z' a+ {    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。. D" H7 s) N2 q* P8 B: B# G
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。  [) p# X9 I1 H6 P4 H) c; m) G
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。: X" o6 h% [8 b
下面结合代码介绍一下它的用法。
; u6 z# [0 X8 I& h0 L我们来得到下面运行窗口的 IAccessible 接口指针。
0 N' j1 Z+ a0 ~. \! ^- r) z( d+ y7 K5 N+ z% Z. s2 o. \6 {5 f
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>! j; c* D! D6 v8 p6 v
<>图一</P>
$ m0 \0 d% j9 q. H$ [9 J<>4 }) P( B5 E3 R+ v
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
; y/ D. x  r9 Q/ P+ W. N
+ M6 c: q/ z! ^. E1 ~7 T* {5 ^! a, U<TR>
  B7 d3 z) j1 i<TD><FONT color=#00309c>HWND hWndMainWindow;8 Q/ ]  e( f9 ~  e9 q  W. k
IAccessible *paccMainWindow = NULL;& ^' `) ]4 R# F2 w' t
HRESULT hr;! l3 I3 ?7 [3 ]) j9 Y
//得到标题为"运行"的窗口的句柄
7 b  O0 Z4 A$ b; s) Vif(NULL == (hWndMainWindow = FindWindow(NULL, "运行"))). R  @3 K9 J4 h
{& w: i0 J( w5 h
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
; U$ G: G- y) r: }6 R}* k! a: N( o: {# H4 r0 K
else0 |) d% w1 R/ Q2 ]4 |
{8 J. X' Q, j$ y) t1 H* W
//通过窗口句柄得到窗口的 IAccessible 接口指针。
$ n! l* k2 d, I" b% ?7 [if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, , R6 X; u1 @% v8 F! ]" C* s
                                            OBJID_WINDOW, + X9 d( c! Y# f$ Q6 {. T. f5 e
                                            IID_IAccessible,
5 K  [! ^7 X9 B: x8 R- X                                            (void**)&amp;paccMainWindow)))9 s1 N8 l5 M; D# I' W/ g' Z
{  Q% c, K' A/ e, y% N6 Z. I
//……我们可以通过这个指针paccMainWindow进行操作。
2 {: i0 Q; z; U/ u1 F. J  K8 VpaccMainWindow-&gt;Release();
! a) G* i; m+ O" M! R# u        }
3 J. _* a: {# y& s}    </FONT></TD></TR></TABLE></P>
9 \* q6 Z* [/ R! A8 k<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
( e; C5 s- O+ z& s! [! W- Z. `    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
1 U2 I6 R' h' _& i, Z    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>; u! A" I. w  `* ?4 V
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
' g4 x/ p. c3 Q; f5 l3 d: F6 ?/ S! v<>图二</P>  }( _2 {6 |( r; ~
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
, R; [) G& m9 Q! j# A- i<>
$ |+ H" C; ]* N8 {2 v. W<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>4 U/ ~; l2 d  I4 ^4 d

5 o% E8 F& b4 \4 T. p2 Z<TR>5 y! O: n9 i, S. T
<TD><FONT color=#00309c>Name = "打开(O):"
- x& K% m9 Z' QRole = "可编辑文字"5 k9 ~6 G% C1 L0 x( l: f
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
4 {7 m: o7 j4 j<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
' w) |* c8 p% C    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
/ [) H6 |7 [! x7 i下面我们开始取文本输入框的 IAccessible 接口指针。 </P>& p2 h% {' y' l( `) r$ X, l  s
<>" r8 ]0 n7 A  Z/ p4 P
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
0 P* K7 a* a% N& [: U5 W6 K7 P
# f: [/ y2 |  H$ }: {" j<TR>
) ]1 F6 a* J+ s1 D; ?, R3 V<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口/ r2 |, U9 g$ }2 M; p0 Y
VARIANT varControl;    //子ID。
/ G3 G7 h1 M1 _! ~  f5 z0 d& v" Y% E: v
FindChild( paccMainWindow,
: Z2 |! Q% |# B8 O/ {+ |4 T' n8 `           "打开(O):",
) x; A# e, i, o+ x1 S' B           "可编辑文字",
) B3 _" t2 a3 f* x& R7 N: M           "Edit", - m0 H3 Y7 `& b% u7 z' @6 O
           &amp;paccControl,
+ W6 B1 `& D+ r+ \           &amp;varControl ) </FONT></TD></TR></TABLE></P>" Q# H" Z) s. ^
<>第一个参数是先前得到的窗口 IAccessible 接口指针。0 Y# ?) k1 H2 [; p; \
第二、三、四个参数分别是名字、角色、类。7 x& h. s% K2 w/ L7 y7 t
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
: J* K, H/ P2 v$ u- c' @" l* |<>7 p) Y* ~/ w) \8 M. S% H, |
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" T: ~: }1 G/ z7 \1 T) B/ A/ {6 e! i8 v! v
<TR>
  n* E) K7 f9 f( s2 D3 }, u<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
$ P$ W7 O( Y( R! a4 d- I6 E9 Y                         LPSTR szName, LPSTR szRole,
& i% x! h$ n; g                         LPSTR szClass, 6 T: }& z6 @, U4 I* D, S6 ~; h
                         IAccessible** paccChild,
' J0 h9 V& Z6 p" y9 E5 @8 v3 |% h                         VARIANT* pvarChild)6 n8 W* z8 S3 U
{
" ~. k6 F8 K9 [4 kHRESULT hr;
3 I* b0 }' n- n8 zlong numChildren;
8 ?/ u( l5 c7 nunsigned long numFetched;  S; ?. F+ m: O/ v9 }& H
VARIANT varChild;
0 f, a3 b  Q- S2 ]0 {2 b) b# w+ ?9 Hint index;
1 ]( E. u+ z9 i1 X- Y& Q* jIAccessible* pCAcc = NULL;
$ V; C- p* ?- e7 L$ vIEnumVARIANT* pEnum = NULL;
2 [, K( O/ ~0 cIDispatch* pDisp = NULL;
. Z- ]: G8 t( E# V1 FBOOL found = false;; i8 q7 W: ]1 `2 C! W2 v. P
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];) [6 `& o0 Q3 A8 g
0 m1 h4 ^% u( Q, H5 p
//得到父亲支持的IEnumVARIANT接口! Q! v* J  t0 G* q0 Q% ~
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
- }+ @7 q" E: ?5 k+ q1 N/ W/ f) R6 k$ \# h& d% W8 {5 Y
if(pEnum)/ m/ i" Z9 H8 G+ F; a" I
pEnum -&gt; Reset();
) `. u: s4 _, Y  x& G2 h/ h0 ]+ `9 Z
& r; a3 ^& n9 ^% k- Q% p) S//取得父亲拥有的可访问的子的数目! I3 J0 s  C+ O9 m4 R5 B6 ~1 y2 e2 ?
paccParent -&gt; get_accChildCount(&amp;numChildren);6 O6 ~& r3 I2 b; X- ~( S! ]& l$ @

: T% m: i" U  s* }$ V+ A, S//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
& I* ~, m% |- E2 L; m3 efor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)# `! @. c3 U" d4 {. h
{4 d1 `3 R" U+ O( Z3 m# W
pCAcc = NULL; ' G# U7 J/ T& t* Y) n* ^! \0 N
// 如果支持IEnumVARIANT接口,得到下一个子ID
0 g3 @# E0 N, \0 ]: r//以及其对应的 IDispatch 接口1 D1 J+ s; A- _" r8 Y1 r7 C
if (pEnum)
$ o2 G, h: w7 ^) nhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); 6 g" ^& t; _+ [: J
else; y, c( J5 m+ [, Y/ s9 s
{
4 ^. Q2 [# O3 M//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号8 ?) S$ w3 T6 J% R
varChild.vt = VT_I4;
/ Q/ P) K; _3 m& gvarChild.lVal = index;
0 L( K, Z! O' }}
# }6 |* C# L9 f1 Q! _1 x5 g: f$ u. a
// 找到此子ID对应的 IDispatch 接口4 o% N- H. F( P/ N) V
if (varChild.vt == VT_I4)3 ?/ m1 H; |5 O) ?$ {
{( d& P2 N8 R- Y$ q+ p8 s9 B3 B- v
//通过子ID序号得到对应的 IDispatch 接口
, |, E+ g# m. RpDisp = NULL;
1 s6 @" F# M% w0 Jhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);2 r5 }4 S, C' q, c+ |
}
' }7 l: Z9 A9 M, f7 |else7 w: S0 w6 U; r# N* M
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口( U$ q$ M# m$ M0 Y/ O$ J$ P" f& h
pDisp = varChild.pdispVal;
$ O3 [' M( o3 _& }8 |& V( j; }
3 K. n8 f" V. N$ O// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
9 R& X$ S7 A4 @- u- W* @if (pDisp)
# z6 Y5 ]. f' R5 E3 k/ c{
+ @) Q8 _9 |; R' G& Zhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
% V4 Y" H5 `  S: h; r/ p+ W2 D* Thr = pDisp-&gt;Release();
! d: D, Z1 ^& L- p: w}
1 M; M! Y4 g7 z( }% j. g' R9 U  P
& D) N/ s+ M; e2 ^. l// Get information about the child
; S8 {; S8 V6 W8 r( T' G- R8 q' i" xif(pCAcc)& k, g/ j5 \4 e0 @/ M
{) z0 F$ T" a' K* x0 B/ ]2 V8 A" a
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF$ V% S/ W% I& |7 I: p
VariantInit(&amp;varChild);
8 a. N( M2 O) _! |" b9 E4 L1 U6 ^9 rvarChild.vt = VT_I4;5 Y% X& r( O+ [( _4 A- A+ h
varChild.lVal = CHILDID_SELF;
" I! [7 i; m$ J9 y( _) P' E6 V/ V4 o) `2 |# ~' a5 D
*paccChild = pCAcc;
$ ?  c9 S$ s& \7 q4 Y' X& y' ?}
( A/ u# @7 A4 C; q3 H7 ?else
" W+ i  Z6 Z2 u% |* e//如果子不支持IAccessible 接口" I. u; E4 ]; ~7 D4 }
*paccChild = paccParent;5 }6 t8 Q8 e2 B( w: [
( z4 K; Q+ q$ d
//跳过了有不可访问状态的元素
( o5 h# [) S* u& E8 D/ KGetObjectState(*paccChild,
$ \, c5 Q; e% o( P( f: @               &amp;varChild, 4 w2 F2 @7 P/ A; s( a. d
               szObjState, % \4 K( k* s4 d/ t
               sizeof(szObjState));
6 k9 i3 S' b; }% Y( C. Q8 S6 G3 mif(NULL != strstr(szObjState, "unavailable"))  Q% X5 G4 Y3 z7 _
{
+ f' _$ \- @. ?8 @6 L: Y3 Fif(pCAcc)& x% A9 {. F6 ~8 p; `
pCAcc-&gt;Release();
% S7 f4 A  \* Q, `' O( I( {continue;
8 M+ {$ b% \& ?4 ~}
) j5 b" j. i  \3 `4 K' m//通过get_accName得到Name1 d8 C" {: {& m% C* m& |
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
5 T! Q" q( u+ q//通过get_accRole得到Role4 }" L: L( k5 P  G/ ]' k
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
, ]' F0 J: O3 [3 }3 E' ]/ y//通过WindowFromAccessibleObject和GetClassName得到Class( a* R: t% i% w
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
; m, ~! H4 A4 N$ o4 b//以上实现代码比较简单,大家自己看代码吧。7 _" r1 Q, F( o

( N) L7 |8 a# t! R) v//如果这些参数与输入相符或输入为NULL2 Z7 y3 S6 i1 J; m& l5 y8 I0 M
if ((!szName ||
' f: t# O& w8 Z& E5 ]6 S3 t     !strcmp(szName, szObjName)) &amp;&amp;
5 f; i6 \3 g. z6 K1 ?5 f1 }6 @     (!szRole ||
% _% G; ?& a/ }, t      !strcmp(szRole, szObjRole)) &amp;&amp;
( `1 V/ c- @' Z7 `0 d     (!szClass ||
- B6 A" U; O$ t7 S# q9 L      !strcmp(szClass, szObjClass)))% \3 j7 i  g7 F
{. J5 G3 j4 }" o% C' P: k1 f+ G, T
found = true;
( M9 X. m: m5 G$ T: O3 d+ u3 N" S*pvarChild = varChild;$ v$ E; d% H$ p% U( t+ v5 Z
break;, c4 N9 @( v) H) u+ a+ q4 @
}8 ]; }# ^9 q" t" Z: w' U0 V
if(!found &amp;&amp; pCAcc)' A1 S7 _8 v0 B  D4 _7 e4 s
{1 [3 q( q9 p$ @( X5 i1 L
// 以这次得到的子接口为父递归调用  \0 R; L! @) d. |5 T
found = FindChild(pCAcc, 9 n( @, w# ?, n, K$ W/ s( S/ R6 f
                  szName, & e$ P8 C% E# s( r8 @
                  szRole,
6 r, b1 k$ F9 r) _/ E                  szClass, 7 @% C# C# s- Z" c
                  paccChild, $ Q5 r: Z  a! N2 }. _( l
                  pvarChild);% r& u/ W: i- i! r9 k9 S" Q
if(*paccChild != pCAcc)
  m  ~0 y1 S6 }# G1 [0 V2 a! CpCAcc-&gt;Release();
2 O: i9 y0 {. v" p}' M& s* k0 l4 a) H+ _
}//End for! q8 q$ e% S# S  H6 U! v

$ L5 I. N$ r  B// Clean up3 \" E+ @. v: k( M
if(pEnum): I2 {& M5 Z- _/ _
pEnum -&gt; Release();
) j' r! `# ^" i! {2 W9 Z, L) n( C) u" s
return found;+ ~6 P( [" O$ U1 o; W
}
9 Q- K1 ~/ E9 Q! d' S. A
8 X% u0 `* ~: p% X/ c// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,6 D+ p" w' r9 @! H! w$ }
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
' k9 G6 T# G1 W( a4 q' E; x& r//将这些或数转换成相应的用逗号分割的状态字符串。1 S$ o  L2 X: v+ k1 y
UINT GetObjectState(IAccessible* pacc, # D( _1 O% P+ V% n1 o4 {! i
                    VARIANT* pvarChild,
0 F) K  X/ U2 T                    LPTSTR lpszState,
, ?' @' B' w' M0 p                    UINT cchState)
; d( {: O  }# K8 |2 m{
9 W' G, q0 o) b" z% o2 |1 K    HRESULT hr;3 i0 ]1 `, R8 m/ B8 Q$ j/ @' A
    VARIANT varRetVal;7 Z9 A2 `4 J/ x3 k7 m( j

1 \2 P- a+ O* @    *lpszState = 0;
( Y5 C+ J$ Q. _0 h) u* ]: m+ u* i6 I
# u, d3 Y6 g8 l/ O( M( d    VariantInit(&amp;varRetVal);
& d4 k$ p1 j" L7 n. o6 l
- z& X  p( {+ ?! i' ~" M    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);1 e* ?( S) h$ g4 @; L! j7 b7 ?
9 R' C% V+ D+ S$ S3 `2 c& L5 f
if (!SUCCEEDED(hr))# `9 ]6 x3 _1 \2 G6 y
        return(0);
" k' K. s  n$ r
+ f5 Z9 K7 Y  K) A3 K6 QDWORD dwStateBit;3 p* T9 S! G6 X- x' Q7 L+ c; Y
int cChars = 0;# E+ I% E2 _' o9 y+ @, l5 T4 v; \
    if (varRetVal.vt == VT_I4)5 O7 |+ D  S) R' t1 f
{- J& b; u6 |( R6 ?5 J
// 根据返回的状态值生成以逗号连接的字符串。
: k. ^. i) I3 W# g* Y& w        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; / r* [( l. ?2 z* O' a9 v/ Q
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
- U) |) }1 `6 b, K1 G: r* k% ]               dwStateBit &lt;&lt;= 1)
( q$ ]# v4 b" S9 X        {
; s/ n; \' U7 ~            if (varRetVal.lVal &amp; dwStateBit)" M$ b2 C# ^2 R+ J  G
            {& B# U. A  i+ E7 _2 s, [  [
                cChars += GetStateText(dwStateBit, . x' B6 A- M( R7 L, ?# I% O% z; m
                                       lpszState + cChars, 0 X& x, Z3 a. j; p* B7 b6 Y
                                       cchState - cChars);* V& f5 x" s$ V- ]% o9 n  S
*(lpszState + cChars++) = '','';) k6 w8 L  K6 b' y2 j6 k- a' X# ~3 R
            }
. g- U. ?/ C# K5 t  v6 ^        }3 R, D. [  g" b; ^
if(cChars &gt; 1)
, k+ A, O4 O4 ]*(lpszState + cChars - 1) = ''\0'';
/ H' P' m! o3 ?8 A+ w    }
% v( ^/ o5 I  c! ^+ @% i    else if (varRetVal.vt == VT_BSTR)
* v: z/ L$ F2 E    {' l; A0 n" v% h+ n) D  u
        WideCharToMultiByte(CP_ACP,
+ V! h9 [& N: k" V+ _& A                            0, # m" K) @9 x1 Z  g
                            varRetVal.bstrVal,
7 m- l! P& O; r, B. C7 O) r6 Z                            -1,
( r: ^, a6 a- q4 p; C                            lpszState,
/ c: t" o  v+ A/ P  G                            cchState,
/ ?; k6 X8 ^4 J% _4 r$ X. x                            NULL, : [9 U+ C1 F5 _( M
                            NULL);
- @9 c+ ]9 p: g: u. P  V* a* H    }
1 }& m: P1 |* y0 X' o7 w0 {
' a) X6 \5 p' _  j/ R8 o4 r% z( Z. c2 m    VariantClear(&amp;varRetVal);
8 D. ]/ G: ?* r: ]; Q0 k1 S6 K- ~3 y' d( `
    return(lstrlen(lpszState));
+ x* J1 T7 u3 s# B}</FONT></TD></TR></TABLE></P>
: m3 O: ^) w3 n# V7 e- W4 [. U<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
/ C9 O  b0 Q! g; b  w' j% {7 x5 g; Z1 A* S/ i2 ]3 N/ F
四、在 IAccessible 接口上执行动作# c9 O* K! P- R: [9 ^
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
3 [$ F- R( O9 F    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
# T4 y7 o7 d. w% e    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>; R' [2 C# }. y* S
<>
# `$ U& s3 @. J( \. o6 W9 z<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
, K2 d4 @9 Q' R! e8 w, t3 w7 w1 t( {  F0 c. g8 o$ z: l
<TR>' K; q" z5 T1 S7 F( z2 l
<TD><FONT color=#00309c>//在文本输入框输入"regedit"* g6 q9 @" ?% {1 D& l7 M" P
if(1 == FindChild (paccMainWindow, "打开(O):",
" N4 j, _6 I$ \1 _* T1 e8 z                   "可编辑文字",
8 _$ [7 v: ]: g% g2 o                   "Edit",
9 [* }7 q& f1 b1 l( P9 }4 H) [                   &amp;paccControl,
- L+ q/ F, b- i# M" Q                   &amp;varControl))
: ^4 S* G$ y0 \: P- U{
$ j: k. H8 q0 y8 A) i//在这里修改文本编辑框的值" t: p5 a' p$ F4 e8 ?6 q5 ?( a# d
hr = paccControl-&gt;put_accValue(varControl,
4 S1 o  C6 Q7 G* w$ N- M  l' D                                  CComBSTR("regedit"));
' ?6 F5 b" Z2 \) c7 k( dpaccControl-&gt;Release();
6 p1 E0 _3 O9 ]5 c( mVariantClear(&amp;varControl);
, E( \& j6 H  ?7 m}+ q  A/ Q7 c3 p% E  y
5 c- w) L7 N8 Y& V6 B: e
// 找到确定按钮,并执行默认动作。# x' P1 F& |& P6 b: F- L0 E+ d
if(1 == FindChild (paccMainWindow, 3 o3 q4 H6 T" n4 _' Y7 U! q4 c
                   "确定",
0 r) T  {( v; n1 z& h                   "按下按钮", ; s& L2 ^( Z4 C1 e
                   "Button",
7 V4 W- {" @" }" p" R( K                   &amp;paccControl,
3 e4 S. S2 f4 @! \( j+ `                   &amp;varControl))
( d% A" Q$ ~4 D$ Q2 c) M+ o8 U{
/ X! l+ q) D1 i* }& C: r1 U//这里执行按钮的默认动作,即"按下这个按钮"
' W  z- u; \" Ihr = paccControl-&gt;accDoDefaultAction(varControl);
, M& A' R9 C2 `( dpaccControl-&gt;Release();+ q/ l0 l9 b* \! M/ D# H
VariantClear(&amp;varControl);
) ^5 w- m( X5 E2 D: S}</FONT></TD></TR></TABLE></P>
* P9 Y* q1 u5 P! p<>现在,你会发现已经成功启动了注册表编辑器!!4 X. S3 G, k' H5 L' n
</P>6 d" l9 n" D4 u- d( C
<>五、模拟键盘和鼠标输入" j( }3 u3 p- R' j
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
7 {/ c( F+ e! |- q+ I! ~" `  T    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
/ r1 J; {/ g4 O/ ~8 J    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
. X' X, Q6 g0 O& [3 v, D; A下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>4 m% e; R$ ]% S. h
<>$ p( H8 Q" \1 E) P+ ^- S
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
0 ?% S( y+ b; Q' V. A7 p
# t7 G) Q+ l, [: O- F- S- b<TR>
8 R6 }+ J6 K* R+ m<TD><FONT color=#00309c>INPUT input[4];
+ [# V: G: ?+ A- ?  Hmemset(input, 0, sizeof(input));
! r( P& n, l" s$ ~3 y  W# }% _: V" e" i9 G6 Y
//设置模拟键盘输入9 ~  K( c) [9 P
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
" T$ ?  Y7 a  B( Winput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
6 ]& P8 e+ q" }- x, F7 einput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
8 H- S  D: K# T/ q1 b0 Q) j
% g& g: W7 f! b* A1 v// 释放按键,这非常重要
. ^3 E( R+ q* T0 l$ i* I8 Uinput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;5 B* ^9 q0 B3 _& c7 {& o/ i
) }0 {' q0 {  A/ E2 w1 d
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>5 j$ K0 A9 q. c/ D2 M
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
6 f' U  T* y4 @# }- C- N</P>
5 y1 R" K' Q7 c<>六、监视WinEvents
+ u& k# A" D3 F8 P; H% E    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
! s4 L" J" o3 B9 h8 k    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
6 c- {  D$ W9 E3 C    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>. k% m: _! x: B/ v, t2 W. z
<>7 B+ T% z, s* ]8 Z" `1 g
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
5 z: _) Q# E4 v( A: L+ `: |" Q4 n
5 `! Z. ^+ K" j5 w1 U<TR>
1 g9 P/ M' |, p<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
# I; a+ `$ E+ ^# o{0 d: v* l' j2 h7 ]/ J8 ?
hEventHook = SetWinEventHook(
' c2 p# F* k+ C3 qEVENT_MIN, // eventMin ID
# k1 @7 Y- ?! O/ G" I$ EEVENT_MAX, // eventMax ID
; ^2 X3 r( W3 aNULL, // always NULL for outprocess hook
" d& u8 q, X3 ]WinCreateNotifyProc, // call back function
5 g; m: O: \; {% d  R0, // idProcess
! i" l; b1 T' F6 j, d9 y( }* \0, // idThread # t2 [  y' c; n
         // always the same for outproc hook
5 \0 b$ ?7 K! x& X( j5 Q, q- M( oWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
: w3 e. ^8 O, b# u( r}  </FONT></TD></TR></TABLE></P>* M+ r5 Q6 l" a2 r" S- c

& F4 @  S( L# h# J, P! x<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。: u& U2 |3 ~0 h8 D
下面是回调函数:</P>
( p- q0 u* R9 i2 D( r0 y2 u<>
: V3 g, ]* A  {5 l' ]: Z<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# P, K" B% E! P" e9 ~: M# Q4 N" n; d  o2 F* @
<TR>6 e1 ?. r9 g. n1 B" l; I3 X
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
% x+ D$ g* r7 W, THWINEVENTHOOK  hEvent,
. ]/ }3 J7 R# ^) bDWORD   event,
7 I9 {" ~( ?8 E- a2 B/ ?% GHWND    hwndMsg,
0 C7 r3 l+ s, v% m" u" Q  rLONG    idObject,9 p8 Y$ C% ~4 t. s: P& z( @
LONG    idChild,' m# C3 S! o. b2 ~
DWORD   idThread,
8 Q8 I0 W* F2 f8 X. T* E( DDWORD   dwmsEventTime
5 g% W7 Q% @4 ~5 [)
+ w! S! S2 J( m, z5 B{* ?& A" r& L. s' S/ A. `

; b, @+ M, r/ d4 D* ]) b" _0 A1 uif( event != EVENT_OBJECT_CREATE)0 L4 U6 `' j. g; B" e$ P
return;2 L( Y: V4 U: C, F  \) z% [3 Y
# @  {+ }& \9 w1 {
char bufferName[256];
  e, F7 H' q% J) C7 e) b& k/ R. G* J" @IAccessible *pacc=NULL;
( M, L& Z) N( g6 R) F7 _8 {VARIANT varChild;0 p6 q' g, G' @: O) n
    VariantInit(&amp;varChild);2 |& U1 i( B/ b* e
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对* v+ P* @; J; K5 m; W+ Y
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, 0 u7 n4 [0 p5 K# e; a: s% l5 f) t) ?
                                      idObject, ( l, m7 e- n0 a, ~- u! K4 d
                                      idChild,
# R8 p% v) q+ J, f+ ?* Z2 X5 T                                      &amp;pacc,
" M7 p5 V2 I* E                                      &amp;varChild);- C3 P7 b( ?/ Z

& D) h( _  o. O  r0 Wif(!SUCCEEDED(hr))! C# m( e/ z6 L, A$ h4 F
{& w9 v% w4 q4 p+ R& O2 I7 p& A
VariantClear(&amp;varChild);1 ^. _0 i2 n9 J9 a- k' Y2 w
return;' }! {. [# a( A; K" m. K
}
; f( L, ~( c  c: z# Z" b/ h8 _" V9 d//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
0 w' _* z7 d$ _8 _9 ~0 I! v, uGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
; t, c2 m, G& y( }8 E8 r$ Eif(strstr(bufferName, szMainTitle))
+ H3 w0 G" X$ [1 q* t2 R1 U! ?PostThreadMessage(GetCurrentThreadId(), ; T8 k# M4 q* a
                  WM_TARGET_WINDOW_FOUND, , i% T" b/ a) w  s: L$ _) r
                  0, 9 j+ P3 [, ^# X: j* v! j
                  0);
2 m$ ~: }4 ^  G: Z- ~9 E1 q
, C; ?( j) Y( m, xreturn;8 C$ H; r+ o5 l: c8 G% u5 H
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
" A0 P% O" R  c# [2 |
2 D5 D% _* B$ R) s- K+ R, k附录:; h4 ]. x8 s4 \+ h+ g) m" M" P( i! F: }
, }" p" |: M% w0 v" i1 h9 C2 `
关于IAccessible 接口/子ID对:
- q9 `( s6 ~) f' T7 d% G( B    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。) {# p, r. r5 l+ N
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
+ Q( ?6 p3 \2 ]* p, j" b    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。8 u& l* d: I8 y1 z6 K6 S1 S' T& v% N
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。- w( |2 M1 w0 O8 C
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!0 M2 F) f) a! L. O2 F5 ]$ }: q' `+ l% Y

( }3 O+ B# ?6 Q. q. O4 R+ T注:1 ^( n* T6 s/ }3 G# b) S2 F
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:). y- W6 o( A1 U/ L% A+ V
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。7 k1 J  G  E1 x

9 P( |) ?" n# U: d1 Y2 h* U参考资料:
; K8 L# f% y; K- |1 ~  T( |<UL>1 D, t9 Z# g( p8 x( |. }- M* A
<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
: K, @  C. t0 |2 R<LI>2、 MSDN中的相关章节。 </LI></UL></TD></TR></TABLE>
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
xShandow        

43

主题

1

听众

385

积分

升级  28.33%

该用户从未签到

国际赛参赛者

新人进步奖

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-10-8 08:11 , Processed in 0.440907 second(s), 58 queries .

回顶部