QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5765|回复: 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>
) N$ t+ K* c; L% h! k) \/ P7 ?
4 Q. R, |, n3 B! U1 X) b<TR>
9 q+ [+ l- H/ y& X7 t7 `% ~<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
- F8 j6 ]) J. b0 R/ n1 S4 f原 作 者:崔传凯
7 }0 f$ s9 V. p原 出 处:vckbase
  B5 [$ B) o8 G  |( F9 q. V9 u: Q# m发 布 者:loose_went
% Z6 T; Z" ^& r. F) e6 X, p/ `发布类型:转载+ a8 h( e* ?0 y: m
发布日期:2004-11-12% P. C# R- J) A( Y/ U
今日浏览:6
$ R! L+ N) }4 {5 H; S总 浏 览:234
/ Z* ?3 P5 t* h</TD>2 _5 V; W+ \+ H
<TD vAlign=bottom align=left width="15%" bgColor=#efefef><a href="http://www.vczx.com/article/file/20041112092122_vczx_msaa.rar" target="_blank" ><FONT color=#003399>下载本文所附源代码<IMG src="http://www.vczx.com/article/images/zip.gif" border=0></FONT></A> </TD></TR></TABLE>) Z6 }8 G& a1 q2 D0 _# O
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>9 [2 t) y5 v8 y7 n8 o: C
% X+ }4 q. M! j; x- R
<TR>
+ E$ i+ l8 {9 `# \<TD vAlign=top align=left bgColor=#ffffff>
$ g6 Z, |7 j2 W6 M$ @: `
' ]# C( P) o6 T) z' ~<>* m+ |! d9 E2 F% B2 i1 H  K( q0 F
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>! p/ }1 @4 z( E/ _7 g+ z
5 c/ t- g- Q" _  [# p# d0 X
<TR>
* T0 g: g8 X5 N; n<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the ; K$ B9 s. `5 `4 M' T- u4 Z9 Y! `
way accessibility aids work with applications running on Microsoft Windows?. It 9 P. O9 W  Z0 N* z& V) v
provides dynamic-link libraries that are incorporated into the operating system : z8 c( A  t) Q5 N# M; d
as well as a COM interface and application programming elements that provide * v* w5 x7 h" C( n
reliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>4 C" Q2 B* m  I5 L3 F5 L3 j! S2 ?+ k
<>一、基础
# z: |& E4 N1 H/ S9 n: m( U    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。: Z6 x: i: }1 T3 s1 q' u# c
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。: L, ?. }, ?/ `1 E' V! Y
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>" x4 T3 q8 O: t9 c$ U" {- E! x( C
<>二、Active Accessibility 原理" d" t- u. y% G4 U
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。: w3 ^. L- y9 B! U! [; p
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。/ b# j& I1 F3 G" M1 i
    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。9 `) a1 \1 c2 B4 N
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。( R$ e) _0 C( Q6 H
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>6 G" Z0 Y/ _) [6 L
<>三、如何得到 IAccessible 接口指针1 y. o  `7 f9 M* w8 ~3 ?
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。3 [% O. g+ C/ r$ c+ e% @
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
% W) {# l' x& n) y9 B* ?, e1 u    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。
- Q" @- i6 [& \8 c4 c/ |0 _    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。7 h! O4 E$ G9 {5 t- q* {
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。7 z3 K" J4 C* t8 ~' T4 b
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。
) z# }" O, Y9 Z8 q; p下面结合代码介绍一下它的用法。
: @: ^5 y/ c3 a& B我们来得到下面运行窗口的 IAccessible 接口指针。: F6 D  L8 x: L
& U. [, o* Q6 N6 \
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
+ Q/ H7 j. H) N2 v<>图一</P>) V* N. ]" _, y
<>3 x4 Q  k2 C$ L# c& D
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0># ?- |8 Y! Z" }; u% M6 e

0 F. c! _' L' |; ~<TR>: |% d$ [- Z; [# `, H4 Q
<TD><FONT color=#00309c>HWND hWndMainWindow;: W* P  U1 F& s5 N$ O7 _
IAccessible *paccMainWindow = NULL;# N! y% x% T) T" |' y, g2 h0 n2 M
HRESULT hr;) `9 s0 j4 t( h# L/ H& e
//得到标题为"运行"的窗口的句柄" Y( C" w/ G0 U8 a1 J+ u; q1 e
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))& r4 r3 R3 `0 q
{
8 n) K$ z! w0 r  A* a  tMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
1 G* E4 G% u" ~5 ], @}0 W+ M, U8 f" ~  z; e7 L
else
$ G* k8 A7 W" |. ~8 ~+ k. {! e9 @{
0 Y/ N# {: c+ d2 n) z- D//通过窗口句柄得到窗口的 IAccessible 接口指针。
$ ^4 N4 \9 b; _4 a  e7 C3 N) bif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
1 q* F2 }) A' K. n& _                                            OBJID_WINDOW,
* z% h& ^6 g: k4 D                                            IID_IAccessible,
# h' u8 @. ~/ z( {( e                                            (void**)&amp;paccMainWindow))): I  {  g( L; ~6 h( r9 w! n
{  i# O6 p2 D8 h  V/ B
//……我们可以通过这个指针paccMainWindow进行操作。
  r: v  U8 [  }% E4 r* r/ p) ~paccMainWindow-&gt;Release();# S' e, W+ ?& u, e3 z2 M( ~4 v
        }9 q0 d1 k9 [" M0 S' |1 F6 K9 p3 \
}    </FONT></TD></TR></TABLE></P>
( L6 [7 P4 E- m8 v5 C; L( t0 ]<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!2 \1 u0 `' W6 l" C
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……* `! e: k. T2 e1 Q
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>" j6 p& h; U9 [* ?. Z( w6 i
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
) R2 _6 ~4 ?5 i( h2 e, `<>图二</P>
' v4 j2 s+ c7 }$ R, k<>我们现在主要关注的信息是:Name、Role、Window className。</P>
% @3 w& D6 o* d% G" H1 k<>5 ?; u1 W5 Y8 z: e4 ~$ g3 m
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>) y1 B2 S& K% \) \/ C3 K
+ M& O3 x9 m* u, @6 H
<TR>5 v, Q& L# F( h0 V
<TD><FONT color=#00309c>Name = "打开(O):"& e. v; w% w5 ^
Role = "可编辑文字"
8 B5 `; T9 {  K) A7 dWindow className = "Edit"  </FONT></TD></TR></TABLE></P>
  p- h' `; R9 u) k2 j0 Q- g5 c: i<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。- x' W8 Q5 P8 Z% B
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
: j/ A9 ^; Z& ]+ C) j下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
( f4 D! H3 [9 j( d8 u$ }# z$ V<>/ m+ y0 m0 c( {  S2 B8 _% z( `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; `2 v& l; y4 W# ?8 z# h" v
& k! H( C& E9 p4 m/ c- v
<TR>
, _9 t; W/ M# ~( N2 X+ r<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
, c2 G) D; b- x1 ^/ I# qVARIANT varControl;    //子ID。
9 v5 c+ l0 V6 D' t& }# o9 q3 s' v3 b. `: p0 f5 F
FindChild( paccMainWindow,
- ^, ]- s) E4 i; X           "打开(O):", ; R& p% J, s  A) ?5 o; g. F
           "可编辑文字",
  s. d5 E0 X* \) ^! l* J+ C/ j           "Edit",
9 A7 {1 g' w5 K7 }( h7 x. G8 z, E           &amp;paccControl,
: z, Y+ n6 Q  o           &amp;varControl ) </FONT></TD></TR></TABLE></P>: d2 H, y! F5 u4 [2 c6 L! l
<>第一个参数是先前得到的窗口 IAccessible 接口指针。+ o8 S1 `3 t% u1 _) |' N. t. W
第二、三、四个参数分别是名字、角色、类。
; w! q& D9 R/ D1 H后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
/ m2 v1 [% H) j$ ~' @<>
9 g# q  R. G, N# B, P/ B; j<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>7 r& i# C- w$ _
1 }* b7 w8 R1 B  s; {$ D
<TR>
6 e, P  ]* E' C3 m) q$ ]<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, + K: s  M9 c( r
                         LPSTR szName, LPSTR szRole, / w' K% ?  U; y! B4 `4 F6 E
                         LPSTR szClass, 2 Q- z; v% X6 |# O& ~5 ~
                         IAccessible** paccChild, + M) x; ^2 v; m5 X3 x
                         VARIANT* pvarChild): \  m0 n  W4 p
{
# |% u7 D( J1 o' d% Q- a5 tHRESULT hr;1 W7 H4 D& l$ @# ]5 l
long numChildren;  u7 B$ j+ u$ P) i- f+ P% n) D
unsigned long numFetched;$ h( p/ w6 m- Q7 }
VARIANT varChild;. D0 D' L! {! ?
int index;
* \% }. h1 E) d! |2 c" Q; H, wIAccessible* pCAcc = NULL;) g- o% U% k( D' _
IEnumVARIANT* pEnum = NULL;# j" t" G4 i$ X, ]: W! q+ z7 |
IDispatch* pDisp = NULL;
6 ?, ]0 K1 I4 ~# r9 c" f" t# uBOOL found = false;- N) |7 y5 W7 Y2 ]+ D
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
$ y, e( \1 U3 u  x; @7 {
& C# e% g+ T! a4 b//得到父亲支持的IEnumVARIANT接口
  Y& C5 y( U, @3 g. C4 n  Shr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);' Z7 p5 s+ y  F
( R3 u1 k2 D; [" V: r9 ^
if(pEnum)! v, f6 f9 m2 x8 u! c' K& k: b
pEnum -&gt; Reset();& q0 T# M) k; n9 J3 {& F; }: K
8 O" Q0 r  W8 s/ u% H
//取得父亲拥有的可访问的子的数目2 S5 t; S* h& T0 I7 h/ Z# t
paccParent -&gt; get_accChildCount(&amp;numChildren);
. @3 ]& ]; z7 V- d7 P" A8 R! Y
" x& u9 n; R, z" \' b# r% o9 X//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。% C6 Q4 Z$ f. ?& @" f6 b6 N  W
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)% b8 t4 c# d- x9 w% N. @$ x- G2 n
{
$ ^! K3 I3 l  c9 @% EpCAcc = NULL;
! A! l3 t. n  K5 g& R( N* z4 J- q* f$ T// 如果支持IEnumVARIANT接口,得到下一个子ID' ]0 ?  ^0 a5 m, R' S
//以及其对应的 IDispatch 接口
( _9 l% t1 C) W, g* [4 E$ K/ Vif (pEnum)
5 K, o, q3 G4 C3 @hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); & ]" r; e8 A" o5 \$ J; l
else* r% _5 q! j9 y4 g8 x1 f: L5 q
{
9 w: t* I/ I/ q//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
' _& m" ^' S$ g; u8 M  PvarChild.vt = VT_I4;6 Z/ d4 Q1 R5 \) e! n+ L
varChild.lVal = index;. V0 U( m* d* v; F) b) R: n+ O
}, h* H1 `: ~% o* n8 Q
$ v. o" i( ]: c- \3 k
// 找到此子ID对应的 IDispatch 接口3 g- z7 O6 T! O
if (varChild.vt == VT_I4)
/ j4 E2 ?' f+ y. W4 k9 b{0 U5 |* q, K: r# g
//通过子ID序号得到对应的 IDispatch 接口! R3 F* t# c' Q- c/ P. h
pDisp = NULL;
5 ~& z! N+ e- d+ i/ ^hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);% A3 Q. x8 L+ n. U/ J+ I
}- ?; n4 `" S/ \- G; s5 v. B/ C7 h
else: B; W# L: m& F" `( p) L
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口. K7 h9 i0 x3 R
pDisp = varChild.pdispVal;% i' z% |1 g* J7 _- C/ G
! f0 M$ {5 E, f) L# F& B1 _1 Z2 W6 W
// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
; T) c) U! `, ?. @6 Gif (pDisp). x4 k7 s7 M, Z" Z  ?5 r3 Y
{0 M  A- X: w2 l8 z6 u* B" b
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
0 s3 M. S; W* [hr = pDisp-&gt;Release();
% N; ^' e- u& B# _$ I# Z}$ z# H7 J2 j7 f

2 P8 d- j0 E* F: x* ?8 N8 F% U// Get information about the child% r& N. q" A' J! Y5 w3 m
if(pCAcc)7 {. U/ C2 i) x) x# T' h7 b( i  J
{- {% i9 M9 U  p) @
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF% I' ~! b3 e( \; d) F4 J
VariantInit(&amp;varChild);
' }6 M# z/ |: |) _  h! v$ BvarChild.vt = VT_I4;
3 w! g- y5 a: l- A9 J  \# Z3 l# jvarChild.lVal = CHILDID_SELF;% G, {9 E+ v6 ]: W6 {' h9 F! `1 `
* r7 r$ T3 t. c; H5 C
*paccChild = pCAcc;2 {2 Z4 n/ f' }1 O& x1 L5 d
}8 D# E$ @' }2 Z5 Z3 @
else: ?$ y6 @' S/ d4 T2 x
//如果子不支持IAccessible 接口
1 W* W; A8 ~9 r  n+ Q  J*paccChild = paccParent;
* e) B" W5 Y/ W$ f1 y
! h9 t. q- L* ~  }# T0 W//跳过了有不可访问状态的元素9 m- B' y6 n# h$ Y% z9 A# a2 U
GetObjectState(*paccChild,
8 C# j* j4 Q( j               &amp;varChild, 7 d+ Q/ G7 o) H# i' R5 d  a/ `
               szObjState,
