QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5696|回复: 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>
: r7 J. ?6 \; a; @* U( ?7 s( U- k2 }) H" ~# D6 g- O
<TR>
. s! c6 w* Z; q% }5 t<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
4 p+ M) Y3 s$ _. o7 v) d6 S原 作 者:崔传凯% [0 L9 v' Y4 D8 A# _/ q
原 出 处:vckbase1 m3 [! B" O- C! _5 K& c$ T* Y
发 布 者:loose_went
; ]% F7 \. S7 I& O发布类型:转载) w6 O2 c+ k$ }3 p0 p8 b
发布日期:2004-11-12+ @* S" S8 c, V% V/ T5 V0 j
今日浏览:6
  a: p1 Y; s2 Y% B0 z总 浏 览:2344 b6 l) U# a1 p8 i# B
</TD>. ]. w% `$ B  t' g( P
<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 }* r9 e+ i! k, ^  }" [0 [<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>
/ \; B  X5 N* r% p; p5 X, A3 a1 A# b6 m( M. P
<TR>
" P$ |. v; h* u9 Y<TD vAlign=top align=left bgColor=#ffffff>$ C$ p! w& Y$ ^8 W) Q: V

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

& q& f; O* m& Y+ l<TR>
5 {8 G! O$ }' H6 J* P4 _; t0 T<TD><FONT color=#00309c>HWND hWndMainWindow;: S& y  y$ F1 Y! v# J4 n9 y6 D- v9 a
IAccessible *paccMainWindow = NULL;
7 j7 S( U4 s, d$ zHRESULT hr;
7 [# f- L: z- [//得到标题为"运行"的窗口的句柄
; l+ C" r0 [6 S) \* Cif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
" a) J  ]  |9 c. r8 `  ?3 @{
/ W9 x0 S  G. B5 B8 x: R6 JMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);- U( ?  u; [7 f* n8 Z0 F% a4 S
}9 N4 T4 \) m4 Z' V
else5 `2 C. y. W* ]
{
, O1 J7 o- V8 u# D+ G3 \0 U$ I- @" V//通过窗口句柄得到窗口的 IAccessible 接口指针。
4 j9 O7 u  F0 Lif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, * M$ G5 q$ P6 p# q7 u% b8 r& g6 u
                                            OBJID_WINDOW, & r5 \7 E! Z: {
                                            IID_IAccessible,
0 W( k% F) v4 b: Y  Y: e4 [; U4 P9 i                                            (void**)&amp;paccMainWindow)))
7 \/ G+ i- Q1 q) [0 \1 c{' i+ I0 e- a$ J2 r3 d
//……我们可以通过这个指针paccMainWindow进行操作。! y1 \! X' a3 J2 N* o' c) N
paccMainWindow-&gt;Release();
. i$ s) x8 Z* ]  f4 z0 c2 f        }
0 J+ k% b; d( Z9 h8 _- f& F- s% d}    </FONT></TD></TR></TABLE></P>
' G4 F' T! ~7 y5 G4 @<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
/ x, n/ z1 C; i8 P& v    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……" x1 n8 o# A2 A, f6 D
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>( O5 D9 r4 v% m5 i$ U+ U  S
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>6 I9 Z- _6 v0 Y$ Z2 u& t6 J3 q: k
<>图二</P>& F+ @6 `3 f/ O( m/ \4 O9 F& G
<>我们现在主要关注的信息是:Name、Role、Window className。</P>) _; g& g0 i! m& H; V' Q" o
<>
5 ?" M& y' c8 ?( ?8 r<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
+ N8 r8 N) X) x; l7 c+ {" p2 x0 {: c- `; J) N5 ?7 m
<TR>' w3 v# x  S. L( h/ D: N  j
<TD><FONT color=#00309c>Name = "打开(O):"  G5 s; ]) ?! Y4 e$ J
Role = "可编辑文字"
6 `  v+ R/ Q3 I- SWindow className = "Edit"  </FONT></TD></TR></TABLE></P>( ]( [, j7 d1 b2 Q4 W
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
3 g2 q# j4 a- ]: I) d& y/ b$ S    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
0 K- Y2 n6 f* w! j6 b  \下面我们开始取文本输入框的 IAccessible 接口指针。 </P>5 q0 [& @% }) }5 m* @0 y
<>
5 h% r7 s2 v2 @( K6 l<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>" E& y, j% e, ~2 f

+ Y0 M1 B8 d% G: ]$ V; U0 q0 z<TR>) m2 L$ @' Z6 z3 G" g
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口( w, K: ?& M, p) O$ M& f2 ^
VARIANT varControl;    //子ID。9 e5 s) |  L' V* N( c: L6 {
( m0 i6 B2 P2 [3 r, H
FindChild( paccMainWindow,
; J! W' N+ q$ P           "打开(O):",
& r7 Z5 {6 |8 p7 |6 Y( a3 z           "可编辑文字", + Y9 B' o* g+ H
           "Edit",
, c" g2 O2 B2 a0 x  O% P           &amp;paccControl,
  W$ v. |7 K% i3 P4 [9 |3 k. W0 d3 r           &amp;varControl ) </FONT></TD></TR></TABLE></P>: U4 X1 Y: U2 Q8 {; _
<>第一个参数是先前得到的窗口 IAccessible 接口指针。, F+ \# i! H% `0 F
第二、三、四个参数分别是名字、角色、类。* F6 {% r  B3 b- u& T3 U- u1 U
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
6 R2 r0 L( u; e+ o<>
; R3 @# x* R/ \4 N, X<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# S" F( M( i3 R* d8 _- ~( }. I8 d5 D5 S+ O9 l+ x" K5 N: Z
<TR>' y/ `9 j4 K8 ?& {& T0 h
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, 6 Z. O- e/ O. U3 l% s
                         LPSTR szName, LPSTR szRole, " E# e- G, k1 p( J1 o! t
                         LPSTR szClass,
7 t" D' E0 m. y# g                         IAccessible** paccChild, 5 R( s1 J: p' g7 I. `0 T0 p) b
                         VARIANT* pvarChild)
2 O1 o/ Y5 j1 a! x4 i{/ b6 p3 q$ c; q# ?7 j, I, y6 c
HRESULT hr;3 w- y- k. M% C- [
long numChildren;9 l  c2 K9 D8 D5 ?
unsigned long numFetched;
: ]% S: K1 q: z4 h1 ZVARIANT varChild;2 |$ L; E% @' U; ^$ }% h& m; H
int index;
" |% y  z6 [0 N8 oIAccessible* pCAcc = NULL;0 f; f# B% N0 K4 t2 |$ P# @
IEnumVARIANT* pEnum = NULL;
) b& w' }* W" h6 u' X; |# I' v" `; nIDispatch* pDisp = NULL;
- r4 v; x  H) y! }$ w9 e0 o" _: MBOOL found = false;. [0 Y8 o7 S. M/ T8 b7 `8 ?) Y
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];  ^2 E8 N( E9 n9 i+ G* g: r: S7 D5 R3 w

8 \5 A+ S' z& y//得到父亲支持的IEnumVARIANT接口% y5 g9 S8 Z7 {( R2 W- `2 R
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
# T- Y: A. j7 ^8 h
$ J8 X2 Y7 f7 ^" Pif(pEnum)
8 @: C: t- o4 j6 [' M& RpEnum -&gt; Reset();* y! N$ h6 M! {$ N% f8 F

, a& B0 d' V3 p, v//取得父亲拥有的可访问的子的数目" ?& _' h5 e7 i
paccParent -&gt; get_accChildCount(&amp;numChildren);6 p7 Z  }! V3 @

1 j( Z! a: B8 y" ?3 D0 u//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。" m) ]3 V% n+ x0 h3 q
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++), _* S! Q) I4 k, d
{7 D1 K- {9 l2 P* o/ K7 T
pCAcc = NULL; & r) t. i6 [$ G2 J
// 如果支持IEnumVARIANT接口,得到下一个子ID
+ D% w; ~' z8 @/ H, V//以及其对应的 IDispatch 接口
2 w( W: @. V& q& B% M( G" mif (pEnum)3 f, D5 s: g5 X/ u! f) [7 k+ T, |6 w
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); - U" B  U1 |5 |, N7 ~  D$ r
else+ w! o( O# Q' m% \
{
+ Z& E1 W; d. `0 E4 l) }2 L, S//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号; W) e, c' ~: y0 o8 b% e
varChild.vt = VT_I4;
$ d  z0 J  b3 `varChild.lVal = index;
9 t+ p: Q$ W, M+ m! W' T}9 y. y' h# a" Y  C5 J" g. {+ U
+ h. P% u6 B& ?; E: `
// 找到此子ID对应的 IDispatch 接口
: u; b! T8 o' H1 M6 l. ^if (varChild.vt == VT_I4)9 }& k9 R( g0 d7 U  ?) i  |
{" c+ I2 `# S: O7 w- m/ G2 p
//通过子ID序号得到对应的 IDispatch 接口
5 h3 _* p$ B) u  r* d4 u8 N3 N6 L* d+ S$ ppDisp = NULL;
) M( v3 j. S. a* O& _+ Zhr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
! z4 a* ^  T+ c9 v8 _4 c  f}
: L, ]7 I- }1 }6 Welse
! T: n2 c5 O0 z- J  V//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口- F6 u+ O1 _; h2 L+ ~1 D
pDisp = varChild.pdispVal;9 x" I% w! W! p# ^( R  }  |
0 R* B6 x- A& `- T) {  K; I( a
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc4 H0 }6 \3 [. s/ ?' V$ |
if (pDisp); T3 N9 [7 P( o. ?7 e& d
{, D" w" Z( y  `6 i. E2 t
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
) R& i  P: {* j( `  Mhr = pDisp-&gt;Release();
! w) |3 Z& T  _* i}4 I8 ^6 t* z+ l+ h; F

+ l. y1 l$ B5 Z  l// Get information about the child
0 [/ M( j* }( q  _( Q! z, |' bif(pCAcc)
8 H- q+ Q- ^7 v4 X/ z' u{$ i- m$ T) |  A+ K
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
6 A$ H  L& h8 R% v7 N) T( cVariantInit(&amp;varChild);3 w* T3 t! h2 u# q: L
varChild.vt = VT_I4;# c$ R: G+ j, b. a2 z1 B
varChild.lVal = CHILDID_SELF;
% K. l# M# S* C9 V/ s
* k" @5 _* B, s& J6 ^*paccChild = pCAcc;
# ^; _, M5 A" s9 D/ |/ U5 ^}
% }# z  m1 B, V7 W+ U! n9 belse$ d" w: i* z0 A* s) l+ u5 M
//如果子不支持IAccessible 接口
+ F0 [% I! j' d*paccChild = paccParent;9 f/ e1 E0 G( G6 r- Q1 h
& a: ^' `1 p! a/ f3 F
//跳过了有不可访问状态的元素* v& J& k- {  e- V
GetObjectState(*paccChild,
* R. @4 f# r8 T4 h/ X! N               &amp;varChild,
6 J( U: `% G7 A& y% m3 X               szObjState, 7 n" n; S' j& Q" [- a% M8 u
               sizeof(szObjState));2 ~6 Z9 h# h) ?- z
if(NULL != strstr(szObjState, "unavailable"))+ c, o& T3 c+ _: ]% l( a8 W) m0 e
{
! \8 H; S% O) H- S& [6 r2 ?  z( k0 Hif(pCAcc)
! @$ g' S% n$ L- V" P' E2 GpCAcc-&gt;Release();! ^& q7 I* W5 O9 `2 g
continue;
" e6 {5 l7 N5 h}
  R  ?8 r5 G2 F6 r% e; H- i7 l, y' d7 I//通过get_accName得到Name( h% v! n" {" I- T3 ~- P9 N
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));. t4 t7 c% _0 |0 N( x, J
//通过get_accRole得到Role# q4 \, S1 z% B
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));) e& y6 p% k# a0 y, R! p3 ^
//通过WindowFromAccessibleObject和GetClassName得到Class
9 E( O5 K6 R. v! z* {9 I- KGetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));6 z: W2 B% [! b+ c. w
//以上实现代码比较简单,大家自己看代码吧。
0 O  l" ^) n3 b: J; b5 l2 I; Q( o
4 G  L' z  i1 [8 a& j' {/ O//如果这些参数与输入相符或输入为NULL/ w1 b4 A1 x5 A) S: Y: f
if ((!szName ||
  N) g7 b; p: C1 t  ^1 R     !strcmp(szName, szObjName)) &amp;&amp;
3 V$ A! u% P9 t! D     (!szRole || * j/ A5 j3 m0 s+ i; k6 f9 W8 P
      !strcmp(szRole, szObjRole)) &amp;&amp; + ~( u) Q6 I1 W% R0 Z. [
     (!szClass ||
$ w- l# u8 ^+ M* [, K7 q      !strcmp(szClass, szObjClass)))
4 k4 G: W: ]# |9 V{
( l) V" D0 |, p! X- Z$ Z9 }5 |3 Ufound = true;
0 V0 R5 P* b- [8 N4 x*pvarChild = varChild;
; R( I# ?; h+ F2 ?9 Z& P+ Y) x+ {. M9 Jbreak;; t. o  T: j+ U2 I
}
; f. T2 |  L8 K7 |' `2 sif(!found &amp;&amp; pCAcc)) @8 `7 \: O- x: T
{
" D( G, S5 a0 _/ O' q0 v// 以这次得到的子接口为父递归调用
5 k7 D2 {5 ?: x& W  }! e* ]& L0 e8 Cfound = FindChild(pCAcc,
, j+ \8 I% ]( ?8 E                  szName, + m* k( k* V+ a4 g" F% Z# t
                  szRole,
1 H6 }  y1 B6 T5 q: F! m                  szClass, , l; {1 w% r/ x, A5 g
                  paccChild, 7 N# q/ L; J9 q/ a( T0 L
                  pvarChild);
/ k  o) s! u1 v0 h% K3 a* Bif(*paccChild != pCAcc)7 E' o) T4 m( u2 v( P% \" Y
pCAcc-&gt;Release();+ U- v# ]! l, d0 T
}
- g/ [3 T! c/ M2 L( A4 n8 X}//End for; Q. S" |6 i1 R% S' X) V
* J$ m3 X: J+ J; @
// Clean up
5 t2 X+ |% A. G( P( m6 N6 P% f% h6 oif(pEnum)
  A! j$ C# I4 K: G9 y5 Q' S2 _pEnum -&gt; Release();* C) g4 v, J/ |3 u
6 k- I) M% N4 \: N/ t3 @
return found;
* b- `. R+ _. ~1 j% ?* q}
. P3 U; G0 ]4 z- }3 N  Q* w, s. h
' e" n# i8 t+ |// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
+ a& P- c1 `8 O0 g3 v//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。: M- S: z8 j2 n' o9 ]1 l8 o
//将这些或数转换成相应的用逗号分割的状态字符串。
  s, [  X% c' P& }4 g& |UINT GetObjectState(IAccessible* pacc, , Q& m" N1 H( I% f) V
                    VARIANT* pvarChild, 7 b: u9 h# M# K3 k; k
                    LPTSTR lpszState,
/ i5 r5 K8 `' [                    UINT cchState)
# ~! {% Q2 F0 m- r{5 B0 Q! \9 W8 d: z& q+ r+ ~
    HRESULT hr;
7 k; \1 O, S4 x, G    VARIANT varRetVal;
8 ~! [6 a0 P/ ]. z
8 [% {2 b2 T2 c1 n9 u3 g7 u. ]9 o    *lpszState = 0;& Z& y( e' s% ]6 E/ I
2 D$ [* }( K/ X, e
    VariantInit(&amp;varRetVal);8 ?! Z3 W1 o$ \& o( ^% Q) g

' i' S; I2 x+ u8 b( u4 o    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
) S8 C. u! J6 y" l" E7 r8 Q4 d6 }* A& i
if (!SUCCEEDED(hr))" {* f# U# K3 v( Y4 @5 c2 h; b7 A
        return(0);" `+ r% a4 G& J+ H5 Y5 j; s
! H4 G6 X' I9 C% [7 O% d
DWORD dwStateBit;
; @, _2 T. l8 f! Gint cChars = 0;
$ m+ \4 p0 C1 F3 n) u    if (varRetVal.vt == VT_I4)
) D# g4 V2 Q! y. e/ j# j{2 z! e& a9 b  q" L
// 根据返回的状态值生成以逗号连接的字符串。
$ j9 g" u" ]9 U1 p: u        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; " H) F$ H# e. D' |
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
3 X+ l3 G: k* F- l3 [! |* s               dwStateBit &lt;&lt;= 1)
* t3 f- t! X1 `  ~4 i, y$ m4 [        {
* w$ S4 y- k# O2 g( M' E/ L            if (varRetVal.lVal &amp; dwStateBit)
: Y: h4 s) M# E9 L4 [% J            {: I: @2 ]. Q+ U3 M* L4 H5 Y
                cChars += GetStateText(dwStateBit,
& N$ H9 Z+ D8 Z. z  |& X6 p                                       lpszState + cChars,   f% l% ]- r0 Q* \
                                       cchState - cChars);3 s! e5 [, f5 C; R3 {0 j
*(lpszState + cChars++) = '','';
7 d+ S) O3 w4 x$ T4 H8 E9 @            }% [; w0 e& n( U$ V+ b
        }
8 P4 j! y( F' X2 V! Q: fif(cChars &gt; 1)
3 A4 {6 d" h3 ]  Z) Z*(lpszState + cChars - 1) = ''\0'';  p" |$ ^9 S+ h' Q" c9 S
    }8 e  H: _7 }+ Y0 ]
    else if (varRetVal.vt == VT_BSTR)5 |: V# @* J* c& z9 V) a( `/ r
    {
9 Y( M3 b7 c, C# e9 `0 e. {        WideCharToMultiByte(CP_ACP,
1 B. g1 ?3 }' y                            0, . Q- H; V, F+ {2 o2 G( O
                            varRetVal.bstrVal,
  k* Q; ~, @7 n1 Z. G& z                            -1,
# M; A  F+ k7 @/ h) E: L. L; J7 |1 Q                            lpszState,
% |. t% w. z' [                            cchState,
4 D; e& w+ P& z: F                            NULL, , h5 l+ `" o7 V% o1 M) C7 H
                            NULL);
0 m# _* a8 K. C" g% _, _    }* S! Y; w6 D2 t: C  n6 R
, p7 T$ L3 l  D) ?+ D* N3 f; W
    VariantClear(&amp;varRetVal);8 d) z4 r7 h4 N5 r2 d$ ]2 f# S1 h
) l% S) _  `$ ^* Y: o0 O
    return(lstrlen(lpszState));. p) n$ S& P( l
}</FONT></TD></TR></TABLE></P>
% e" S# `2 C* J* X2 g<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:): v, S- |+ q! |

) ]2 r) o* F! {8 {$ a四、在 IAccessible 接口上执行动作
- p! w6 ]& d' `7 `; i6 \    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
7 g8 M+ L! Z: ]  o" x  h: M" A) l    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
, K& R  y* {. @: I" E$ B% v+ l! w    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>1 h% @+ v8 ]  q. \6 x
<>6 n1 q( p* n' h
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>$ H' H. d/ v) B1 D5 s

& J1 v' a! y  U& u0 U<TR>
/ C* \! |/ N5 r- P& m<TD><FONT color=#00309c>//在文本输入框输入"regedit"
' c' K) C# b! ]+ _if(1 == FindChild (paccMainWindow, "打开(O):", 3 z9 ]8 w/ S! M/ R1 b% q
                   "可编辑文字",
* U6 y' ]6 n+ P7 a                   "Edit", 1 c' B" \7 U6 P: _
                   &amp;paccControl, ) u: _8 S0 X0 c3 }
                   &amp;varControl))
