QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5719|回复: 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>
' F# ?4 N4 L: ^! o- N' T; n& n
<TR>
3 r$ i  t3 |6 f6 R6 `<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
% Z* ~  R) u/ @9 F原 作 者:崔传凯4 p* e. G1 ]+ k5 I4 \
原 出 处:vckbase  M$ W2 x/ F. B
发 布 者:loose_went
7 k# ~& }( x& e0 g4 @6 ]发布类型:转载
% G  ~- {. C% q+ h) i" G发布日期:2004-11-12" o2 I; t. t% f# g( B/ A/ c
今日浏览:60 b2 V$ E0 W) C, V
总 浏 览:234
! J$ ]- Q% l$ M: B+ I</TD>
6 E+ b6 L0 {# E' A3 N! ^<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>0 g: ?! d2 n  q" E+ [* [/ B
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>% m$ Z, Z5 {0 A) K
; l( A# D+ b# N! t/ r' P
<TR>* S$ |1 x* d! O  Y0 [; ~
<TD vAlign=top align=left bgColor=#ffffff># U3 P+ ^$ |8 m. z- }
% R$ r7 N5 F# M, g# g. D
<>
2 V  ~1 q% e% W5 u- H<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
1 J; E! G/ m2 l" M4 |( \/ l4 X' K* V  J) j+ Q* m- z
<TR>+ e* s5 o' H5 A; o( L/ o
<TD><FONT color=#00309c>Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 2.0 is a COM-based technology that improves the
# z5 b, |7 x/ }! x, lway accessibility aids work with applications running on Microsoft Windows?. It
1 w/ l6 ]1 ?7 J+ e6 X% nprovides dynamic-link libraries that are incorporated into the operating system
7 O6 E! I# `# D/ ~) |, m" ]as well as a COM interface and application programming elements that provide
$ _* w" \& H' u  K5 v; F, treliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>  ^9 E8 g  ]; X+ R/ {" t  b% q
<>一、基础
5 V% q( x, @: T- _& Z    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。$ |+ G  t: _: F
    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
- h& I! D+ E+ k    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
' A& [$ F) `& H5 m) h' F  x& m<>二、Active Accessibility 原理
  S" |& T  ^& ?6 ]0 z4 ?" w    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
( d2 y2 ]! O1 ]    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
7 u6 @& Z& u3 `/ ?! l* n5 `, r( W; ?    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
+ c+ k* @7 C0 l2 p/ g( ^    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
9 y2 t. c$ C5 d1 ?' I    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>: h- h5 I5 E& ]8 P
<>三、如何得到 IAccessible 接口指针& ]; g! T! w- L5 \5 _% O/ ?8 Q* Y
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
* L* v9 A$ i7 X# @) u    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
2 [4 k5 i4 b' a$ D: c- o    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。9 A' S; r9 {6 o, W. A: z) a
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
/ L- G& Q, l0 |) ^( y0 w" k9 e    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。  T8 ?. T$ ?6 @) P( {2 ]
    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。) i! c0 ]- L4 W9 ?1 s: a1 o. F