" L  B0 ~- r8 E% _) a* U               sizeof(szObjState));
! N% r9 V! x. g* m) ^6 Nif(NULL != strstr(szObjState, "unavailable"))) {+ [& `3 V5 q1 T
{$ M* ^. R0 C7 ~) R6 |0 W* O
if(pCAcc)2 b2 i% _$ J7 M, \
pCAcc-&gt;Release();
- z4 Z, ^5 c/ u8 `7 O- a+ c; p9 y2 \continue;
+ V! f: |, X# ?5 ?) T}
% C* ~- L% p6 j1 @6 u//通过get_accName得到Name
" i1 i- U9 b' {. W4 h: S5 EGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
2 a3 |4 R$ R3 X! a//通过get_accRole得到Role4 t, A7 Q6 y6 y. `2 d; m9 ]
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));4 k4 B5 ~1 H' L% V1 k* L
//通过WindowFromAccessibleObject和GetClassName得到Class. X' F% h# M1 }7 ~  [9 P% I7 L" U
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
( w" {7 s6 j8 `: A* y0 J/ E//以上实现代码比较简单,大家自己看代码吧。
! e, ?  T9 [# _6 ?6 |2 [8 F; C- ^7 Z. L3 l, L, C* c5 ^
//如果这些参数与输入相符或输入为NULL
* _9 i* C3 ^* S! e2 I8 S$ U/ nif ((!szName ||   \( U4 ?2 s. Y0 Z( T
     !strcmp(szName, szObjName)) &amp;&amp; - U; Z' A" a; ~* U9 w  N3 V) v  I
     (!szRole ||
6 Q' Z  t" w% J/ S  g      !strcmp(szRole, szObjRole)) &amp;&amp; , Q" G* A( j* e2 [/ ]
     (!szClass ||
) _! N7 T9 D( I( w  s! m      !strcmp(szClass, szObjClass)))
+ Q; ~8 _7 k0 J; Q{
) ]1 g8 f0 `- Y: T, ]found = true;
- U% T# c4 ^  U0 r$ g*pvarChild = varChild;5 ?( {4 G5 [' ]
break;
6 V9 h0 `& a% U% P! F3 C3 L}
9 |1 _3 P4 h* `2 A4 Sif(!found &amp;&amp; pCAcc)  G$ n9 |  \- Y" H
{
" n4 x* L$ |8 W% a4 a// 以这次得到的子接口为父递归调用# R7 ?# a  y: N0 x, Q  r& l+ w
found = FindChild(pCAcc, 2 [7 G) X* C% C) l2 k1 \; n# p
                  szName,
# B' Q  ~3 e( X5 y: I                  szRole, $ T7 q( `5 m' L( S  c% m% w) }& |% t
                  szClass, 8 k- D8 j# V* L# X5 o2 e; I
                  paccChild,
/ f: Y- N: h1 h( t6 x" N                  pvarChild);! D6 ]" C! p! _) H$ `7 {$ m
if(*paccChild != pCAcc)
, L& ?) U0 W3 z+ EpCAcc-&gt;Release();1 k3 T  A" d7 ~. r
}; ^% O6 T  c' i* Q
}//End for! M5 W- R4 E3 d0 g- b

4 \+ W4 \: c0 W& b7 Y// Clean up( S- e$ [! `2 v0 \8 M
if(pEnum)
% P4 H# [7 @2 K8 b( QpEnum -&gt; Release();
' g1 j' H7 F+ U# q* ]7 Y% X9 W
/ N! O9 B8 y0 k7 ureturn found;; r& @5 J6 r. N9 D: l. R
}
- [% z" q3 x- |' {2 v
& O: F! f3 L9 m: ?. W// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,. ^  Y! T! R6 ?3 j4 t/ ^- R8 y
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。/ B, \# v$ X* }
//将这些或数转换成相应的用逗号分割的状态字符串。
3 J# ]: U8 Q  w0 c! s) TUINT GetObjectState(IAccessible* pacc, . z4 y/ \; n6 E* J
                    VARIANT* pvarChild,
8 D; S5 J, r" ^/ Z! f% U: j                    LPTSTR lpszState, # e. d* k( A% T2 n3 n/ E6 S2 ]' F
                    UINT cchState)5 a2 P' Y) _, k. i9 A0 T
{
! A, P% K% N4 O( q$ w. N: }: d6 {# F    HRESULT hr;
5 ]8 Q/ _  d- J7 U    VARIANT varRetVal;
6 x3 H( r6 n: F; y0 A( X6 F
: e' x1 n" T* {! |8 O- u    *lpszState = 0;
; z/ s; c7 p8 }1 N, _, N/ |/ ]+ b0 a9 n7 t# O/ \7 }% \0 c: `+ S
    VariantInit(&amp;varRetVal);
' x  x6 D( L* p8 `- O7 v
; q- z9 K4 p9 A! Z. V3 v- H, M' |/ \    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
% ^* K+ p- Y1 N, o' ?; G! X0 n+ D2 ^/ ]% v+ d0 c' i; k  \: S
if (!SUCCEEDED(hr))
& j* R* {) r! Q& x; L; l" {3 l( n: D        return(0);0 q; E9 ]' [" B, w7 U* N- O8 Z

+ S' n% S2 q4 f2 @- D$ fDWORD dwStateBit;
6 V# ^: n. s- q7 n9 w% _int cChars = 0;
# V$ }4 W; d4 E    if (varRetVal.vt == VT_I4)
! U7 y9 h) Z" i{4 p7 _+ A3 X5 [/ h( g% i
// 根据返回的状态值生成以逗号连接的字符串。: o+ e! i0 V$ p; T- x5 k4 |
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
* e% l" X; M6 M: I               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
/ |7 r8 C- m. \               dwStateBit &lt;&lt;= 1)
6 b: j+ S* d' k! H+ t; D+ J- \        {, _' ]) \% j+ B$ ^6 v) ~; c9 O, N
            if (varRetVal.lVal &amp; dwStateBit)
) i2 L# l: i  f# E6 _# K# ?* d            {
0 ]* w# s. F) i                cChars += GetStateText(dwStateBit,
4 ?% b( l' Q: C2 F% J) X                                       lpszState + cChars, 2 T  P3 `. e5 @1 G
                                       cchState - cChars);3 E7 z" k( @" E; Z; T