+ \' K: I# b" c+ N0 B{" T0 }+ y4 A) {& M! g/ Y; L
//在这里修改文本编辑框的值
' x8 l3 [/ c% @8 o3 Q& H: {1 Ghr = paccControl-&gt;put_accValue(varControl,
/ @0 n# D# Q: _                                  CComBSTR("regedit"));" S& |" j3 @& e+ \2 A! k' \& _
paccControl-&gt;Release();
$ m6 o+ Y: r6 e4 F/ QVariantClear(&amp;varControl);1 c0 ^  L# |6 {1 v* W
}
5 r, c0 H. P9 z' l' H
: R4 R: d0 u- r/ `$ K, C// 找到确定按钮,并执行默认动作。; F+ q5 n2 P! U$ z$ S# H
if(1 == FindChild (paccMainWindow, , V% @  r! P: r2 O
                   "确定", . h' @( s/ }# q7 o9 Z
                   "按下按钮", " m* t5 k3 D# m! b/ U: ?0 z- y+ ]! G
                   "Button",
  U7 i! F. ]& ]. M" \% x& k                   &amp;paccControl, 7 J  t$ \, ]% K& ~& _  e0 ?, d
                   &amp;varControl))
2 u* L0 j' Q# ^6 `$ }7 F$ D{6 O2 g% Z6 H" ]4 a/ @
//这里执行按钮的默认动作,即"按下这个按钮"/ a' p$ t5 y5 G1 q
hr = paccControl-&gt;accDoDefaultAction(varControl);8 J0 O) N0 v3 E, r0 m
paccControl-&gt;Release();/ A5 W# T. z, N  ]
VariantClear(&amp;varControl);5 t( o3 v$ S6 i# I, y" u: M  V) D6 r
}</FONT></TD></TR></TABLE></P>
! _% s/ I; A/ s) X<>现在,你会发现已经成功启动了注册表编辑器!!
' q/ ^& \4 Y- L</P>
; x! d9 `$ O( |" {2 f<>五、模拟键盘和鼠标输入
0 J( i, H# e! M" D) _6 p4 a    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
; y: b) d/ [" ?8 b+ e: ^    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。/ k* b6 m" u* W  N6 Y4 V
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
' G7 [' `- K6 ^( t% K$ ~下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>0 d; ~5 f( ^& t5 J) z$ Q4 w* }
<>
4 X( o, z' ]  ]" P9 a<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
2 ]( O) j0 d; \7 O8 n
% p2 A9 T" ]5 ?6 ~6 G5 D6 v9 d" J<TR>, A2 z) t, c! D* `# Y8 s
<TD><FONT color=#00309c>INPUT input[4]; 0 k$ h3 ^5 R7 m. |; P$ x0 h7 l
memset(input, 0, sizeof(input));
# V) I6 B$ ?  r) c7 U8 l" Q. \9 T% {' b+ J1 B
//设置模拟键盘输入, j3 X" R- L0 d: a, Q; a
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;3 x/ M2 j; c4 `, S* q
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
5 _- N, \; F/ j2 l" ginput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
2 {; s3 v7 F6 C# y# B# p7 [6 _- j9 m& ]( }- a. h- s6 ]- I
// 释放按键,这非常重要+ O, u! D1 O) y) B; T
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
  G( o( ~, Z: Y) M
1 g: E* h. S* k3 G( T% XSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
6 l/ z6 L: B: T+ V' C: E<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)- J9 i3 W8 E4 q4 Y/ ~
</P>( ]1 x$ T9 v7 @/ t. a* X% [
<>六、监视WinEvents
! Y+ H. m. ]7 j    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。# t' H6 X: B* E
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。* A+ i( e6 x2 e: J! n' ]! i
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
" q# l5 f# ~2 s<>
7 c. i6 [/ ^& ~$ T' l3 x7 g<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>0 Z9 C6 F) l! ^9 w/ Z9 ]& u
) ~/ [6 q' }+ e
<TR>
* u$ T8 U6 t3 p' V! ^1 k1 [<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
# }  E" A2 X. t/ O{
' w- \! K& m- |hEventHook = SetWinEventHook(/ q: T" W1 f& z/ ]
EVENT_MIN, // eventMin ID
* N7 Q9 {" t/ X3 iEVENT_MAX, // eventMax ID2 o5 _& I) a7 D8 o, H! c
NULL, // always NULL for outprocess hook% `6 T2 F) q( @' v' S$ l
WinCreateNotifyProc, // call back function  n, {! E9 x: c+ w' Z) W
0, // idProcess0 ?3 C. N6 a  `3 `  U, ]
0, // idThread
  H, \  a. l8 a' G* L! b' \         // always the same for outproc hook6 X1 ?5 T7 b$ N; h) d
WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);0 K) N- M- Q+ v' S! [
}  </FONT></TD></TR></TABLE></P>/ p+ O( p2 w9 i( W
- R1 o7 c; `! U7 `! ~+ O0 y" N' U
<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。3 L. |5 L' j/ |: N
下面是回调函数:</P>4 C) B- O0 B7 G$ B! l
<>5 ]4 ]% Q9 {- p6 G7 D* Y/ K
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 |& S8 R, A" p4 @7 ^* J
& z2 K6 p- Z7 T# t4 L$ i<TR>
# j* x% T6 r- H7 u<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
& b, A' X) r' `( R& b! lHWINEVENTHOOK  hEvent,
' Q/ f" X) ?4 M1 v! P3 QDWORD   event,/ W! B" I; ]0 R- |7 e( G: }- T( r
HWND    hwndMsg,
! V( }$ }+ k4 v& I4 `5 VLONG    idObject,
6 \, E, j/ {6 r0 \- ELONG    idChild,
" u0 w( s9 {: n! KDWORD   idThread,9 i! n3 @4 Z6 t0 B
DWORD   dwmsEventTime' s; B, i. w0 b2 h9 s1 P
)
! e9 P0 N; L- C' g8 h* L! E' z{
6 q# K( O# m) S# ~% }2 X( I$ e- r' B, {, Q: o6 ?) k  e. P
if( event != EVENT_OBJECT_CREATE)
' \1 L. [6 n2 Xreturn;
' E/ R, N, r' j: p# b9 S: K3 b0 E  h' m* p/ Q! X5 T9 e
char bufferName[256];) R9 J) ~: n% v! D7 ]( j8 P
IAccessible *pacc=NULL;
' W$ g' B2 v) }/ XVARIANT varChild;
# I! v% }5 d+ |. x    VariantInit(&amp;varChild);
4 @% A1 _! f+ v0 p0 V$ v" H//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
* y/ z% b2 P% B/ Q1 qHRESULT hr= AccessibleObjectFromEvent(hwndMsg, . e6 f- f3 e  @4 g' F! V
                                      idObject, # x/ u8 i6 l5 z: Q% x% N( T. \( \
                                      idChild, ' ]) @. P7 r8 m- j1 l. u- H
                                      &amp;pacc, ! U% r; W& ]  z
                                      &amp;varChild);