下面结合代码介绍一下它的用法。
/ x/ t$ ~- {. \3 M2 t我们来得到下面运行窗口的 IAccessible 接口指针。
! \. t6 e4 m+ L/ Q2 a
, I  V5 j( \+ u2 Q( [+ z  f3 M. l<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>8 \# `9 @& M' o3 i' Q, o1 e
<>图一</P>
2 v* o9 V) R. i8 x<>1 z4 [% R* P8 U" O( X6 o% ]
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
7 B/ \5 y9 x  m# x( u5 d( y8 U* ]6 h; b
<TR>9 X! ^4 J( A: M7 d- Y8 d
<TD><FONT color=#00309c>HWND hWndMainWindow;
, \, M7 ]8 l, x, m: fIAccessible *paccMainWindow = NULL;
6 y5 e& F. _3 O7 L& X9 HHRESULT hr;
5 `# f4 a* Q9 ~! o' M8 N- L//得到标题为"运行"的窗口的句柄
6 S7 W4 y- s( Nif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
7 z! M, i6 e9 A, a8 E9 @' S{
/ {: p& `; e; q5 w4 m$ T5 A: b- rMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
" S, Z% J7 v$ |1 U) g9 I/ R  J6 P}, T% Z$ i% C) i6 [: v
else
" Y$ K1 z/ e6 ~1 B4 U1 W{/ g- O. p- a' a; j5 V
//通过窗口句柄得到窗口的 IAccessible 接口指针。
5 H+ ]. g* ]. w1 [8 }if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
; Y0 H% u8 v" U9 S3 [3 c7 t                                            OBJID_WINDOW, ; a) D: \& L" h" e, @
                                            IID_IAccessible,
# e: @$ F9 ^. B5 S9 T* h                                            (void**)&amp;paccMainWindow)))+ c1 l* Q5 Y# N7 t: C1 D* ?
{6 q7 \7 N: ]& _6 W3 Y: G) J
//……我们可以通过这个指针paccMainWindow进行操作。5 F0 P- l3 W9 `- o" c
paccMainWindow-&gt;Release();; H2 u+ h% z% `4 _! g
        }+ p" _% F  O9 n7 \
}    </FONT></TD></TR></TABLE></P>  q* K8 A6 V% y
<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!3 I' l$ v; _: m6 Q+ j2 e% W: Y
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
1 y  b* T2 |+ s, {3 U7 j5 j% {6 @    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>1 K+ n+ Q) V* T% H3 S$ M. o
<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>6 G5 V% z% j3 T/ d0 _
<>图二</P>1 I9 y& B  [! E& L
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
: @- n3 \, C7 W/ W9 h) D<>$ `* x0 s0 ~! U4 |& A
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ E! M$ O2 o# X+ S' I6 w: ~
$ w1 L2 E" W: }) E) H( R! p( A( D
<TR>0 J2 v& e2 V6 z( @# B0 u
<TD><FONT color=#00309c>Name = "打开(O):"
" b5 g# d% n: Z  aRole = "可编辑文字", T0 g7 @" ~7 E4 K. C
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
3 L' m; `/ s, P& C  n# P1 H- W: J<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
5 X- v+ G1 {: J5 J3 Y" ^! P5 s    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
% u0 U' H) B( h" q- o% K8 i& ?! k下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
& v; O/ e6 m$ z<>
8 J* }7 W% r* o: }; V( x<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# C- Z- L6 n$ \. t
' H  g3 X* J2 r! ]0 c1 w6 V* w, ^<TR>5 D) w/ V% a) E; [
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口1 g$ U( z5 k9 ~- x& p, I
VARIANT varControl;    //子ID。1 i, Y. H0 Z0 i7 P2 u7 ~

; V- j; o  J- }7 N# u, I% G' QFindChild( paccMainWindow,
/ j& D- M3 w6 K8 t3 b" o4 N           "打开(O):",
: H! X1 q: Z( Y- ]$ {0 _           "可编辑文字",
+ j/ N5 |5 ]& s* U           "Edit",
  j1 `" F+ v7 G$ n5 P0 N; k           &amp;paccControl, % T8 t4 w) N" f1 h: b9 n
           &amp;varControl ) </FONT></TD></TR></TABLE></P>9 B& b  a  c/ @: O' X! a/ |9 I, D% ^
<>第一个参数是先前得到的窗口 IAccessible 接口指针。
) C1 r7 J3 m$ {8 f7 V$ ]第二、三、四个参数分别是名字、角色、类。& a* H/ D( O$ w+ |" M0 Y1 r
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
0 w- H- U* v( s, X<>
  Z. m/ r5 m+ q$ P) U1 N9 c& W<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>2 p; [; ]8 e2 k, Z# x8 c9 I
- ^. _: F0 S% D+ h! C. G8 }, y
<TR>3 Q3 j+ b5 n3 L( @7 q, ^4 O+ |+ q
<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, # `# \/ m7 _. M. k1 W4 v* l+ ~
                         LPSTR szName, LPSTR szRole,
% g; |2 E6 T* g4 v7 Z! I                         LPSTR szClass,
! _  G4 F: J; d: X                         IAccessible** paccChild,
. H: p. t9 t4 R5 v8 i* H                         VARIANT* pvarChild)( v: @5 l5 U8 K- T2 R6 B' U
{, Y6 f& j3 L. d" X2 D6 v
HRESULT hr;& h9 z4 {/ F& e  g2 ?2 _) H  D$ R
long numChildren;) o/ w5 y5 d$ x' Y
unsigned long numFetched;
5 K0 a6 J1 o8 {; w5 C; X$ U) _. }VARIANT varChild;) `* g: C/ M4 Q$ n; b- M" \8 \
int index;4 O8 `( @- v- i" z3 C* ?! g
IAccessible* pCAcc = NULL;
1 O) c$ Y0 X: d% lIEnumVARIANT* pEnum = NULL;
0 N. F; ~9 D9 J, ]7 RIDispatch* pDisp = NULL;
+ r/ x6 f$ m2 q7 Z1 T! uBOOL found = false;+ t. i- L4 L% M" N
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];3 i5 U- i: k2 V; A5 ?3 c$ _( S/ o

$ i9 P' |# j1 i1 D. a$ v//得到父亲支持的IEnumVARIANT接口
6 s& N3 t% B- \2 @4 W; rhr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);: s& e; d; |9 k& _& r5 i
* I5 n2 x2 N( p  e# \0 j
if(pEnum)  w& k/ l* Q6 Z  D
pEnum -&gt; Reset();+ ?7 q- n; ~3 z- r8 L

& _! P  v+ w8 J+ O- v//取得父亲拥有的可访问的子的数目
2 p5 p( g9 P. A2 [8 IpaccParent -&gt; get_accChildCount(&amp;numChildren);1 a0 [4 \1 ?( d  A

7 l! J% |# b0 O/ k//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。+ W. b7 D  c5 J3 x( e7 E+ }
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)2 Q) s2 N3 C3 w  _, p8 ~0 u% L$ F
{% `$ N  ~& z# C2 V: j% x
pCAcc = NULL;   G! B5 U! Q- {, G' k# p
// 如果支持IEnumVARIANT接口,得到下一个子ID/ a1 t, i1 U' v2 j* u  z
//以及其对应的 IDispatch 接口! x- D3 V1 C5 d* o
if (pEnum)/ `& C2 j) T3 t9 |3 E" ]
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); & n2 X! W; p. h1 _
else6 g0 h$ n! B! ?9 y* v
{% L- w5 F% Y! p7 i+ c, F
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号7 d( N# k0 l9 w* {5 B* c* a
varChild.vt = VT_I4;
' @3 ]* c3 |7 y8 [$ A6 LvarChild.lVal = index;; B( K, q6 ~8 f# g% g% v; _
}
2 D! K! t7 A- `: t# w9 }. n) [6 D8 d7 Z! w
// 找到此子ID对应的 IDispatch 接口6 c, {! T4 q4 s0 `6 d
if (varChild.vt == VT_I4)
7 v5 i+ j6 V7 n$ Z" {9 d{' D. B5 {/ F; I3 Q- y
//通过子ID序号得到对应的 IDispatch 接口
" V- \! ?% L2 fpDisp = NULL;8 q1 r; ?! U0 l/ j4 k9 \. D* X* o4 Y
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
5 M+ C  k) L3 ]}5 p0 T" A) ?. t: a! V" a/ _
else* p- J* w" v) @
//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口! x: x* t$ k8 [+ v/ R" L
pDisp = varChild.pdispVal;
- V5 p0 P0 O0 K" ~
# B6 J$ E, J; m5 [% I/ |// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc: W' u% f, K/ F/ P
if (pDisp)
9 Y/ C' w9 ]* N& E- ~# ^) W, G{
7 R- B0 c0 n4 lhr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);- `$ ^. F) a$ @+ S# F  A! l6 r
hr = pDisp-&gt;Release();# _+ m. l' G& ~2 C) J2 u
}
  x" D, ~4 l0 D5 w# y# [* n5 @. ]' B. p) `; [' P! J- {: ?
// Get information about the child
! y" }/ g+ O# S8 I; {if(pCAcc)
& Q6 s* p% |8 Y- k) E0 C{
6 b" A& u6 J+ C& e5 |+ }//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
9 D& `' R9 Y' WVariantInit(&amp;varChild);
$ i! u& {) x/ ^- S* zvarChild.vt = VT_I4;& U8 }! R2 Z3 h) b& ^( Z8 b
varChild.lVal = CHILDID_SELF;. o4 `  ]$ _1 W

