QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5721|回复: 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>
3 m! o4 q) M( V% \4 U
& J5 {# w2 a! h4 z! [<TR>; b/ y8 g4 E- ]' f. Z8 u$ ]7 b5 x7 s
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>6 t% C; [$ `( |! ]9 ~! J$ i
原 作 者:崔传凯
9 V; y0 m6 K1 N- K. I原 出 处:vckbase
1 l% I% M8 }5 e! P) w& a0 D0 o发 布 者:loose_went. Q' j0 j$ y6 k
发布类型:转载
2 Q6 R$ g# b6 _4 i% {6 s发布日期:2004-11-12
* C0 U/ O0 v4 q% \今日浏览:6
- n* e$ t) w: ?* q1 T. ?0 F总 浏 览:234* Y4 w! B) F( S! c3 G2 w5 v
</TD>, y2 L8 T& I0 F3 Y) V. G
<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>
" F- h# `: u, k; n<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
2 u6 x+ V7 z7 s- u% f/ p
# C6 ], x/ ?. B& G5 `- W7 \0 `, j<TR>& M" F% ?# P, C  y- A- k
<TD vAlign=top align=left bgColor=#ffffff>
) d- h' J$ j) ~7 h/ ]
4 k3 |9 @9 Y. U& m0 h2 n0 L0 s1 ~8 w<>* i3 ?6 K2 D5 ~% H9 o7 b: m/ g. r0 U
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>  E2 [' ~( \2 G: k4 P9 E
7 }: {3 o2 a% h4 o; l# u. ~
<TR>7 t3 C1 C: l3 A' A* u
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the & M  z9 H; B5 |2 }  b4 J
way accessibility aids work with applications running on Microsoft Windows?. It ( J8 m. r. i$ {2 H, [0 P1 {
provides dynamic-link libraries that are incorporated into the operating system
! K8 q: x4 R9 B3 C; D& T$ ]as well as a COM interface and application programming elements that provide   R- K6 m- S) t% Q4 y' b* ]7 u
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>
$ Q5 m- I3 X$ |  |8 @/ @* h' O) L<>一、基础
9 O! h1 B# B8 u    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
! L! D1 d3 ]- P5 h0 j    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
3 l3 s; R9 m# K2 @0 M8 G& F. X    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
# o" {7 V" W& g2 |. C; J$ o# w<>二、Active Accessibility 原理( r2 t3 b3 m, c. `
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
+ d1 y. \2 ~0 O- W! W    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
5 Q6 p) p+ C2 R7 s    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
9 U+ V) n( i( K% f2 q( N2 A8 j    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
$ T1 f) M0 l4 A! k    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>+ p' a9 n# M- [% [- n5 r
<>三、如何得到 IAccessible 接口指针  M3 ~% h5 e1 P& c
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
! i$ P0 P5 G7 g) p2 X1 E, Q    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。3 w. _, a/ l( T. z7 _5 R
    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
7 @" e8 I1 O; |0 |/ h; n    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
! f4 a+ a5 P3 f9 |: @# ^    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
6 ~# n2 a$ M9 X" g: O+ u7 o    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。& j3 [( s8 i. Y+ P& K5 V* u" H
下面结合代码介绍一下它的用法。
9 B) a/ J9 a  X; c2 {/ I# H我们来得到下面运行窗口的 IAccessible 接口指针。
. T/ q* w/ o6 {9 L* ^
% Z! j3 K* P8 O0 w$ A9 Y<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>. e1 w+ [( Y/ J) U5 v& J* [
<>图一</P>$ ~! A( c: B! W; `
<>
; [2 u: |# L, @% }" m8 e4 o<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) V, m: h( b+ U0 @. N' L

( L$ W- i1 O* {9 w" _<TR>" E( {' ~8 l9 [
<TD><FONT color=#00309c>HWND hWndMainWindow;/ p/ o/ M& N/ R  n( d0 V
IAccessible *paccMainWindow = NULL;. @# T% t; i. s6 f
HRESULT hr;
9 _6 K2 x+ D1 N+ b$ O1 I. Z//得到标题为"运行"的窗口的句柄: `; q6 T) Y2 e, N: b  L# P
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
3 e; u+ h4 I0 s2 v; Z{% F8 D& v- z" t, o
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
, V. [5 c+ ~/ P}. ?0 C! I, B; h( S" f
else
. q0 S; e! @3 F{# C) Q; K. p, I) ^' o' a7 U7 _
//通过窗口句柄得到窗口的 IAccessible 接口指针。
8 I% _: T0 N+ C9 U1 Eif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
; _& {% m( ^; G8 `4 k  t                                            OBJID_WINDOW,
* z+ G1 w8 h) E) m6 ?( z0 W7 Q3 W                                            IID_IAccessible,  T, {! X( X; A( z, e9 Z! o
                                            (void**)&amp;paccMainWindow)))% I, m6 z4 {5 v3 S  r8 j6 w4 ?
{" Z7 P+ ?! O5 l
//……我们可以通过这个指针paccMainWindow进行操作。
% [% k' ~$ ?' b& apaccMainWindow-&gt;Release();
% h) E1 r; ^% u        }8 N# T5 ]$ x4 N) M
}    </FONT></TD></TR></TABLE></P>3 n% }+ B: v' `8 ?3 h1 A7 u
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!: s9 d8 Q* s' B* I: U& P$ h
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
2 ]+ s* F( Q0 e$ a- o( g! Q    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>3 W! Z* y' h) B% S7 ?8 o
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
8 \% M$ o: I! H2 f4 Z! p9 J<>图二</P>
) j: y- \$ g* V9 M) `! o& f4 E<>我们现在主要关注的信息是:Name、Role、Window className。</P>
: k1 w: b2 N( d3 s) l<>
5 s) M# @/ h9 A; [# [1 o: }<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; }/ O8 J: f. B3 f, K, M: n

# [% g" \! T3 G<TR>1 A- b) _/ l' \  X
<TD><FONT color=#00309c>Name = "打开(O):"% ]) Y9 `" F2 x7 r$ T
Role = "可编辑文字"
! M' i; Z  \0 g8 sWindow className = "Edit"  </FONT></TD></TR></TABLE></P>: ?) G6 |/ U7 s
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
4 |! x! w% f% d5 C  i; M& |    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。+ r  [6 M) n: ?4 c$ `" o5 o
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
: l; }3 S0 C: ]  A<>
/ e6 R8 z' X0 w/ d3 I<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
5 x  ^7 i! t' Q; E+ G  B) w# l: _* I0 P) p
<TR>
7 v/ [% t; x2 w<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
/ O- M5 M% B6 [2 B1 ?2 F: nVARIANT varControl;    //子ID。
3 }1 x5 r' H  r, k0 A/ P8 \
. Z/ W! T- n* O/ m$ XFindChild( paccMainWindow,
4 G6 Y8 t2 R2 {* @/ p2 b4 M           "打开(O):",
6 d) E+ I/ i3 f$ U3 d. F           "可编辑文字", & E# N& N5 }: _- `* g) R
           "Edit", 4 q- D/ n$ b. i+ {8 S# c
           &amp;paccControl, 2 z* S  g. k. g# |7 N/ S
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
. L- q$ N# b* J5 I2 |0 @5 [: R<>第一个参数是先前得到的窗口 IAccessible 接口指针。( e! N2 k/ [9 u
第二、三、四个参数分别是名字、角色、类。
0 C9 i) b) @9 Y# D4 m6 c& ^后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>9 t4 H8 `' y7 W2 @2 @* F/ `
<>+ b0 g0 j4 n% O" v) g' Z! M- B) ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
3 K% {2 k& G$ i8 Y7 z9 d; W: \4 }9 _
: B' A/ Y! G" O1 H<TR>) E# h# T) h* I2 v
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
% e7 O# O* p1 D                         LPSTR szName, LPSTR szRole, + J6 S# Y! A$ V: o
                         LPSTR szClass, . s  Q4 l" U' y6 E/ T" o
                         IAccessible** paccChild,
+ Y% M9 p; f6 m                         VARIANT* pvarChild)& m, X( _* B$ n- m6 m
{
' |# Q0 H; j. MHRESULT hr;
! o  P5 x) p, Z: C2 V( ylong numChildren;
8 J6 k# v8 d- W# uunsigned long numFetched;
' x) E/ x/ G/ I4 V8 Y# \- bVARIANT varChild;
- R4 c5 r# R. W, N0 s+ X" Oint index;
5 Y# d! ^- Q! H. f2 ^# nIAccessible* pCAcc = NULL;
5 y( O6 Z$ u! D) {0 FIEnumVARIANT* pEnum = NULL;7 I+ i/ t1 l5 P3 O" j
IDispatch* pDisp = NULL;
, y- @8 @" I" V! V6 Q) ]$ hBOOL found = false;
- e( `5 B% F7 a0 tchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];. r0 O$ M* s  U# y7 v. K
/ q& m* d+ |5 O2 U! R- R4 [: ?
//得到父亲支持的IEnumVARIANT接口
" |) a* z7 l5 A1 T, mhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
' |+ Y' W- C- x$ G3 M4 J0 C- X+ S, R5 |$ J. ~
if(pEnum)
$ f+ d3 a4 F# B% p' dpEnum -&gt; Reset();1 [" X$ y, s6 \3 \

! \2 g9 x( @% v; z' P//取得父亲拥有的可访问的子的数目( o: g$ k3 A! V1 ~
paccParent -&gt; get_accChildCount(&amp;numChildren);4 E# B3 T4 e0 X$ C% K5 X' M

- B: x" E4 y" w8 B) ~5 _5 w& O$ m//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
) @2 u$ P! I, x. a' S# Ffor(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)& l) [7 p+ @! Y% J! Z* S
{9 d' h% t: K; i
pCAcc = NULL;
3 l% k0 h" y: @: s, Z# |$ p; x// 如果支持IEnumVARIANT接口,得到下一个子ID
/ @, I8 T( p0 a1 A# {- y( h+ P, x, ^//以及其对应的 IDispatch 接口9 ]6 U& j0 n& L9 A  @
if (pEnum)
  U1 X5 X% h& t) Z; R# j) bhr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); $ |; R9 n6 H+ o$ z9 ]6 n2 w  x
else
3 a5 l: s& u+ ?: Y9 e* s1 l9 m( C. n{1 f' X- a8 p8 i9 y5 e9 i( Z' N
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号  k( I( h* ~+ K4 M
varChild.vt = VT_I4;( m- E7 T& T, n0 S0 ~* V5 K9 C
varChild.lVal = index;, }  a6 Y3 e+ r! O
}
4 D2 j; b+ ?( I$ A. c$ G! m# S+ b9 N5 y  ]- G. ~
// 找到此子ID对应的 IDispatch 接口
% v! ]3 J/ C; s) R0 t& ]if (varChild.vt == VT_I4)5 A0 i- G$ e$ g  {5 S5 B# [5 D
{
/ N' r( t4 w! V- z# B) X5 g//通过子ID序号得到对应的 IDispatch 接口+ L- b  ?& B4 E# @
pDisp = NULL;
6 W- u. o, \# @0 p: }* l" k- qhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
' ?* N) Q9 G6 P7 y6 H9 D6 f/ V}
+ }; o" N% i/ Melse# i' P. f/ f+ e. h$ P
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口5 h# v. ]6 j$ V$ |% A$ U# C! L
pDisp = varChild.pdispVal;
' W$ {/ S4 F5 V' Q
2 ^& [& d  ^' E$ v- Q( y" J3 \// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc! Q7 ^& Y( c. i# |8 n, t8 t/ z
if (pDisp)
, q  |: `4 M/ R9 a# P- t{; C/ f, _: e  V% t$ A7 e" }
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
8 s& F  P% O: a3 I0 p1 W  nhr = pDisp-&gt;Release();8 @& @  J. |) S
}
7 h  |+ o3 m' n  u( l- m. X# X
! I4 X9 C) Y1 Y  m// Get information about the child
& [; n2 m3 D% R! k$ u" [" qif(pCAcc)
6 w5 v) E/ \& d0 K{) f. Y0 c/ L* g( c# E
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF' i+ m+ {" E. ^4 B+ S) ?# w
VariantInit(&amp;varChild);
9 c0 y' ^9 t. X1 `3 U$ M- UvarChild.vt = VT_I4;
  d6 J* _2 _, q1 ^& U0 y# F5 ZvarChild.lVal = CHILDID_SELF;
% t4 J4 A# f9 \  V9 a% I- O, Y" z' s' y/ T8 v! W3 m# z' }
*paccChild = pCAcc;; `1 k& n, l, E! D: F" ?# q4 N
}; R* D! |) V0 b) @: E
else
+ G, M  T' b3 {3 b/ Q# b9 o//如果子不支持IAccessible 接口
% x; l. Q6 s, o1 G/ w! g5 W*paccChild = paccParent;
" }/ a9 y5 p+ Y5 h
. M" _5 t) @; p, [% J//跳过了有不可访问状态的元素6 g3 `: G5 q4 T) w2 F
GetObjectState(*paccChild,
: n4 v" ]1 V; _/ {               &amp;varChild, 3 D+ n# ^3 T4 B  u
               szObjState,
6 p1 ^$ x' c% M4 M8 T$ x: q& x               sizeof(szObjState));; x( j6 m! t. j! P1 t$ ~: g1 Y3 F
if(NULL != strstr(szObjState, "unavailable"))- L; |! u) u8 Y5 S  F; N
{
5 B  [# A* |) S6 lif(pCAcc)
9 B$ A# ~& H0 p4 \$ ^; vpCAcc-&gt;Release();: _% q& ~/ ]1 V+ `8 J
continue;
+ g# P$ {3 n* S' K5 Z# ?}
5 r; N; |; i8 P+ ]! l' x: B//通过get_accName得到Name: T: {, w  n, v
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));6 M6 N; N5 t# k* |+ b
//通过get_accRole得到Role
& a3 C/ d& D5 x9 H- P" j; }# _3 J" NGetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));  a" B; `$ h+ C8 d4 s6 W+ s
//通过WindowFromAccessibleObject和GetClassName得到Class- u9 z7 w  j5 m' D! O- J
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));; |9 J2 z8 m6 y* W; n
//以上实现代码比较简单,大家自己看代码吧。
. P0 s/ D2 N  j5 e, L# [9 Z" K9 o2 i9 f1 u) W" J0 c/ ]$ A3 \0 B
//如果这些参数与输入相符或输入为NULL. ^: l" j+ X# J% Z3 u! N" k
if ((!szName || # Y7 `8 B* }2 P. H& S
     !strcmp(szName, szObjName)) &amp;&amp; : f7 j0 g; S0 J# [& c* n6 L
     (!szRole ||
4 m2 q( I$ `4 X- s0 ^      !strcmp(szRole, szObjRole)) &amp;&amp; 5 w( Y7 {! S( w2 @4 O8 t
     (!szClass ||
/ I& v4 v  Y: g5 f9 J6 Y$ [      !strcmp(szClass, szObjClass)))- M5 |$ Y9 y2 y# }4 w3 w) U( @
{2 O4 L8 y9 b; B, d0 i  k% ]
found = true;
3 m% V* @! D4 T; ]; R, c& L*pvarChild = varChild;
, k$ R- }: B- ~break;
: ?" j% l7 I2 {' n1 J) f}0 T  s$ [  Z2 ~3 t  _3 {+ @) @
if(!found &amp;&amp; pCAcc)
7 h$ s1 O5 o: {* @+ y' F/ y{8 D6 A$ S' {( J/ \" L
// 以这次得到的子接口为父递归调用4 ?, v4 t4 v- Y& m- ^! E) g  t
found = FindChild(pCAcc, % ]' @9 [" w6 n% b
                  szName, 9 c: f# R+ {6 k0 I( W
                  szRole,
8 R* k/ q2 \$ U& X; q" i, Z                  szClass, 7 Z$ Y$ V; c$ b) Y% I$ S* t7 {/ `
                  paccChild,
# q9 V/ ]) K) l* t9 O9 X) A                  pvarChild);( r6 c7 n8 Z7 @, h0 r
if(*paccChild != pCAcc), T4 d; z8 w& W8 ^% X
pCAcc-&gt;Release();
: q5 U4 ^6 K5 `" Y}
( F, l! M3 t4 A; R. U2 u( t* |}//End for5 R, L- e$ k' F! W

1 @* R' l+ W8 h8 ~% K! H// Clean up8 j) s4 f8 f9 u! a
if(pEnum): p7 x9 A& I3 b( J3 e2 w
pEnum -&gt; Release();6 h% Y; r* `1 g4 ]1 j, O5 l
" I+ n" W; T4 U
return found;
6 |" h+ ~8 q& y}& ~7 L. \7 J0 Z! X
9 s" @" A+ E) U  Q$ ~; {( ~0 ]$ H
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,1 n& Z* `' J+ y4 @
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
1 h1 m  e3 J" z; _+ D//将这些或数转换成相应的用逗号分割的状态字符串。3 i% G3 P5 k8 \0 u9 ?8 z4 `
UINT GetObjectState(IAccessible* pacc, $ ~6 O5 X/ o$ A
                    VARIANT* pvarChild, & V  a# A" K% n. z2 B
                    LPTSTR lpszState,
8 _  r' o$ B' N1 y                    UINT cchState)4 m3 ^- s) d6 o: w' ~
{* q2 h& Z/ n  E' t
    HRESULT hr;
% r9 `' w; [4 P8 Y    VARIANT varRetVal;/ }5 L6 ^: f2 Q: W/ d

* C$ M& [) z: ~0 Y3 A. Y9 P    *lpszState = 0;
% [' `* b2 V! p' i" Y- m
# C4 l6 l, c" v    VariantInit(&amp;varRetVal);: i8 @( C/ i& P2 x% |

4 F' j1 c, L; y* d6 Z* o$ H    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);' M0 ^8 ~' Z- G9 f

: x/ ?$ ~6 Y+ r9 d4 fif (!SUCCEEDED(hr))$ h( H+ \7 y8 D* w
        return(0);& y$ k) R, k+ W# A$ f" ~" s

3 Y, }9 @' k9 K, a3 K4 a# kDWORD dwStateBit;; P: l8 w: w4 ^1 T
int cChars = 0;
) k( }1 c. S1 A5 I( D8 h: [    if (varRetVal.vt == VT_I4)
0 P' E. d1 Q+ h" o{; |' {6 F# J$ m7 X: C, o
// 根据返回的状态值生成以逗号连接的字符串。5 {7 f) _  b! N
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; ! N" o, y8 y+ |$ q, _. |
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
8 o% T( H: p% T9 l4 E0 P2 c5 E+ N4 d               dwStateBit &lt;&lt;= 1)
6 E& N8 Y7 h* B" c        {
; w* ~- S. ~6 I' m            if (varRetVal.lVal &amp; dwStateBit)
8 n: q' ^9 c  z. K, w            {  i7 k% {2 V. M
                cChars += GetStateText(dwStateBit,   J/ n1 q! j: }! T0 D" U
                                       lpszState + cChars,
* ^. |6 k+ Q; Z% }5 v! x, z& U' e                                       cchState - cChars);: g  [& K" L* v' }7 @
*(lpszState + cChars++) = '','';0 R$ w+ [1 Z  L
            }0 T0 p1 y, O# _" h
        }
8 Q6 t8 k5 Z* B7 F6 G% D* \0 bif(cChars &gt; 1)+ N- r( w+ i1 ?! y
*(lpszState + cChars - 1) = ''\0'';
/ s  }$ D/ V5 i    }( J/ L, x- k* K" y0 ]
    else if (varRetVal.vt == VT_BSTR)
* Z( `; S. \% m: ?8 K    {  Z* [- U/ B/ i" h; |0 T
        WideCharToMultiByte(CP_ACP,
1 |2 Z, o; z/ S: K( ~! d                            0,
- ^+ h. q; g+ O7 _7 S; V                            varRetVal.bstrVal,
) U& k3 q- \" ^+ @( L9 k- `1 H                            -1, : E8 q& Q+ R: j; F$ O6 Y" m
                            lpszState,% r5 C1 l8 e8 w( O: W
                            cchState,
& R; t2 f9 }  O9 c                            NULL,
, R3 d& {$ s3 E1 l/ m7 Q( |. O                            NULL);) A  d3 e, p5 v) N
    }; q7 d5 A5 _4 o
