QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5665|回复: 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 A* @2 Z, k1 l. L) \/ `4 \
6 Z5 u6 N* r( g: K$ p: H
<TR>
( ?4 e: n2 y& E( N<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
+ l( P  F. D! M  c) W' ?原 作 者:崔传凯- y, J2 P9 w2 ]! N& ~' L( B/ B: v
原 出 处:vckbase: x  ~/ H1 S+ P4 e8 j1 K/ I' m# Q4 s
发 布 者:loose_went
: u$ o. F) y9 T5 g* h) O发布类型:转载
9 B! ^9 p' G6 ]6 u7 j发布日期:2004-11-12
. S8 |1 B/ s  {/ S+ {! z今日浏览:66 K" i; x0 ^* v8 t1 q
总 浏 览:234% W0 d( q+ h7 w, n2 ~6 a8 ]7 ~
</TD>- O& w- o7 L8 X& ~/ a4 S& M
<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>
, I$ B4 u7 h+ f! T! G1 L<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>( N5 O. t# _7 G7 K8 M, M! U. F

/ s% ~2 u$ T# E/ |  L<TR>0 E" W/ B/ p; t. ~. g6 E3 \% ?. |
<TD vAlign=top align=left bgColor=#ffffff>$ [! ?) D" M, P+ z) L% a8 P

/ ~6 P( o) o' r<>
9 W6 s: {- n% h$ [# L0 U<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>: J0 m7 g* W: B# g3 R  t

* Q8 l6 s9 B  J- q; q4 D0 A5 Y6 U/ i2 X5 }: ^<TR>) d' d( w* Q( @: m
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the 2 t9 L. [7 L6 H/ d
way accessibility aids work with applications running on Microsoft Windows?. It
- V: F/ R$ t4 U& [0 P7 uprovides dynamic-link libraries that are incorporated into the operating system
, `% T& \) k7 y6 b- fas well as a COM interface and application programming elements that provide
  _3 Z' Y- q+ h" nreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
) K* X  B* {' e& B6 a<>一、基础; J2 w- X! {. h; o: o% V* e/ x
    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
2 p6 b9 D  h% O2 L4 M    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。" \* J1 ]0 D& w% H0 L4 K% ?; X( T
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
6 M' X" \1 g1 I7 Y<>二、Active Accessibility 原理
  ^  ]- [) _; ?    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。% C0 W+ ~" K1 c
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。  O' i: A: b4 ?8 e) `
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。. @& `+ Z. q- a* _( w0 E
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。9 B: j# f, z* z
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>
9 b  s2 R: X8 I( y& `4 T4 d1 Y" {<>三、如何得到 IAccessible 接口指针
/ s2 O# F5 S+ ^) u( w' s    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
2 J* B% h5 \  z' q' y    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。. U2 F0 u7 B' ^/ @% X' O. G+ a/ i0 o" {
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。; T1 P8 Y7 w% A3 t
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。4 n* B( P: O: Q" i. E
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。2 f* _) T' Q& W) Y' }7 c
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。% @, w1 v2 M- c; H
下面结合代码介绍一下它的用法。& z6 S/ p& B7 I
我们来得到下面运行窗口的 IAccessible 接口指针。
% Z+ b( y5 e9 I8 c( S( t' y
, {$ A& S7 z; B' h/ v8 O- Y<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
# K; d  T8 U2 F7 a<>图一</P>' P- K/ h* q8 F+ T
<>% S" M$ u* n5 `$ z/ l' b2 V
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>1 F  [8 b9 V9 w' a) t

' h# C$ F6 v& q$ V4 |6 g) f' p<TR>
/ D3 z, u) M/ I/ ?- Y  Q<TD><FONT color=#00309c>HWND hWndMainWindow;
! a" c# e; c1 w6 y  K$ A3 z6 OIAccessible *paccMainWindow = NULL;
) K) {( e6 ~1 X8 r. C/ R7 @! cHRESULT hr;" m4 w* U( B4 A  e  x  E; _* `* A
//得到标题为"运行"的窗口的句柄
( A# m! G, ~# z0 _) ?if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
8 m, ?% `; ^- w( f  ]{& D* Y% i+ I/ X( U7 d
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);# g6 X; C; e2 t1 `: g5 l
}
9 c! @! U+ I6 a' v8 ?7 [0 c9 Ielse
9 m0 s/ V- k! q7 ?- X( V{6 P/ [& \3 b# S' L  ]  }/ t0 T
//通过窗口句柄得到窗口的 IAccessible 接口指针。
* t! v% H) F8 ?: S8 o( N) nif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, . ]3 x# D: H- L* E% |
                                            OBJID_WINDOW,
+ D; d- V& F# F8 S8 `                                            IID_IAccessible,% E3 ]4 P. q& ], |( g0 N. f
                                            (void**)&amp;paccMainWindow)))
; m9 b9 P, K& T1 |" s* T{! X5 @1 J# B$ \
//……我们可以通过这个指针paccMainWindow进行操作。
3 E5 O1 O% u& G& i6 S& D6 |paccMainWindow-&gt;Release();  s6 T& t- E4 ^+ n+ ~
        }6 m, @1 O5 S. c0 i, U( Z, d
}    </FONT></TD></TR></TABLE></P>) c& F, C+ N* ^9 b0 h$ j2 F/ a
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!: f& C7 s; P0 ?8 }& n# G
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……# t6 y& n2 S$ Y; U. j& F4 M
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>1 T1 @: `3 S+ C; ~% E  ]
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
8 ~9 C" e- ?7 o' d7 f<>图二</P>
3 w: q. L/ f, r" y* M8 z: Y0 e<>我们现在主要关注的信息是:Name、Role、Window className。</P>9 c3 q+ }8 K% z3 _& `! m
<>
, o" q1 C# o/ ]1 }! o; c<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>  W! D* V& m7 z8 L/ C) w0 C