8 L4 q4 O$ K' C" e$ \*paccChild = pCAcc;8 m, ]8 ~* O6 s
}' P8 l( J' ?: I! ]/ T3 C* v; d
else
7 Y  `, k2 H  j- c: Q: f0 O//如果子不支持IAccessible 接口) [2 e9 X# D4 e: K. D
*paccChild = paccParent;
5 k6 w( y% v) A# |) Y
8 H) l7 ]5 a# [0 B( E+ K0 i4 M//跳过了有不可访问状态的元素; E' [7 C8 T  ]
GetObjectState(*paccChild, # ?! F- s! ^: R* j) m" g
               &amp;varChild, 0 G  S- H% ^% d8 a: l2 `( w0 W+ y
               szObjState, 9 o8 g2 x, F% L, m
               sizeof(szObjState));
/ f9 p  P- H+ mif(NULL != strstr(szObjState, "unavailable"))
1 |8 H# V7 ~$ U2 t8 [# |{
  ^. Z3 _9 c' e5 U+ D% L) Jif(pCAcc)
2 ]/ m! e8 R1 X1 apCAcc-&gt;Release();: w1 z9 A( a% m9 T! ~% O# _8 O
continue;
2 |) @& ~1 k; ~. l}7 F0 o, c8 J* z; f% y
//通过get_accName得到Name
2 M! h: E0 H. w8 oGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));: [2 B  S# q$ V* ^# [3 j$ g8 l( U
//通过get_accRole得到Role* j) w+ R- U* I. K( T
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
# \6 {" ]% s) S//通过WindowFromAccessibleObject和GetClassName得到Class
/ J* A) x7 s3 v/ B) i  U9 ~GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
8 K8 x6 Q, @) D, x. _, [//以上实现代码比较简单,大家自己看代码吧。( P2 ?! d, X! M! M, w8 E( q

$ I% d1 R/ \# ]//如果这些参数与输入相符或输入为NULL
* |" z2 i6 A. M; t2 p. Rif ((!szName ||
: F  W/ r1 x( z     !strcmp(szName, szObjName)) &amp;&amp;
8 i* @" a6 r) Q1 b     (!szRole ||
  m. m) _7 ~% G      !strcmp(szRole, szObjRole)) &amp;&amp;