; b" a' Z1 `  ^( R1 H! E
    VariantClear(&amp;varRetVal);
/ n7 D* d. J) f" v. h' o+ M( p9 ~1 Z0 g+ V
    return(lstrlen(lpszState));$ L/ H( I6 f9 k, ?* D
}</FONT></TD></TR></TABLE></P>9 j9 F' k/ C/ ?3 U
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)  b$ Q& j' @; R! ]; }6 j' W* d
4 F' ?: v' D3 Z/ i* z9 ~
四、在 IAccessible 接口上执行动作+ T# Z6 {& @3 f: y7 ^; _
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
  _# R$ j. O' T    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
# u8 `! @$ `5 S0 L    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>. W9 x  a) z4 b6 s1 n! C
<>
  y. H( i  Y  E  F7 t* p, K3 C<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" w) D% b! ]: o9 g; ]4 k. x
* f' t" w$ n; {$ s5 H/ y1 ?9 [* [<TR>
# _; s( n8 @; q<TD><FONT color=#00309c>//在文本输入框输入"regedit"9 {# J1 d+ L4 p' `- C2 z
if(1 == FindChild (paccMainWindow, "打开(O):", 3 A+ [8 T0 e8 k3 M; a* b7 c0 U
                   "可编辑文字", ; u  B1 ]! P4 u, P
                   "Edit", 0 k4 h+ G9 H- J9 o- G
                   &amp;paccControl,
* i5 ~2 O" A8 s                   &amp;varControl))& v3 F. l% D! }* F9 X# W0 [
{
8 Q  F2 o! Y6 s( V4 L//在这里修改文本编辑框的值
, \$ b0 r- j+ khr = paccControl-&gt;put_accValue(varControl, 1 ?, L3 u# y+ ]  f/ R1 y: B
                                  CComBSTR("regedit"));
( ^' `: d  l0 ]& H, apaccControl-&gt;Release();
$ A$ W1 g: H' c, y, o. |VariantClear(&amp;varControl);# J' n% Z/ |# T9 Q4 |
}
, q% G  t- B1 K& Q+ \8 x  m3 f& k0 l  T
// 找到确定按钮,并执行默认动作。
' M9 B$ @$ ^6 j( S# {if(1 == FindChild (paccMainWindow,
$ n! w5 V/ y$ f+ C                   "确定", ! ?8 x$ V% D; p* [
                   "按下按钮", ) I: A* Y- j' w$ \- j  U: ?+ g
                   "Button", ( I7 u8 `! P9 i6 [  C
                   &amp;paccControl,
1 i* n% n, e/ K" @  @                   &amp;varControl))- Z; h8 y" e; a9 P
{
& Z. `! U1 S; @0 M7 m8 E9 T* b3 s: f//这里执行按钮的默认动作,即"按下这个按钮"4 w$ [2 H( e- S# p# j
hr = paccControl-&gt;accDoDefaultAction(varControl);
4 Z7 }/ T1 t7 H4 \: V, T% XpaccControl-&gt;Release();/ r3 y) r, X2 h0 ^% J
VariantClear(&amp;varControl);* x1 r( T) U2 D8 O3 d+ A4 T
}</FONT></TD></TR></TABLE></P>$ b, H0 K% U! L
<>现在,你会发现已经成功启动了注册表编辑器!!
8 T+ t% _, @, k1 u$ o</P>2 x" h9 d- @1 r
<>五、模拟键盘和鼠标输入  y! X5 U2 C2 o
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。. h$ b# V% C: S1 ]. ^
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
& p0 I) S" S) T- v/ P! {0 A4 b    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。, U- ?" E- m- C/ I1 [$ k& v
下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>: ]' S. \! _; j
<>
3 W3 F& `4 F  i/ ^% D( j0 E<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
. k# L$ L! m( l: o& j+ o9 {$ l1 y0 }8 A7 J8 w) V8 B1 U( m
<TR>
9 {1 i; @' f5 ^* z9 M<TD><FONT color=#00309c>INPUT input[4];
8 @, \" [: A/ m% Rmemset(input, 0, sizeof(input));
, K! ^: X$ b) a+ ?5 D! m0 ^/ v5 W8 L7 g
//设置模拟键盘输入
1 g+ W! @; f* m8 i2 A7 k3 dinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;. q3 X4 f  p- ?
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;  S4 B; r  b! g* E
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;8 h  X- T0 W' D/ i

& H- [4 l9 F; F, m3 t6 e5 U// 释放按键,这非常重要
& O0 O6 X9 s& ~' w- einput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
: V/ L1 c9 W/ c2 a& u
+ l( d+ t9 \9 z  ^$ S1 DSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
8 H- j+ ?! B3 }8 G$ \6 ~; X<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:), `6 e' y5 A3 D  f
</P>
" x/ A3 U% r. [" r4 \<>六、监视WinEvents& d3 t# W0 e( j" h6 D. Z  ]
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。( L/ y6 r$ |- J+ j; v  [! {2 B, p
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。6 b! ]' |, O6 y% W% V  a1 ?
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>& h7 z, A7 R* I; q6 `
<>+ L) _- p! R0 B! U/ M
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ r* g' s  q( y0 P$ k" O+ k* M

8 M% j1 c3 F* `' T+ @  e<TR>* S  [. Q% v+ R& o* y
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
- ?: P  O7 E, R4 Z& o+ E! r9 |{- t2 ~# z$ M8 V& c
hEventHook = SetWinEventHook(" X- N( d, L$ L' T: S  A
EVENT_MIN, // eventMin ID
5 C1 h, E/ _" \& m4 JEVENT_MAX, // eventMax ID
/ D$ i, `% B& g7 {# QNULL, // always NULL for outprocess hook- I7 G& t' S$ d5 }; O' {- t; G
WinCreateNotifyProc, // call back function0 t5 m( X9 q9 k# a/ A/ q, S
0, // idProcess! v" A$ Z* N9 \5 a6 z* k2 R+ [3 @; y
0, // idThread , F& q9 V. [  i1 ?* ?; Q4 }
         // always the same for outproc hook/ u3 R, `# `& U  ?8 J
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
# e. u; v2 a2 h0 C$ |4 H' k% F}  </FONT></TD></TR></TABLE></P>8 b; D: J( R! g: K( H9 I2 P# v
7 e7 E* u! X% E5 I  {
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
& n4 d7 y7 p$ m8 i7 G# h. K下面是回调函数:</P>
3 J- m- [+ t* J& g. z# T2 l<>
2 y6 ], i  E0 D4 L<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* r, l2 e1 S# E) Z( [0 N" \
# g8 r8 o6 }6 p0 v  T6 Q7 d# W
<TR>( O; x4 ]# R; e  S9 ~
<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
; o; ]- W7 k( B4 {" O; h6 u( uHWINEVENTHOOK  hEvent,
4 W4 P4 d3 Q8 P  c/ P( VDWORD   event,
6 J) W/ B, ~: f; kHWND    hwndMsg,- x' u8 @$ b% ~( ?" G
LONG    idObject,
6 v# h, q- F% P! T4 U1 w5 b7 m- r) aLONG    idChild,
5 b( t- T( z# P: y6 _! l7 `DWORD   idThread,
$ j7 k( i% q: m( A7 EDWORD   dwmsEventTime
6 y6 x! v1 k$ d/ B" l)
7 p' q" Z, m: k9 e3 F" @* w{. p' S% E# Z- \4 g) h- K# a

  \7 H+ x+ \% b$ R# Cif( event != EVENT_OBJECT_CREATE)
6 |5 Z  z8 F4 c. `- m2 rreturn;8 S2 ?- M! b8 a
/ V/ [+ P# Q" T* x: L5 d
char bufferName[256];
+ T5 K1 ~; R' a# z3 wIAccessible *pacc=NULL;$ c/ A, i/ c! _5 t5 g5 `
VARIANT varChild;
  C( Z2 B7 u7 x: K    VariantInit(&amp;varChild);
( ]+ z/ ]- Z3 K9 ?/ [//得到触发事件的 UI 元素的 IAccessible 接口/子ID对2 G1 b) ]8 y' h9 s% t
HRESULT hr= AccessibleObjectFromEvent(hwndMsg, 5 w+ Z) t# o7 C
                                      idObject, * t2 W# H$ ]/ e: \+ I
                                      idChild, ; f; G- u0 B6 E8 M3 W' C7 |+ L$ b, M
                                      &amp;pacc,
: h# M7 _& b" m' p                                      &amp;varChild);
" Q. O. P2 e5 _0 O/ z) c) o1 n* N- i5 @, M+ B4 a
if(!SUCCEEDED(hr))
0 w$ }/ c, D; a. @. q- u9 L+ u; p{9 a3 v( `8 x5 K, Z7 |& w( {
VariantClear(&amp;varChild);
+ k4 Z! `, A' ?4 H8 ?return;/ q. F) {, l! j* Y; _5 s
}
# l/ C1 `1 `# N" U) |5 N7 c//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
3 a2 B2 _- i! RGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));: ?- |1 J4 G/ k
if(strstr(bufferName, szMainTitle))
* c& H- n/ C. q5 dPostThreadMessage(GetCurrentThreadId(),
- }& \. h3 h. \4 n                  WM_TARGET_WINDOW_FOUND,
9 _& g' m. m4 P9 p; P  C+ t                  0,
  Y7 M" }  c3 d+ v2 f0 N/ v                  0);9 s3 B" D! g& [6 `

+ e9 F/ P8 s, @* Z; Nreturn;7 [7 n$ }/ Z) h
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。+ c0 ~' R* t+ T% s. u, P, i. e: T
8 ^$ @! u- V8 @$ h  ~& K/ v  P+ {
附录:
+ q$ a2 W. M) u: Q% |% G
8 P3 m4 O' @- i0 e关于IAccessible 接口/子ID对:. |$ b+ t$ [; O6 ^( s7 Z7 e( I
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。# A0 S- g  J1 W* M0 _
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
  g, ^& U+ F' D2 S, e9 R8 T, y    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。- R0 m* Q6 X: L. O* M0 `0 ]8 o
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。) L: m$ Q' n4 l. M& U: G
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!, r1 V1 h; d! U

8 c& X( P" Q% e注:% C' g' n2 d' D7 L% z
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)7 g( [2 t; I4 U, }0 a8 r0 W
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。3 I: X- W$ P( x% h: H( u

  g8 H3 \4 r+ g/ b' N4 O9 S参考资料:
! G% @7 A% o6 Y0 k3 B8 r, g: T' k<UL>: ^2 V" h3 [4 ~% t1 k' ~- q3 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 9 W! v; ^) W0 r. t
<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-8-9 16:13 , Processed in 0.658487 second(s), 58 queries .

回顶部