9 z7 J; V1 F  p1 `. z) i<TR>3 Y8 g% l/ `2 W) [& y% ~
<TD><FONT color=#00309c>Name = "打开(O):"' L" t3 x, |: o3 D  ^
Role = "可编辑文字"# R8 Q6 m0 K7 C8 ]
Window className = "Edit"  </FONT></TD></TR></TABLE></P>4 Z* C8 x1 s, [* I) ^, [
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。9 D+ g& l# L+ g  e
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
0 E- a4 W1 b/ B! g( c下面我们开始取文本输入框的 IAccessible 接口指针。 </P>' v1 v, M( ^2 y: b
<>* ]1 K5 \' n3 Q+ C9 V1 n/ i( M
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>3 \% c# m" n# ^3 e2 b/ f
/ U. u  Q2 Q/ F1 K0 f. j/ w! Q3 o: L) R5 f+ r
<TR>
6 J0 m/ l  v- e( Q. M8 J<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
1 H0 {# q5 G6 N7 k" ?VARIANT varControl;    //子ID。
# s% y1 Y5 }; ?. x  B: `; j
, g9 W$ O0 X; h# G# M  [FindChild( paccMainWindow, ) n$ _  t* J" K& O; F
           "打开(O):", * \" d' c2 D. q8 W7 E
           "可编辑文字",
8 h' Q; S+ e+ L  W           "Edit", # e4 m# S+ e% G+ }
           &amp;paccControl, ' I" y# T1 B& f4 I' t
           &amp;varControl ) </FONT></TD></TR></TABLE></P>0 \  u5 E# E; j* b% }( r! T
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
1 H& \) H4 o( a3 z$ e, u( z7 k# z: q第二、三、四个参数分别是名字、角色、类。
. `2 y; ~0 z$ h后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>0 i# i4 j4 `3 i3 L) g
<>
- X4 W' V# [* W9 s$ f" A<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>6 b3 F' g* q; i/ r" |) u- f

. \: n+ [( e- }* }# t5 C<TR>. W/ |4 L5 ~; O; \3 I! J6 N3 g
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
' o4 {4 V" p2 O9 |: T8 F                         LPSTR szName, LPSTR szRole, 9 F7 u% c" z6 S: [) o
                         LPSTR szClass,
$ ]6 ?! e1 u( f6 m2 j. J                         IAccessible** paccChild,
4 j" s( @% w& Q& d( G& M& D9 n( j                         VARIANT* pvarChild)$ M/ @! d- h% L
{
* b# P3 o7 ?' V9 t; p: Y- L& XHRESULT hr;# u( t- p  b( F0 E1 a9 `$ K0 E
long numChildren;+ O# U5 c; o0 e2 `
unsigned long numFetched;
+ Y  B* c" s1 {7 cVARIANT varChild;# k2 M: S+ E& {! h( N3 t
int index;
8 f8 y& o/ U0 SIAccessible* pCAcc = NULL;' P' u% U' L$ k* [
IEnumVARIANT* pEnum = NULL;3 @& {0 q" B5 W: m% S$ P, h7 [
IDispatch* pDisp = NULL;
  ^8 r( a9 d- gBOOL found = false;: B7 Y$ Q# Q6 e8 @- c
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
5 p; `) r5 i4 l+ t' x
) e( n, w. Q* U//得到父亲支持的IEnumVARIANT接口2 N3 K: o- n# U4 @5 y& `2 |
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
3 Q0 L' z! }1 g8 Q* \: p- M. E! C$ _0 C8 J3 o$ q" R/ V
if(pEnum)
# R& u: b$ E8 u$ X2 E7 ^+ XpEnum -&gt; Reset();
& Y0 P( d/ j  B/ A
' L) t7 f, s  [( c, w5 m7 W//取得父亲拥有的可访问的子的数目
" C7 l' N$ u$ r$ M, z9 \8 LpaccParent -&gt; get_accChildCount(&amp;numChildren);
3 r* e" Z0 S: j1 e
+ p) Y2 O) w; s0 P! E//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。' x; B) ^! ?6 Z8 g3 l
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)( K' O6 Q$ L0 q
{
+ d+ r# @+ x. t! S7 A8 N; t7 a8 WpCAcc = NULL;
) J3 }' X' }; v$ [% {. l// 如果支持IEnumVARIANT接口,得到下一个子ID; t% y9 N* e9 u+ \8 a
//以及其对应的 IDispatch 接口9 o# [6 Z9 ^" m/ o
if (pEnum)
& K/ P8 N/ `2 U9 B5 @; xhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); 8 r( M, N) l6 ?% K# n' h5 H$ u' |
else
5 y, \) w! W5 m* S' p7 I: ]9 z{
6 J# N2 J* Z% l& m) V9 l//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号' M* M3 K  M+ d6 m: U: r6 z
varChild.vt = VT_I4;
; s& f5 A, M- qvarChild.lVal = index;+ |3 v/ y( e5 B
}" k# w. h  I2 J- G0 V: g: y
' e! h* u# F- ^6 n
// 找到此子ID对应的 IDispatch 接口+ h, t( u# Z3 ^" }
if (varChild.vt == VT_I4)
& a5 j2 M  K) H+ Q{
: e: Y& t: x2 m5 e. v8 U% j3 d//通过子ID序号得到对应的 IDispatch 接口+ J. I* Y* X# M" `' K/ Z( `
pDisp = NULL;
6 j$ G: @# [( dhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);2 f9 P7 g8 t2 z
}  e# p6 D4 ]6 [/ h/ x* C) y
else: s/ q) r8 L8 O* b7 ~  e7 A+ p, e
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
" ]6 K0 e) a# k. k+ ^pDisp = varChild.pdispVal;
/ V% `0 ?. @! U+ o) [, c5 t6 a" }5 q0 {! u& b% i$ n
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
$ {" o; X: ]4 [if (pDisp)
  g% U: {. T! v0 W{
5 P( R. a! a/ @! M# Y" qhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
: r2 u% V5 F4 T0 O4 N* hhr = pDisp-&gt;Release();
. x1 T, X0 }% |1 l}
9 T8 a' C: G& v" [1 S+ D- N$ ?" w: j& b
// Get information about the child8 T, h: c& t7 K5 v
if(pCAcc)% E* e& V1 t% l5 C  `& i9 C  B
{3 k( j0 V* q. p0 }. O2 D" |
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF! q4 T9 Y/ e( }  [
VariantInit(&amp;varChild);
" Y4 P% p8 ]5 E! Q  N8 p' T  V" dvarChild.vt = VT_I4;
( p0 |+ n7 C# k) Z6 zvarChild.lVal = CHILDID_SELF;* f! m' {9 x& L, Y" _6 Y, y7 U

% E, l$ }7 M- h( X, z& q*paccChild = pCAcc;; n5 R/ V5 W% F- I
}( H, D+ _/ |; B# Q
else$ X7 r  T1 Y8 }8 K! c
//如果子不支持IAccessible 接口
. q' ?: u3 s, R1 r, W& A*paccChild = paccParent;3 l. I2 X: a) z3 t# w2 B* M, S

1 f9 [; q4 c0 u: h( A1 \# n% i//跳过了有不可访问状态的元素
1 \5 z: j& f& \1 JGetObjectState(*paccChild,   @& ~2 ]  L7 n
               &amp;varChild, , @; H% }) F5 f( g9 V6 ?) w2 [
               szObjState, ' G  k7 z7 x2 a( Q6 u, T
               sizeof(szObjState));$ f- c3 X' ^5 Q4 h
if(NULL != strstr(szObjState, "unavailable"))
; ]; d& i$ {1 B# D8 F3 l) L+ v& s{2 P4 v8 Z- s! g, z
if(pCAcc)$ R) H7 \7 s# @8 a; r# H; f
pCAcc-&gt;Release();
1 Q2 W: W* W0 l; B. xcontinue;
3 i/ h: {4 q+ R# x$ j}' X6 f- Z& D- g, d
//通过get_accName得到Name3 q+ P7 y/ W2 L. N, L; F7 v
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
( F+ X# S! n+ m. C//通过get_accRole得到Role$ {) X: v$ U, H7 ^! K
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
+ K1 v; A8 r; M! M' T2 O) F//通过WindowFromAccessibleObject和GetClassName得到Class% F, Y2 ?! p" n; m) H
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));; `1 @5 c  M+ S- |! _5 c
//以上实现代码比较简单,大家自己看代码吧。+ ]3 s/ o; W0 V! x2 ?7 d8 `& B9 I

* q  L! Y% F6 P9 h8 v//如果这些参数与输入相符或输入为NULL
4 E& [; ?& l8 e; dif ((!szName ||
' t8 w5 U( b$ F4 R. w) O     !strcmp(szName, szObjName)) &amp;&amp;
  I4 {0 |2 X* Z$ X* W# E0 }     (!szRole ||
0 k: o: V2 g- E3 C/ w( W1 e      !strcmp(szRole, szObjRole)) &amp;&amp; : A  R1 M- a2 N: w8 H7 y
     (!szClass ||
% [! T6 i( W$ \8 F  Y8 i      !strcmp(szClass, szObjClass)))
7 D. G- G9 L7 Z{- G9 R' g3 [. {* h* v
found = true;6 s6 ]0 z& f, p& b. X7 @: k- x
*pvarChild = varChild;
& x  M- s& @# z7 P+ z7 Y9 n5 T8 Gbreak;
( J" a; R* Q4 y+ G1 n; L: I2 j}4 {7 N2 L6 ^  D' T8 |
if(!found &amp;&amp; pCAcc)# F4 Q3 x; s3 Y7 K! R
{
  Y* X% o1 ~. B) G' [// 以这次得到的子接口为父递归调用
8 j1 t8 ?! N1 Wfound = FindChild(pCAcc, 8 @& V5 Q# q5 ?) C! Z
                  szName, - W( l6 _' ]9 D1 Z
                  szRole,
$ ^3 J5 B* R% V( f" e) J  g                  szClass, - d+ ?" }* z$ q8 Y
                  paccChild,
- `  C0 m# H4 p8 A                  pvarChild);
& Z* u, r' K! q3 Cif(*paccChild != pCAcc)
/ o4 u- w9 y3 c) p  ?pCAcc-&gt;Release();" N% D5 h* x$ {5 g2 }% ?
}* {' a1 W2 C% s" @+ z
}//End for  c7 C* T0 C# x' R9 }3 q+ D3 `

, W$ V0 S3 p* P- I# M0 M// Clean up, V2 `" k5 R: J. ^2 f, @' Y% I
if(pEnum); ?* F% g* N  ?! h- r
pEnum -&gt; Release();
, e- w* T  u2 d- q8 Q1 l& ^
! f4 ]" a: {3 H" V4 k; mreturn found;
  p: j8 s8 m7 ~}: |  J$ s/ Y, f+ j

# F# m* j( L6 U! ~0 o6 |// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,3 [, _5 o4 i" r# w
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
8 o0 S# Y; v& [$ a( ~& q//将这些或数转换成相应的用逗号分割的状态字符串。8 u( f9 }2 i+ O5 m
UINT GetObjectState(IAccessible* pacc,
2 A5 f) o) X: {- ?                    VARIANT* pvarChild, ) D: P) r: g7 W; v( ^- }
                    LPTSTR lpszState,
; x& G# w7 X  i                    UINT cchState)
1 U$ h. m9 ]# \. r. B5 ~9 `{0 W- Q1 i. @/ v2 u4 J4 a. h
    HRESULT hr;
. l3 ^- F; L9 T; `$ l5 k  s    VARIANT varRetVal;
; j8 F/ B# i- B: J" u5 ^. l6 |. R6 P: X8 I7 M
    *lpszState = 0;) }' x- z& \: c, }
1 N7 S) l* a3 _0 N+ N6 W
    VariantInit(&amp;varRetVal);9 B# g$ h5 o; t" c

; L! B' y4 R3 X, I; L8 C' n7 q    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
( Y5 a+ A9 O) T% c9 M; |2 `4 G) j- [
if (!SUCCEEDED(hr))  _  L9 |- e3 y- R8 f% Y' r. n/ Y
        return(0);
& I. a8 V2 s( J+ q, a) ?5 I
3 T" E+ y  _+ h7 A, s6 N4 ADWORD dwStateBit;0 `) v8 y9 S9 d$ q
int cChars = 0;! H' r- D8 H6 Z- {% [( O
    if (varRetVal.vt == VT_I4)# U: _7 C* I1 B6 n+ N: h
{; }1 C; N. K+ k7 V
// 根据返回的状态值生成以逗号连接的字符串。7 s$ P, b) i* b( Q# F  c- G' V
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
! Z0 I& P# |% ~" J8 A$ N               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
, i, @2 ~5 Z1 N  _0 W& ?3 Y, l2 a               dwStateBit &lt;&lt;= 1)6 z" E6 b9 k/ m7 `2 O& g/ ~' C0 f
        {
. i3 j1 M) N" L  a1 p            if (varRetVal.lVal &amp; dwStateBit)  K2 p" j# Q% i6 Z6 m1 p
            {
: k) H2 J, R4 e; W! X. f: _                cChars += GetStateText(dwStateBit,
7 I! N" @" h9 v  b, N                                       lpszState + cChars, * w: P6 ]9 f' A3 o: i
                                       cchState - cChars);5 p- Y4 O  g/ a! A' u/ D
*(lpszState + cChars++) = '','';
4 S- r( [  R3 K- b2 P$ m            }2 v. L- I, W4 u* G3 W+ l
        }
# v6 E' z0 o* B" l- T- ^+ A' gif(cChars &gt; 1)
0 G6 C7 u1 ~8 h0 ^*(lpszState + cChars - 1) = ''\0'';" x- v0 W/ ^7 Y
    }; _0 \! W3 {) v- A$ Z6 _4 M1 j1 k
    else if (varRetVal.vt == VT_BSTR)
2 }" J4 A2 W  W5 l6 m- D    {% h* e  Z2 e4 Q- X- Q# |% C
        WideCharToMultiByte(CP_ACP, + x: j. i! o& j/ B* Y7 V
                            0,   S, e. d: E' Y' W
                            varRetVal.bstrVal, 5 j6 p) {. b8 g, n
                            -1, 9 j8 o, l: _  X9 l! q
                            lpszState,+ h6 {3 z/ V5 S2 B
                            cchState,
9 j7 t1 C' }) k# Q: k0 s. b0 h: v                            NULL,
5 x7 e6 H& E# x8 {1 }                            NULL);1 [( R$ p3 X9 H4 S9 \
    }. q  \0 s  o6 c% t+ W# d9 v5 H9 @
8 U# Y8 s7 I' p/ t+ J
    VariantClear(&amp;varRetVal);) b/ E8 S& `% W$ M& v; I) l4 D( J( n

7 G) [8 c3 B5 j1 j: r    return(lstrlen(lpszState));
8 d' X' \+ l: Y" y" L0 ]}</FONT></TD></TR></TABLE></P>
. _: `# X) t+ ]. Y1 t/ B<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)+ e5 d3 X. d5 Y: j+ h( z
, H5 _( ]! C4 P0 ?) k
四、在 IAccessible 接口上执行动作
! m! K: U4 R; f9 T% U1 |    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。4 l0 g+ a* C5 D! M' T
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。1 X; g# |' M) {- X
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>1 o& J( ?# w2 _2 s' u: l7 r
<>: n* g9 o" o6 W( ~) @
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" r! M4 b+ r4 O1 t' h1 ~8 z( b/ b' P" ^0 r: Q/ v  @
<TR>
4 Z! N/ k  F7 b1 z1 k8 R1 W<TD><FONT color=#00309c>//在文本输入框输入"regedit"
1 `! W- B( M: N' Uif(1 == FindChild (paccMainWindow, "打开(O):", , T1 A# @: ?8 s' d, g; J+ y: O# @. Z) J
                   "可编辑文字", ( Y2 |6 o5 t6 Z/ U, i- s
                   "Edit",
! j4 f0 k1 G  y9 |) y% w                   &amp;paccControl, 8 @) @, F  W( ?, Q8 _
                   &amp;varControl))
6 x6 }5 P4 Y/ I{3 i$ ~" j' Y% T
//在这里修改文本编辑框的值
4 }  r0 f3 O8 @5 Z8 F' q0 J  Rhr = paccControl-&gt;put_accValue(varControl,
2 S# V2 t  z- r4 {                                  CComBSTR("regedit"));
" n. ?) |- a- b' A# IpaccControl-&gt;Release();3 o7 L, U% X8 w3 U! ^1 \
VariantClear(&amp;varControl);% `, F) K6 G' q
}9 V- `) @* ?) |8 ?- c! B

# N2 J! F# Q+ t" `7 T0 R- s// 找到确定按钮,并执行默认动作。
7 j8 d( S, a- ~: h# r9 Y! z) Bif(1 == FindChild (paccMainWindow,
$ C1 j& T; E, h4 f* o$ j$ l                   "确定", ( ~4 w  d7 z6 U) M4 \
                   "按下按钮",
% T  C" `+ ]: N  _; @                   "Button", # W# W/ ^) w" _" X4 E$ ^
                   &amp;paccControl, & Y* W5 L+ u8 e: v2 g( ?, b/ I
                   &amp;varControl))
) O6 v" e; j$ ?3 A: V, L{
8 c4 W. L9 F, B% S6 L& o//这里执行按钮的默认动作,即"按下这个按钮"
6 K2 C1 K) S8 f. N5 Dhr = paccControl-&gt;accDoDefaultAction(varControl);& ^8 p" ^2 y4 U- Z
paccControl-&gt;Release();
2 b- z0 T7 U) T% c: x% V. E! D. NVariantClear(&amp;varControl);
& B( x3 y1 o, N7 q. k" b}</FONT></TD></TR></TABLE></P>
$ t: g$ N' d4 e  z4 T<>现在,你会发现已经成功启动了注册表编辑器!!; b6 y8 E( q2 e) o0 ]8 \' V3 h. G2 `
</P>. g# E# c& C9 q& ~! A
<>五、模拟键盘和鼠标输入& t* T. ~2 K" V+ H
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
* s4 U! y9 B% y9 O' J) C" v& p    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。6 _; c. o: i- A: u  W
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
( O; ]0 G, u' f6 f下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
7 ^8 D5 G3 r. C4 g<>
; L9 S' g8 m; ]7 k<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>9 D! t! [' g) V2 x