' }& S! [( T* F  n; \     (!szClass || 6 t8 S3 z5 V  i: n6 H% d% d
      !strcmp(szClass, szObjClass)))  [; N5 k. I" t0 P# x
{6 y. I8 i8 E) f2 v* C
found = true;2 y7 Y. Q+ h3 Y4 e& K
*pvarChild = varChild;
3 F: H9 u) K2 hbreak;% u* ?/ Y, u# y5 a, q% \+ `
}0 z3 f' K1 _) O3 E
if(!found &amp;&amp; pCAcc)6 X. V1 Q* l5 W
{
8 t1 q4 e* U, c& y; \// 以这次得到的子接口为父递归调用
4 v: O1 H" D- @found = FindChild(pCAcc,
6 t0 U( ~' R) Q( x" K                  szName,
( _, |" f$ x$ t& M: F                  szRole, ! t( ~2 H: L! E
                  szClass,
# w" }7 X  F+ G/ ^                  paccChild, $ `; ?: g# \; T4 @$ q; n7 ?
                  pvarChild);: E* M8 M( r7 Z
if(*paccChild != pCAcc). y6 G) h' b; V% h3 f8 n0 [4 e( b
pCAcc-&gt;Release();& Y) q% h: y( c+ |$ W6 j7 R0 f
}
% v4 ?0 _: [. X, m3 e* J}//End for$ a8 E$ G) M- Z2 e: V6 K* Z" q
/ Y2 P) B: p! E! T  _9 X- l3 |# ?
// Clean up6 o7 A# G" {6 N& u$ L
if(pEnum)/ T" [: v, d1 d  y% I7 O2 E4 ~
pEnum -&gt; Release();
" X# y' c( A+ _8 x+ d5 ]" f0 x2 U6 C4 R7 y- f# T% M
return found;3 U2 a8 |$ |. A# p, `  N! d
}
& P# n8 {4 @9 e7 C3 G% W
+ _& I2 F0 g7 x  w// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
5 K0 |- v. U$ X3 N4 u//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
# S/ j) l0 ]+ O6 U//将这些或数转换成相应的用逗号分割的状态字符串。+ ?+ `; k. L5 }1 H& D
UINT GetObjectState(IAccessible* pacc,
# d3 @, s& B6 [                    VARIANT* pvarChild,
+ v1 ?" N9 b, m4 l                    LPTSTR lpszState,
- ?( Q' D* u* x7 r' l& i$ _) l1 l, T# F                    UINT cchState)
+ ^3 t9 D1 ]9 J* r: H+ X, }8 t- B; `{
/ I' ?. m9 D) H2 g/ l0 G% e9 G+ w    HRESULT hr;% {/ Z1 C' g4 q1 k4 u* q7 K
    VARIANT varRetVal;
$ W: Z9 c- d5 |; N2 e. P$ h/ g: Y+ `- p* G2 L
    *lpszState = 0;