: B7 n3 s; h% T) K
* P! W4 `% ^! H, Tif(!SUCCEEDED(hr))' G9 z. u+ F3 I, J7 n
{7 S3 e" Y# T* o
VariantClear(&amp;varChild);
) [* b2 \. n3 @& ]6 s5 {return;, N( ^( B  ^0 ~) ^* ^$ V8 m
}
8 U% P$ {1 |9 P. F( B3 E* e//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。1 _: \- f; Z* \' g! a
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));: l/ W2 _1 Q8 E' D* J
if(strstr(bufferName, szMainTitle))! T0 J1 \  a* O% N$ i$ P
PostThreadMessage(GetCurrentThreadId(), ) m" H, J1 |% N
                  WM_TARGET_WINDOW_FOUND, ! O0 A" E3 A0 }! k+ Q- s/ m" r' r& D
                  0,
% ?8 @. ~; S/ B4 Q" X- ]                  0);
# N  P3 G7 T' M" ?: E+ c0 x  Y- [+ o" V2 g# N; D4 F
return;7 J1 P) C: f' c7 \5 s
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。  m" {( G$ l5 d% c: g: c  J

. @1 M8 D4 X- x9 K% t0 i& }附录:
9 X  x9 c) @. Y  [, d5 J! s/ s; R% H( g& i. f& S2 q, C- f
关于IAccessible 接口/子ID对:8 U2 P- A, j- ~* U
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。9 q, y' U# L! U" ~7 `
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!8 w0 [- @& }* K+ q; f& [
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。& A& J# O6 Y- \
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
% ~- u/ n2 J5 }$ A: E    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!( W9 _( _3 k+ R& A. Q/ G0 v8 n5 J

4 j% Z" t+ D1 Q) F& B6 ^7 m& a% I4 H* I注:
" `" t; S7 x0 k- [    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
$ \+ K6 Z* K. O4 x1 _' P: q9 l- o. f    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
4 R# i6 {# ^3 |- \& N  L3 l, _6 z, T3 t2 D2 }
参考资料:
+ O6 x+ U  c. w* a<UL>
2 l" N( v: o# G<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 P4 ~( g, a9 W  P0 {. b6 A
<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-6-11 22:21 , Processed in 0.436303 second(s), 57 queries .

回顶部