*(lpszState + cChars++) = '','';
2 R& M: x4 r7 H' l/ [8 J$ H) {            }
; r+ p/ a' I  I7 ~. \# @, [" g        }2 {- l6 m* a* o& z
if(cChars &gt; 1)+ F2 j( Z/ y" t2 v$ b* ], S( }
*(lpszState + cChars - 1) = ''\0'';
- R# ^0 A' K4 u+ @6 u% L# h    }
" p; P( |/ ?2 ^: J8 X, }, ~7 q    else if (varRetVal.vt == VT_BSTR)% q, @' w% t6 h( [  h2 }
    {
$ N; r: G* Z6 d' q5 [        WideCharToMultiByte(CP_ACP,
4 w( y; U: _7 H0 y                            0,
! g8 O9 Y9 t/ Q4 U) N+ m7 ?! x                            varRetVal.bstrVal, 3 x+ V8 P& K9 I/ B5 l
                            -1,
2 V3 S  V% R3 {                            lpszState,& ^& a1 H. B& l8 \! N: d
                            cchState, % a/ c# C/ o4 g4 V$ h5 h
                            NULL,
' v$ g" |- G1 q1 _; A                            NULL);/ m& I* h6 _; [! q: b$ Q* z: d
    }4 m: G  R! w6 \
! V" \- {% `0 }6 {* A3 E0 C
    VariantClear(&amp;varRetVal);1 B, A7 l: q1 u% H! A! T2 |# P
1 y- U  b; X0 F8 o1 E
    return(lstrlen(lpszState));9 X9 Y. E0 p* E) K# i7 V% @' v
}</FONT></TD></TR></TABLE></P>
% n+ O3 w7 c+ J$ }<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)) b5 T: Q% U9 ^6 ~3 a
) x' Q! c; z% [" t! a
四、在 IAccessible 接口上执行动作
9 e* @  E* k7 Q4 p+ f6 B5 o% D    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。$ S+ [7 W! o5 J) f
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。2 k$ `' `* q6 V
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>% Z5 e$ n* J$ ~
<>4 f6 I1 Y, i% n. S
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
' k5 U, j, `; D. Q$ `/ J5 N0 g2 e7 P9 Y4 q3 m* e3 w
<TR>
5 n  B/ a1 [, B0 r; _<TD><FONT color=#00309c>//在文本输入框输入"regedit"9 Y/ H' m0 p/ J3 [) ?% V
if(1 == FindChild (paccMainWindow, "打开(O):", / q! k9 T5 a3 ~7 O
                   "可编辑文字", + o0 o4 Q/ o$ H: ^  _' v7 B
                   "Edit",
+ h9 w. [0 k' ]/ O. Z( F; D1 r% I                   &amp;paccControl,
! D" `9 l5 K+ Q                   &amp;varControl))( f% P3 J9 b: [- e  o  d
{, u- D. b; l/ N& q4 u; k9 R
//在这里修改文本编辑框的值& F7 w! L( N& r8 @- `/ H. t& v& v, G
hr = paccControl-&gt;put_accValue(varControl, & o' X; n" Q7 B7 h! H
                                  CComBSTR("regedit"));( e' W9 c4 A, ^8 q
paccControl-&gt;Release();9 S; ^! F- c) N/ E; e" I, O! w
VariantClear(&amp;varControl);' L& z( Q  V5 o9 z' X0 M! A' ]
}- R0 J; i3 @8 M
9 k( g& N( a. ~/ u0 E" O; N/ Y, D
// 找到确定按钮,并执行默认动作。
* U6 \! F# q. o! V" Yif(1 == FindChild (paccMainWindow, + S$ c$ S* a2 d) r! ?0 U" x
                   "确定", 2 U6 _6 i* w3 j& |- H; Z( e
                   "按下按钮", # U) R6 S9 q0 ~+ M+ B) \
                   "Button",
5 l; j, a8 |7 L. O                   &amp;paccControl, 6 W  j# J% Q9 J0 S4 x/ t" G3 T" \" }9 P
                   &amp;varControl))5 ?) d% d9 a6 U# R
{! p, o4 d5 U# _- Q) B" @
//这里执行按钮的默认动作,即"按下这个按钮"/ [+ C' J& g6 G  a2 ?1 r
hr = paccControl-&gt;accDoDefaultAction(varControl);: ^- K! L' j- q4 _: _
paccControl-&gt;Release();8 h% ^/ f: m! j% b
VariantClear(&amp;varControl);- ]) v; J, M  E4 s4 S) K
}</FONT></TD></TR></TABLE></P>9 h0 h1 l& J7 O% f& c. ]# p
<>现在,你会发现已经成功启动了注册表编辑器!!$ t- X% S3 t( g) L& f$ ~$ p- a
</P>
  Q1 s0 Z/ {( E% r+ r<>五、模拟键盘和鼠标输入* i6 y8 s# [* T6 t; k. h: o
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
( V3 `: Z9 q7 O) ?    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。/ {- W8 t/ H  [5 o+ H
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
# m; p5 b% v9 q. F1 m6 I' H2 n( ^1 s下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
9 G: h; h. d% I- H( v' z<>' d: \. A0 a$ D) d( H! r
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
9 V, t% t& V/ z
% h' K( L8 P( c<TR>* t3 H1 Y6 k% O6 T6 u( w" {$ o
<TD><FONT color=#00309c>INPUT input[4]; & a0 [) y7 a4 q0 |
memset(input, 0, sizeof(input));! Z% I" r  o6 q% C8 P

& E! z( J5 G4 }4 ^//设置模拟键盘输入
8 X0 e( \# I4 Oinput[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
1 W1 u0 }: P. j' C* t- e1 `  ?) J; kinput[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
9 B+ n" s; {' ^0 K( g6 w4 Y& Ginput[1].ki.wVk  = input[3].ki.wVk = VK_F4;4 Y# V- o/ S, v1 o; T' }
* A- q& p$ T4 T' _% T1 l" P6 ?
// 释放按键,这非常重要
, B1 ]; j3 q1 l6 N; a% B, Einput[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;8 E  e- [4 a9 w8 i: h# s

# }8 K5 i( B2 v$ B# R7 @SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>- e# E7 f' I- D5 ~$ R, R
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
6 Q9 I. t* R$ l& s4 M</P>) x5 [7 l/ K8 n% [( ?  f, ?
<>六、监视WinEvents
' }- V; Q, f5 ]; g    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
0 f6 V  i. C' ?8 o- Y/ J" M1 b4 a7 D    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
1 ]; [9 \/ y# ^7 A/ f9 E    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>, ~: |6 r' e, e/ N
<>$ |+ w2 o, t+ o" ?5 G- o
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 L" B# x% a8 y; a4 E
* N8 w; S. V5 t, V* a, m<TR>6 R) G: i& c) q* Z1 D# B
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
6 ?$ ?) q7 g, f  d3 f. |{, h+ @: V2 j" S- h- w# e3 h
hEventHook = SetWinEventHook(
+ ?9 N1 d8 i% G: l  K' R' l, HEVENT_MIN, // eventMin ID% b5 Z, h6 N0 m2 z2 D
EVENT_MAX, // eventMax ID8 m1 A2 i3 z" U1 r6 @
NULL, // always NULL for outprocess hook
$ N' `0 O; C- L" y% y3 f# KWinCreateNotifyProc, // call back function  {/ y4 W9 ~2 R
0, // idProcess6 ?2 w' f  W  H1 N
0, // idThread
) K- O' F& w- g" C! d         // always the same for outproc hook
0 D7 G1 P' a- LWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
9 A# |& s+ q; Q2 R* Y' F& {* Z}  </FONT></TD></TR></TABLE></P># u; f9 D! N" n9 ^. R" b4 c$ S

/ H: }8 a  u+ l7 `; y" h% c- ]. L% a<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。8 g4 b3 o3 S. j/ {
下面是回调函数:</P>, H- H! a" i  T0 v
<>
9 _# T: g2 o# P# G# |) v2 F3 p) C+ \$ z<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
' y. ?. t4 S6 {. {" s. d. ^# `8 l  f  j( T0 X
<TR>
: H" n5 e% N, z5 @6 O<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(
) z, M! L' G+ P& r4 n5 CHWINEVENTHOOK  hEvent,
9 a) s# l! U6 T3 j7 ~6 @7 R4 ZDWORD   event,
/ {" H% p& Y8 |5 Q7 m+ H0 u0 iHWND    hwndMsg,
# W7 \( l4 e) W2 W' T5 Q, [LONG    idObject,+ X) W" y% w& ~+ I6 M# G8 M+ w
LONG    idChild,
4 }- [: x* v, E3 `2 Q: o' zDWORD   idThread,
8 f& c5 Z5 U% zDWORD   dwmsEventTime
2 I7 c- ?8 I# ~6 J)
' B! O( E/ X$ y+ }{
1 m4 I  p, G% s$ J' d: r5 v' O7 r( C. f7 Q% _8 k  J
if( event != EVENT_OBJECT_CREATE)
4 X0 v5 c" [6 ]! Sreturn;9 e5 {% k$ u- @$ n
( c$ B" X# g( g7 |7 Q$ ~! }
char bufferName[256];! Z/ |2 p- n2 C
IAccessible *pacc=NULL;% ~1 E9 _$ h: I+ T) {
VARIANT varChild;
+ E, H0 s( J6 o    VariantInit(&amp;varChild);0 `5 H7 {2 W+ j
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对6 y% w: Y6 s8 f9 D
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,   W: b2 x1 t) t2 H! ?
                                      idObject, + B! `* O, X2 U/ w; \& {1 ^; A  H0 J! j
                                      idChild, ( f) m* ~' G, U
                                      &amp;pacc, 9 M8 q5 a; ?5 Q1 `( p% b6 @7 N
                                      &amp;varChild);6 O% a3 V3 U. U& F# j2 _& {1 C; m

2 h' {$ D, l& }) e9 t, h3 rif(!SUCCEEDED(hr))
' d6 k" W0 G2 ~: g: n# R{
0 P, `  Z& b& ^* NVariantClear(&amp;varChild);
; G. v7 b/ ?, o# F) f; f# C7 xreturn;
! Y) M# C- u5 C( X}5 s5 D! ~1 H3 R9 x  O
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。9 x, G4 h1 y8 J6 ^
GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
" h: l+ p1 p- L$ Sif(strstr(bufferName, szMainTitle))
0 {  O7 Y5 Z6 vPostThreadMessage(GetCurrentThreadId(),
' ]. }! a' h- T5 V- S: w# d                  WM_TARGET_WINDOW_FOUND, 4 X& }& }3 O% b( L5 N; W
                  0, # E- ]9 d5 X; k3 l
                  0);! z7 F' C4 q" F; l) M# g$ ?) k

! b/ Y  J1 {# S) L4 D' i, r  P, vreturn;
$ J3 S/ m, s" q4 u9 I% h, K}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
+ y0 P' |6 U" {/ o' T; U7 x8 p8 h5 J8 w8 W# z$ }4 i5 B; c  ]9 N7 ^) w& `# L
附录:6 F9 h3 Y5 Y3 x2 S. F) n8 h

) z# P7 k. _  S+ @+ U- Z) ^关于IAccessible 接口/子ID对:
  v4 r' ^4 ~; |/ K( ^3 G# f    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
+ w7 ^: _% C, G% p! s    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!- \0 c" E$ x% m) m2 X7 H4 j! q
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。
& _) h; P3 w; }- H* ^, f7 |    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
% x  `& I% V3 {    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!
" \5 @' X) K# E+ E+ X: B9 N2 H/ V) v0 \4 \' V9 N1 u3 e' ~
注:  H' L9 T7 I: V* ^
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)
8 Y. v* ~! L  ]* H    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。2 F0 @) ]' [! ~6 E1 @

3 M8 }7 a1 g  s  w1 v参考资料:
% e, r9 z# G! X$ P- k; M& {<UL>
: p$ |3 v  W. C, f' L<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
2 Y& U6 R$ B1 |, T# ?, P<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 05:36 , Processed in 0.372924 second(s), 58 queries .

回顶部