# X7 U( w. `7 j- }5 ~. v% ^% `9 i+ a- U9 X* m, q# K: B3 b
    VariantInit(&amp;varRetVal);/ T9 f2 v, g- S1 |1 r

. N* }3 `/ R! E  u: ^: t    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);+ P9 _2 R& d9 }

! m; `2 [4 d4 K* V; d# C5 W, W3 Yif (!SUCCEEDED(hr))+ g- U% M& d2 c! _! b+ u
        return(0);
$ E1 Y1 l6 Q% s# W/ B* s  i8 T/ j4 p! ~: t
DWORD dwStateBit;
  q& n* n& v9 @; `# g/ Lint cChars = 0;4 [) `9 o! p* C7 }( l7 Q+ X8 u3 t
    if (varRetVal.vt == VT_I4)# }& H# K) {0 v4 j- k: B$ n
{
) x6 ]; j6 H1 f# U// 根据返回的状态值生成以逗号连接的字符串。
9 ~- R9 ~3 l6 k& ^* S! p5 u& H        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
; _; E7 F1 k7 V1 R5 c, I! s               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; 5 H+ t- [+ }! |! U+ y
               dwStateBit &lt;&lt;= 1)2 `" j3 n- ]& a- N9 C  u
        {
$ ]6 \4 g7 w" ]6 `            if (varRetVal.lVal &amp; dwStateBit)7 z. h4 q/ ]3 q$ A
            {
  f4 A% x- ?) G7 j4 ]+ d                cChars += GetStateText(dwStateBit, . a5 ~  Q1 [  y8 H1 @
                                       lpszState + cChars, ! b# s5 c/ ^) b$ ]
                                       cchState - cChars);2 q3 R: f1 d% @. D( s5 \
*(lpszState + cChars++) = '','';# L$ B+ t' K" m) I
            }8 D. z8 C# P0 L1 L3 T
        }