5 Q- X# e7 d- w, r6 b/ z1 b- B3 n<TR>0 y" \3 }8 _) A& i  J7 w" {+ o
<TD><FONT color=#00309c>INPUT input[4]; 4 S0 z- Z' ~5 c' Z& H, w
memset(input, 0, sizeof(input));6 U! n* ^$ I' r; `5 p9 P
. Y* k) r3 v/ J
//设置模拟键盘输入3 ]. C) b3 [" s+ g8 j
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
9 A9 V3 f8 M9 T* L- ^input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;& G- t: ~( K: A, F7 Q% V! j
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;, V" R& C, W; J* D6 L5 X

5 i& f. w/ ~8 [# C" q# o// 释放按键,这非常重要4 |3 L5 J  I5 t
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
. t5 [2 Z. }# C3 |( G8 [; ~9 K7 k& x; u5 G) u6 M" |3 C
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
6 y' w* C: P1 q7 Y, k- Q+ Y$ W<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)& o$ z5 a" M6 f
</P>" h5 B6 a; V4 k5 A7 v  Y+ C5 v
<>六、监视WinEvents
6 s  i8 E/ d/ B    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。' d# D% s4 T. [. H( O: q
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。9 G# d" D3 {1 H7 S% U
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P># d& Y: d, g/ l( q8 A" `8 o* o; d) e
<>
' g$ G( p; R& {% O0 C4 \<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" ~( z" u' t' T& w5 Z
: K* X* I8 B. H8 r; f) g  c  G<TR>& g; P, P* L  z. D5 r& A- K
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))# R5 x0 N4 c* a, j
{- H' S% d6 B. M9 Z; t
hEventHook = SetWinEventHook(
8 W5 s; i- t/ r: G2 mEVENT_MIN, // eventMin ID9 T& M+ M# `9 i+ g9 H" d. R
EVENT_MAX, // eventMax ID% l4 N9 e' R) T% h; F! s8 d$ v
NULL, // always NULL for outprocess hook
5 K! M$ d4 o" `$ XWinCreateNotifyProc, // call back function8 e- x, B8 g0 H, c7 R5 Y
0, // idProcess: R+ T3 Q- n& H* J# u. I& j
0, // idThread + T9 |0 _2 K9 _3 d
         // always the same for outproc hook" i  \1 W! B5 Z7 o3 H' r1 n# x
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);7 V' x# ~2 X0 ~& [4 k" X
}  </FONT></TD></TR></TABLE></P>6 ?  Q  T  A* _; B  F
7 ]( b9 }0 ?9 S/ k2 e# }
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。9 h; o/ W& u3 G% Z, p& h7 Y
下面是回调函数:</P>+ r3 y2 u# m+ V, V7 [; P
<>
% o# C0 Q7 D! B: x<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>/ i5 j6 T- ~; s% D0 X8 @
  h/ K' v7 o2 W$ J$ e
<TR>+ Q: ?, t: X! |6 Z. E/ d& R
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(# ]3 R% P4 B: Y, e1 E* j/ B
HWINEVENTHOOK  hEvent,
) h: `$ j1 Y: IDWORD   event,
) C! N: r  O: |( }HWND    hwndMsg,
$ P% B4 h: n2 {$ B2 k: s" k/ RLONG    idObject,
$ K  u- R  w! _: A7 Y5 l2 g4 kLONG    idChild,
$ d2 x4 x3 D7 U9 p) s- aDWORD   idThread,
" G; v2 M9 v0 ~6 HDWORD   dwmsEventTime' `% O2 }4 [1 z5 G/ r0 y  {
)
2 h( i$ v& P9 G4 s2 w) J{1 Q/ c1 I+ R8 z5 p0 y

  i/ m  H" Y. Wif( event != EVENT_OBJECT_CREATE)0 ]9 _3 ]9 Y. L3 D4 y; t
return;2 L4 a( e3 t7 v: Z- Z

. H" h* x7 {: y$ k; y6 O  f9 _char bufferName[256];6 F4 Z; l& K, o7 D( c1 c, B) b
IAccessible *pacc=NULL;& B# j9 b; C! q( Y- E
VARIANT varChild;, K" \$ y: [# i5 _$ N  ~1 ?$ t: K
    VariantInit(&amp;varChild);: i6 o5 }% }8 B! i4 O
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对! N* [& ?; A& h9 w9 `
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
  K" p! n8 K. N$ L                                      idObject, 0 z/ ]4 l% P4 F9 f6 s% h4 d" t7 }* C6 F
                                      idChild,
4 h, |# w: ~: y9 K3 V1 e' t2 W                                      &amp;pacc, 2 f8 Q6 y  r/ Q
                                      &amp;varChild);
" S8 }& Q+ P3 _* s! y# i  L' x
. c. W  p3 B. p  Zif(!SUCCEEDED(hr))* G1 o: N; E, d6 j, q
{& q1 S) F7 g6 r+ F3 N2 y6 P. ?- Z( ], |
VariantClear(&amp;varChild);
, o1 J7 v7 u% T3 preturn;
' M" w( r! s: q- W}
- H' }. N; w8 j+ F* K+ ]; j% z//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。, \' V% o' P- Q( j  Z/ i* ?9 I
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
$ A4 b* c9 t) \! g3 u& cif(strstr(bufferName, szMainTitle))
. a3 G' Y7 F$ ]/ OPostThreadMessage(GetCurrentThreadId(), " B7 n0 }+ x. H3 m+ E0 R6 z
                  WM_TARGET_WINDOW_FOUND, - D; ~, C/ j# @) L- G
                  0, 6 X0 d: V9 R- A& n
                  0);
  l$ X. h( Q  b' u$ A" {: T  N8 }4 v
  H( n+ x! }6 F$ a) lreturn;! ^$ v7 b; _! f9 j  u7 P
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。9 |: O8 p- g* k6 t$ U# D4 Y# i
' Z+ ^+ @9 x# L5 d2 j
附录:$ o  g- I8 H& ]
! x. ?/ R) H  y  |
关于IAccessible 接口/子ID对:
& m: e; `2 h  n2 a+ a! c    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。  e; T2 L: j3 t3 ^$ L
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!  J0 Z/ \/ _( w, f: V# T
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。3 @, k$ N) K% w
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。+ \+ ?; |# h7 I
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
! @/ P+ G6 W3 r& t8 S1 _2 q9 w0 s& }2 Z( }) C( ~/ [; F
注:3 w0 h; a' l. U% b0 a: u4 Z
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)# c# v3 t/ Y- @2 D
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。, B" w0 ^. M& h
' t' g3 n- N: e' g+ |3 s
参考资料:
7 a% x0 }* d( r! J- A: E- g<UL>
/ Q% R3 n  g3 k<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 % T; u3 K. B7 C; ~. k
<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-10 19:16 , Processed in 1.744015 second(s), 58 queries .

回顶部