- O4 S1 S. `4 ?; Q6 Gif(cChars &gt; 1)2 L/ C# N1 F- q( U( T( L
*(lpszState + cChars - 1) = ''\0'';( Z0 \; l+ U3 o6 O+ }: Y2 e
    }- A5 Y8 k7 }" E  S
    else if (varRetVal.vt == VT_BSTR)
+ g& E4 @/ m+ @: R& g' V& N    {
( g  J8 ~  t  w* h, ]- R4 ?        WideCharToMultiByte(CP_ACP,
) A, l* @# I1 F- p, B, ~0 [$ g                            0,
. A4 J! w4 J9 q5 V- F& C                            varRetVal.bstrVal, + S4 t. [6 O6 [* H
                            -1,
+ E7 I: _! S, J4 }$ s                            lpszState,
( K$ d, ~& l  p3 X. u: Q                            cchState, : K$ J" l) |  R; O  j
                            NULL,
! j" Y8 J" z' Y- z                            NULL);) t$ J& R( E$ V8 s8 D
    }
- e+ ?( F& y# `8 `8 X0 o/ x5 \- g& p
    VariantClear(&amp;varRetVal);
( I2 d, m' [6 i0 R2 o& s9 w# U3 I* n) {
    return(lstrlen(lpszState));
" G% g; |+ J' t- ?% [8 G8 B0 S}</FONT></TD></TR></TABLE></P>) Q# H0 n; E! z% o0 G
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)( l% n! |! y5 i
* I: c5 T; P' H
四、在 IAccessible 接口上执行动作
: c0 k  G0 q8 H# K$ j, ^0 k- s    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。2 ?) i- g7 F, `- Q9 }" a4 C
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
- ]$ P, J0 o0 A" D/ T& J, R    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>; t; E) ~! [  }6 \( n2 k) ?' ]9 R
<>
5 I) F0 B: ^8 }$ d<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" W. @# v4 ]8 @) u( Z0 v2 g8 B
7 \- t4 U0 p8 u; T8 l/ K<TR>3 l, ~/ ?. ]6 o
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
# Q# x# ?5 a1 y; tif(1 == FindChild (paccMainWindow, "打开(O):", 6 a/ E3 @' k: k' p
                   "可编辑文字", % y# X4 ^" d% l# {6 W
                   "Edit",
: x7 r' o7 _/ {' W                   &amp;paccControl,
: h7 ~3 m* n" b( n                   &amp;varControl))
. M& ?  E. e" Y/ q, P{
! u1 \0 {( G5 \/ E//在这里修改文本编辑框的值, x$ `9 ]+ c& x0 n; i) p) U! Q
hr = paccControl-&gt;put_accValue(varControl, 2 B& R9 n! g# B7 S
                                  CComBSTR("regedit"));
! c3 x( U9 p% r& r6 i6 d8 _paccControl-&gt;Release();; Q* Y4 e6 P8 T# n; c
VariantClear(&amp;varControl);# L9 e# s1 Q1 b1 x0 w! b
}* X" Y1 J' R9 n
/ y) v9 p+ H% P( o6 L* p; f
// 找到确定按钮,并执行默认动作。' h! A0 s" l4 i2 x5 w9 u7 c
if(1 == FindChild (paccMainWindow, 0 n2 Y' D! j5 v8 F. X: C5 X2 ~4 H
                   "确定",
# S6 z; i6 D5 k- B# N: J% I2 G* M                   "按下按钮", ! V3 s5 A0 U; x
                   "Button",
* m/ m* s! M( K5 _                   &amp;paccControl,
* ~3 F/ D* e* b, s5 f- [& C                   &amp;varControl))  a7 J# E2 a  y" n! ?; J' E4 T
{( C* \1 L6 f! u* x2 J4 W
//这里执行按钮的默认动作,即"按下这个按钮"0 n1 F: E, P% ]: E
hr = paccControl-&gt;accDoDefaultAction(varControl);  n6 j+ _5 C* }# @5 V! B- u& h
paccControl-&gt;Release();
5 p% ~$ H( S6 Y1 W! }% p! T' ~VariantClear(&amp;varControl);
3 V7 W# E5 z8 X}</FONT></TD></TR></TABLE></P>6 T- F) s  S0 D+ s
<>现在,你会发现已经成功启动了注册表编辑器!!6 ?% y4 n* T0 B! |. B" N
</P>
# i# a$ W" ~- z<>五、模拟键盘和鼠标输入: F5 W* m/ L& E- f1 R
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。
/ S6 u) _$ V2 Y: U    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
" E9 w4 w: W- H( ^3 ~    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
" f( v! s  b- M  |- i  l5 V' y下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>8 @; }& W* n* @: C+ l" T
<>( S1 T6 p: T4 b0 q- ^% P- Y
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
3 b9 N7 a+ |9 N9 L8 T
- H! k5 J% Y- [4 K1 {1 X<TR>
$ r, o) w% G- L* X, [* R6 [) c<TD><FONT color=#00309c>INPUT input[4]; : x4 f4 Y+ f: g; [7 {
memset(input, 0, sizeof(input));* U6 r# p" R' \7 Y' O% d! T7 H
0 e( F" x7 G( {7 i& v. \& i4 W0 e! w4 g# J
//设置模拟键盘输入6 n& z' I( _' X
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;$ f0 S. Z7 `/ a: {
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;* N) _2 i# V2 Z  p
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;
- s5 P. M# w& s$ Y
; X8 ?3 o8 h5 w* K3 |" X* r& `// 释放按键,这非常重要
, u3 g) e0 s; ]% _input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
$ Y$ y4 L. e6 T, k* v4 o2 C8 r8 `1 G7 h% ~6 q+ g2 l) z* x
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>
- V3 ?; w# q) s( M: m<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:). I$ }% h  [3 s1 ]' n
</P>  L6 S8 S$ ~6 v* W5 p$ s) ?
<>六、监视WinEvents# J7 k% f3 D( g9 @3 C. K
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。
, O# b5 [# y9 Z. i    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。7 ?. U) ]5 M* J* U0 {& S7 G1 m" L
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>
9 A' s, w# M$ D$ U" z<>- A2 T' Y% X; {, @" N
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# M1 m/ _7 H; ]( o7 `8 F% d  X1 S6 T. j) m1 P; N% j
<TR>5 @3 `) v7 H; b5 d
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
$ z* _- L) O5 ?& U{  H+ M+ [. l& D+ ~! q# d8 l
hEventHook = SetWinEventHook($ O- b0 {  a$ I* p& e& W
EVENT_MIN, // eventMin ID
8 U/ U% V8 J4 A  H) uEVENT_MAX, // eventMax ID# B6 j. f6 O* ~: E& U3 K: k
NULL, // always NULL for outprocess hook
3 e* |- V* T3 a  M1 fWinCreateNotifyProc, // call back function
. ~6 m7 _. p" u0 m. S0, // idProcess  i3 d- J. g$ K/ H5 X* \
0, // idThread
% ]8 K, \* e/ L; r4 |         // always the same for outproc hook
. e# e/ Y& Z- k+ o) Z# {$ OWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);" |: `* u$ N* Y, t2 d
}  </FONT></TD></TR></TABLE></P># G$ [0 y6 K/ L7 e- C- o5 G9 D, H& n* [1 A

- x7 Z9 i1 W: l: N* @% ?. c<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
) ?( y3 _  m, q+ p% q下面是回调函数:</P>
9 }" w; m3 F9 y+ M0 {2 y* |6 C% d/ T<>
/ q5 C2 V, r+ Z; C7 _$ J<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' L$ @4 `3 X. s0 i2 E. W
* }  L6 f! d1 O" D9 Y% q
<TR>
% {4 g/ D  J0 ^# \3 \/ k8 l<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(8 n8 g* F4 m0 [- M) @7 e+ x
HWINEVENTHOOK  hEvent,
( g+ d5 D; I( U  d$ hDWORD   event,5 C0 U+ u, ^2 O  r' d
HWND    hwndMsg,
* D% e+ ?: }/ t" H  HLONG    idObject,
# X: D/ {8 G( w. o8 G, SLONG    idChild,
1 y# M1 c! [# E2 @' \DWORD   idThread,) z; J5 C) w4 y4 j0 }$ B
DWORD   dwmsEventTime0 r7 _. ]$ a& _( ]" M4 e" H
): c: M( b. D3 _( N% A
{% T7 F! Y- v2 d. c
! E9 w+ a0 Y# E$ r
if( event != EVENT_OBJECT_CREATE)5 j( d8 J  h6 w1 l; Q5 @! U
return;
7 S8 ]0 D7 r* z+ @9 m. }) C$ b8 G8 T! y0 Q& N# |& T
char bufferName[256];8 R$ i8 J: z2 @) I% R6 o* \' O
IAccessible *pacc=NULL;( m  O5 d5 Y' u! ?9 g  ]
VARIANT varChild;
! s5 E: k, r1 k+ F& L% t: C) P    VariantInit(&amp;varChild);) x( e6 g1 ?8 ?3 N' J+ W8 R
//得到触发事件的 UI 元素的 IAccessible 接口/子ID对4 T! M  _* Y; Z
HRESULT hr= AccessibleObjectFromEvent(hwndMsg,
' U1 e% _  N. R+ }( }1 Y7 \2 F                                      idObject,
. i$ ?$ R3 |+ A+ T* G                                      idChild,
0 k; Z! M8 m5 @                                      &amp;pacc,
+ W# U& v, Q2 e5 U' o5 ?% z4 D/ Z8 J                                      &amp;varChild);
; G$ {/ T6 E9 k( t* F
' c0 _% I9 ?, @5 F% bif(!SUCCEEDED(hr))9 f2 v! M. G1 h/ W1 `' b: A
{
" U9 x8 m7 d& I) S+ fVariantClear(&amp;varChild);# o' e1 ?. r# U2 C. [9 ?
return;  _3 e8 S0 o/ d6 j- O
}
* u9 u5 v2 {" C% R; E//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
* n; X; e5 y$ N+ c& b6 j% aGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));
! ]( J" F3 C% l0 I; _/ m# Nif(strstr(bufferName, szMainTitle))
0 a. f8 w; V0 H& V; K/ b- NPostThreadMessage(GetCurrentThreadId(),
: `/ y' Q7 _. B* N                  WM_TARGET_WINDOW_FOUND, / v2 W1 }) O5 z( R$ S8 W
                  0, 7 z5 R6 F' X5 o9 o
                  0);
* P! Z5 T* O5 `. w- g1 N+ S) `- r* ~9 E2 K. F9 m) I* H$ _
return;
) a$ C1 I; v) Y0 w! O( n}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
7 p* K4 @- i7 Q% B! O6 j0 h: ~# _2 u: p, f2 P/ o3 C( W# e! B) E
附录:: t7 a; c+ I$ t2 f
' ]  E' A' J  V8 Y, V
关于IAccessible 接口/子ID对:1 |6 A( N3 {4 L7 D8 j
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
/ Z7 b9 y' ~' `, U    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!2 t4 y$ r8 \2 d7 l& J' x" \
    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。* [8 o0 \6 ~  f1 g5 q9 h" w
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。4 ?! ~, U0 n9 h1 C% ?$ O; z
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!( d" D/ e1 k& [/ L6 [# G

/ W( K- e+ |) Z# G) K注:
' x0 B; [# F9 J/ q3 P! U0 Q    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)- A  {8 b# f+ u2 a! p3 s
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。* u3 O, `/ z9 W6 B& K: R8 E4 a

/ g! T8 K0 f3 V参考资料: 7 q; Q; f+ V$ R  Q" |8 i  f
<UL>9 Q" @, j! C2 o4 j
<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
4 Q5 x8 |6 Z0 l<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 15:09 , Processed in 0.588025 second(s), 58 queries .

回顶部