QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5770|回复: 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>
, G' ~2 M+ \) {# V, {( K
0 r) g% A9 ~" W6 w8 p; [& h* k<TR>
# @, ~( l$ ^, y0 I<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
! s" Y3 ?6 s. U5 M2 \原 作 者:崔传凯
2 L; H# O% L  N& \) x; {原 出 处:vckbase# _# g8 r* J8 T3 @
发 布 者:loose_went% i% C. A" C: D2 d. {
发布类型:转载
0 P0 ~' B) ~3 A发布日期:2004-11-12
  p. f5 o, j" Y- b1 W- O今日浏览:6; U# i3 R: i6 g6 z* {" D
总 浏 览:234
6 F# b0 o# \" Z  _</TD>
2 l4 P+ X0 l2 Z  \, \$ l! |% T<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>$ P) Y9 Y- j( S" {9 \  m+ ?; L
<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>4 I9 p& s& P- H8 q( `* h

3 L* M: W+ p( e, ?& Z; ^<TR>
- L& _: C, ^% l- N0 g9 s1 \<TD vAlign=top align=left bgColor=#ffffff>
( }  j% {. l  x0 y: G" f( B5 ?  T! k0 u8 O& q7 B: h5 `
<>
" R! l; Q! T( m8 o* @3 {<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" J0 l% a5 `8 P  i5 ~. _9 B
6 B, {  e# [+ y! H' y<TR>/ Q8 a+ F( 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
+ c8 {* S. l1 a* b1 Qway accessibility aids work with applications running on Microsoft Windows?. It 3 y6 F! s" }% y
provides dynamic-link libraries that are incorporated into the operating system 9 J% r) j6 A* {4 F5 u, f5 b0 q
as well as a COM interface and application programming elements that provide
8 {) Q9 A- u3 Vreliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>4 L: z- D! t: O4 M% W, F
<>一、基础
& u8 T0 C* v7 R: j) Y    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
1 I7 B! e# H. T3 J: |% g* D1 B/ ]    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。& s6 s2 o4 i# r- _5 s
    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
2 @' ~/ y5 |0 q+ T2 `<>二、Active Accessibility 原理. J9 z& y5 K" v) ^) ]! N
    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。& s" U1 f  k: ?% L* p+ L2 E
    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
& d) L, C7 X/ \+ c- f0 J9 O    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。8 G  U* s. E; K4 w
    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。- T% S% v( U3 {  b- m6 d( H  ^, v
    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>. ]. C* [  V$ X6 T
<>三、如何得到 IAccessible 接口指针( Z, C  C8 a& J
    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。" O$ Y. z+ L$ d+ e3 ~
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
# |: ?+ h5 I& g) V9 ^( A; E, B6 |" C    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。/ N# |2 V. x0 {/ ^+ q
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。6 f9 Z$ b' P; a$ T7 \7 w
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
; R9 t; L6 i# l# ^8 y3 ]6 H    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。/ c: v- z, P& f" @* Q. Q
下面结合代码介绍一下它的用法。0 C! J8 x4 _- W" o3 b
我们来得到下面运行窗口的 IAccessible 接口指针。
" c$ m7 P6 K3 {8 G% O5 g2 s' j% E5 }) V4 a9 |! I3 C' d- T, L5 A/ s
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>
5 K- [, O1 c$ R# h$ u$ L<>图一</P>  j! h1 w( u+ Q* \/ |9 c3 t$ v
<>
4 K( t; {) Y' G3 m* k6 D! A* T<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
. d+ K- N0 t/ v& j! i" [" Z7 y/ Q! b  N
<TR>1 R" K) F% R- B$ X) e
<TD><FONT color=#00309c>HWND hWndMainWindow;
$ c8 c- n' g* c# F4 r% h; r" dIAccessible *paccMainWindow = NULL;1 _5 r' u9 D* o
HRESULT hr;
  Y& x. w* W) A( h" n$ Z//得到标题为"运行"的窗口的句柄3 l) i. h$ ^" V9 S! @+ \" F
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))& r, r* U! W9 O/ B/ Q
{
& \! w1 E" l% W+ K0 y4 H9 EMessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
4 T. |& y4 F- M6 Z}
$ q1 \3 ]8 [: ]9 belse% s6 t8 A! b" [; c3 S$ ~  k
{
$ L9 j! ]7 H7 N, X! Q2 M8 ]5 L- b//通过窗口句柄得到窗口的 IAccessible 接口指针。
- s% K5 Y# p5 Xif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, * F3 \7 i1 L+ a( U
                                            OBJID_WINDOW,
! \/ C* ]5 B% j  ]! |% f* C# I                                            IID_IAccessible,, ?) ?# R: N8 q, G% }% }
                                            (void**)&amp;paccMainWindow)))
* u! b9 f* g$ m4 U) J! I' g{: T' r6 u) m" Z' ]# J# W) ~3 |/ j, M
//……我们可以通过这个指针paccMainWindow进行操作。" J6 |7 s) @6 R8 |- F  C
paccMainWindow-&gt;Release();- ]4 n2 M0 r5 [
        }
; q4 p! b1 G9 _% ]2 N; x3 C}    </FONT></TD></TR></TABLE></P>
1 a5 J0 k$ I/ Z6 e4 s# q! v' ^<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!. S  i5 k! t* R6 y; N. f6 D9 Y
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……, d7 b3 {' ~" Y/ `+ U2 i7 G
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
4 W0 g" X- d) V. `% B<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>, x/ G7 e6 O! X' A7 Q9 M, t
<>图二</P>/ r3 U3 R( C, Z8 r( y0 `# @
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
, ^4 s! o- q/ a9 D2 u% T7 p<>
( K0 ]! R& N+ b<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>+ p: D9 _9 B& C
( N+ j6 }9 M7 [  \- _
<TR>
, B/ r$ G# w) Q, B8 o. l0 Y<TD><FONT color=#00309c>Name = "打开(O):"
" p/ g. w$ _! Q9 ~6 |Role = "可编辑文字"
; g: e7 N. ~3 B. X' q$ b( b2 }1 gWindow className = "Edit"  </FONT></TD></TR></TABLE></P>4 y) X  ?5 U; A' l
<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。
8 V. F2 J. X0 H# m+ E0 ~$ A/ y) t* ]) d    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。, t5 Q/ o  V/ q1 b. p9 G" l& z: m
下面我们开始取文本输入框的 IAccessible 接口指针。 </P>1 V, ?9 W9 I, m1 @
<>; O0 A( I& ?' X0 u2 `
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
# @+ S$ U1 y* E) Q9 X! l8 u, |0 Z& E# p: K% y+ L
<TR>/ w+ w; ]1 c' U  U* \
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
1 E' [+ {1 o& N3 E% BVARIANT varControl;    //子ID。
: Z; x. U4 L' j! x7 X2 }4 \6 B4 Y6 ~9 ?" ?' @; e, e! W6 C- V3 j& ^9 M
FindChild( paccMainWindow, 9 u' P2 e2 n! [" c' s8 |+ @
           "打开(O):", 0 E1 i5 X; ?8 J) P: B. }4 r7 ]# o
           "可编辑文字",
- z0 \! r( J- c4 k8 L7 g- Z+ \           "Edit",
/ l" v' m0 U5 x0 v           &amp;paccControl,
4 h( @& H( ~5 l# V! X           &amp;varControl ) </FONT></TD></TR></TABLE></P>
% x8 I' {  K2 I' ?0 P% D<>第一个参数是先前得到的窗口 IAccessible 接口指针。' U& p% X* N1 F
第二、三、四个参数分别是名字、角色、类。5 \0 x* a  ?/ F2 @& ~
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>
6 s1 L' a+ L/ Q2 W, ^<>
* ?2 V8 j6 H' c* Z% b( j9 i<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>! P( B( |- [! u2 y# j
# Z* R2 t: f$ ^/ n/ c
<TR>
) n. D. V4 g$ H! {) q<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent,
6 B' e5 u' [+ }" ~3 X                         LPSTR szName, LPSTR szRole,
8 _+ W) V# l# G/ B+ Z7 ]                         LPSTR szClass, ( k- L8 u0 ]/ \# a3 y2 l
                         IAccessible** paccChild, 9 M+ J) I$ j3 b9 x0 @2 O2 N5 x
                         VARIANT* pvarChild)( [; {! M- ~4 {; ?! Z
{
# r+ H$ U& y7 mHRESULT hr;
/ b1 O- O3 m3 f6 l# v; D" Wlong numChildren;
+ Y; Z0 J- c; M$ ?# r/ runsigned long numFetched;
, Z0 n- X5 R$ CVARIANT varChild;
# Z4 s; S: i. Qint index;; }: F, V) ?; r+ I7 w
IAccessible* pCAcc = NULL;
/ L; m# g4 ^2 |0 z+ _IEnumVARIANT* pEnum = NULL;5 I3 _% i. ~8 q) C/ H; K7 Q0 c/ G
IDispatch* pDisp = NULL;5 L; V# q5 y, a8 p1 K
BOOL found = false;
7 M' X. |/ x1 c5 y# `1 uchar szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
4 V- p- a$ d3 w
) W1 X) u8 @  ?//得到父亲支持的IEnumVARIANT接口% n3 S# S0 C5 x. a6 D# j
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);! Y+ P( |0 R: G8 [) v
6 }4 t3 V5 }# E
if(pEnum)* Z. \  ^% ]  K  L/ k8 F8 ^2 C
pEnum -&gt; Reset();9 y4 s# y$ z+ u7 f! \! K

! |1 V' z9 H/ {; Y9 V//取得父亲拥有的可访问的子的数目! N, z! @# z! g
paccParent -&gt; get_accChildCount(&amp;numChildren);3 \( b6 S( W( M3 N. L

# o& J. a2 A) g. j1 i$ _% b: I9 u& A//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
& D- U# \+ T, _for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
$ U  M+ x6 l3 f5 N6 u$ U9 F{& B) K$ w! I: |4 E
pCAcc = NULL;
3 S, r5 A0 U$ M6 _+ x( t1 l2 q0 d, s// 如果支持IEnumVARIANT接口,得到下一个子ID
( \* f& m& B# }* Y, s6 U; j//以及其对应的 IDispatch 接口  Q/ q9 n/ `9 C% K. A  M
if (pEnum)
0 w$ o0 p  ]" z1 j( p1 w: ~hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); 7 u# f# @/ n' F0 ?6 X
else
" F1 ~( Q  A* d- }  V" A{  p# z, S3 t3 @
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号3 d, V6 L: g* P4 q% S1 c) J
varChild.vt = VT_I4;
7 J4 [) ^5 q! \6 c* R: N! CvarChild.lVal = index;5 b! Q$ S& T& j$ t  \! k
}6 V4 E: h" ~$ Y3 w$ x. f. E7 R

( N) n- E9 f' a4 D8 C( E* q- _% @// 找到此子ID对应的 IDispatch 接口
, C8 Z4 b! N- [4 ]0 _8 |. `if (varChild.vt == VT_I4)$ P% a' @, d" ~( z% }6 l+ L3 S
{4 h" C" Z! E5 F8 i
//通过子ID序号得到对应的 IDispatch 接口/ X. D$ `4 C+ }; Q' P* d0 ^; `
pDisp = NULL;/ U' ^) h6 G5 [4 y$ [/ v2 O
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);2 ]0 w" w, F" {/ v9 e" e$ @" [9 @- Y# u6 V
}9 }. v! E" \: G4 {) T7 X
else
# t+ n6 |7 S9 A$ x6 E//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
  m# h& p4 E6 |' Y# |; [7 q% x7 c( ppDisp = varChild.pdispVal;
. k. Y  L/ W, W/ B
9 Z1 q# R1 Q' L! C// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
+ V7 X4 X1 F. G2 c$ e6 W% d& Iif (pDisp)
2 U' n; z. D& h1 p% ~& m{
9 Z7 ?: B0 x$ G+ ?hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);
: t1 \% v6 s8 {- Ghr = pDisp-&gt;Release();
1 p, J+ ~6 U5 I5 p7 j}
. w* M( q  C' H' v
' Z  `2 [* h* i# m  c// Get information about the child
$ J: m5 e' z' F/ ^if(pCAcc)* h/ x- d* @) y% C1 h
{2 U$ U% j- W: G2 G9 B
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF8 a7 V& }7 Z1 e% M1 ~
VariantInit(&amp;varChild);
, q/ f) }* b% S8 _, L, p+ evarChild.vt = VT_I4;
8 c% Z) ]3 Q- R4 nvarChild.lVal = CHILDID_SELF;
  v# r3 V% ?/ K2 D3 n! `8 r$ \; U- f# B
*paccChild = pCAcc;
  q/ u- R3 m  q- M# H}8 E2 G. q5 G" l3 c% x
else) V6 _7 g* D  t' {) P
//如果子不支持IAccessible 接口
" i/ m, S/ U1 J& l: c/ o! h*paccChild = paccParent;
, E! {0 e9 ^7 W1 J: f2 |
" {" k8 a: o) w* d4 M- U. h//跳过了有不可访问状态的元素# y* Q0 i/ r3 n# M$ W! P
GetObjectState(*paccChild, 3 Q, y5 H# [# _1 z
               &amp;varChild,
/ N3 e& K, @/ u$ r+ S; Z               szObjState, ' H0 N& j9 Z* j0 X3 Z3 ^
               sizeof(szObjState));
7 L( b+ n2 N+ ~if(NULL != strstr(szObjState, "unavailable"))
9 p. Y# i6 X4 R  v$ b{
- t' _  Y4 j4 A" ?! X; iif(pCAcc)
$ _! d0 C& }- Y  v( U# H3 dpCAcc-&gt;Release();
9 M4 P! G$ n- R& l: E3 m  q) Ucontinue;. V8 e0 Q2 b; r* I( z7 m
}
9 e. Y: k1 G% Z" P3 t. ]% v//通过get_accName得到Name
1 m% u6 S9 ~4 A, c: g( mGetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));
! r3 p3 K* S, s2 U) O* E0 t//通过get_accRole得到Role) f' \( m9 G; i! v4 v4 \- M3 O
GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));: b7 O: q# j( m2 c
//通过WindowFromAccessibleObject和GetClassName得到Class6 y- A. p1 y0 D" z5 q
GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));2 G3 K$ z7 k9 _/ j$ X$ a% C: {
//以上实现代码比较简单,大家自己看代码吧。0 A0 U3 ^% M0 w, Q% \3 {

1 C6 d, L4 r' y//如果这些参数与输入相符或输入为NULL
" E9 o3 n  K5 T1 L. K0 U. |if ((!szName ||
6 s" P3 ]1 \/ X  B; N* |0 h     !strcmp(szName, szObjName)) &amp;&amp;
8 T7 c( T1 n% X4 D* P     (!szRole || . S) T) p2 L+ M1 s9 |& K+ ~
      !strcmp(szRole, szObjRole)) &amp;&amp; 7 i! r; p5 v+ f4 m  y9 L
     (!szClass || 8 h0 {" N  b  ^! ]: U1 |( h$ l! I
      !strcmp(szClass, szObjClass)))' V+ F  S+ x$ o4 d7 m: `/ D
{' ?1 ?1 N( {- J" [% }# U( l
found = true;. M6 d! }  @# ]- Z
*pvarChild = varChild;- \. Y7 W' `6 ~6 r/ N5 L: S
break;
0 f/ Z7 {5 W& ]) W7 x. M}
2 Y# L: ?: w+ Z" G% D/ r+ [# |! D7 H6 Gif(!found &amp;&amp; pCAcc)+ L7 V" }: {4 [, h, g" S( E+ l
{
5 q5 k. v1 H! r( ^, |// 以这次得到的子接口为父递归调用! A% B; u" p0 F7 w3 s7 c3 n, e: \
found = FindChild(pCAcc, 0 c2 k2 X* j, I1 ^& W3 }
                  szName, 4 i+ p, E. q* _
                  szRole, 6 K8 ]0 ]0 H5 V2 @6 l- O
                  szClass,
. j* j$ F8 B. d8 |                  paccChild,
/ I$ Q3 ^; [# z# s) |5 y                  pvarChild);" g0 Q+ \: D8 ~6 l
if(*paccChild != pCAcc)
5 s) I- r: O' C4 z8 F- _6 PpCAcc-&gt;Release();" Y- I/ X  ^  Z! O: b: `
}
; }& p5 G. R; [$ D$ O5 @}//End for4 Z' V  \& v* K8 [5 J; m

. w; `( x" g/ b/ x( w' ~. V7 H// Clean up
" n5 n% N8 \  b1 a) ~7 p) c; F) s+ Lif(pEnum)! x  e% ]2 A: G: u
pEnum -&gt; Release();
% l9 H8 T! n3 R& z/ p' d( n9 @" J5 z% |) |; N
return found;1 G( R/ u3 V; U* N" U
}
$ p4 A9 S8 _$ i: u6 z. H" f! o6 i+ i. O
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,' z% O+ j4 [0 `9 `7 |
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。' p5 }/ m5 O% O8 Y
//将这些或数转换成相应的用逗号分割的状态字符串。7 A; w+ x+ s7 t; }; `: v
UINT GetObjectState(IAccessible* pacc, + H6 Q0 C$ y9 w
                    VARIANT* pvarChild, 5 U" q0 w# w9 R4 S5 H
                    LPTSTR lpszState,
0 \# @2 a3 l1 G+ B- i                    UINT cchState)( t: E3 [/ c- a, C. r
{2 T3 N, _/ A- c- P! ~
    HRESULT hr;
6 y! j  ]1 K$ u    VARIANT varRetVal;
  X. n4 e- h+ @. o7 @5 G
+ C& C) i9 S, `. G6 c    *lpszState = 0;. m4 C: |5 }4 P2 V* A4 w
6 c5 _( z& u4 M5 v, T- z
    VariantInit(&amp;varRetVal);
# u2 x0 P; ^9 G5 x: F0 o" G) M! Y" ?! n3 c% r- |3 m3 j: ~
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);
8 B0 F" v+ d9 D$ r& k# Q- [+ Q4 \7 n1 g2 w
if (!SUCCEEDED(hr))* ^8 Q9 B% Z7 [# r# m  t
        return(0);
" Q) U; R% X6 y& R) P% M
* a6 p2 X7 J4 ?& S5 _  X  T* ^DWORD dwStateBit;
3 s+ P4 N% z3 c, o+ X7 ]int cChars = 0;3 G: p: U3 }3 q1 E( D: J" g
    if (varRetVal.vt == VT_I4)6 M, `4 T" d4 f6 l& n
{
+ e% j3 s* F2 j4 n. }// 根据返回的状态值生成以逗号连接的字符串。
, h9 y3 w; ~) D        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; 2 T3 f% W! Y$ R- z  _
               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH; 4 ]6 u( s/ \, O
               dwStateBit &lt;&lt;= 1)8 [9 y' K5 D2 Q2 ]+ X
        {
' L5 t/ p! u2 e! O            if (varRetVal.lVal &amp; dwStateBit)8 q6 R; `+ G( u/ Z, I3 X
            {
/ ^  {. ]( I! J! g4 T# W, Z                cChars += GetStateText(dwStateBit,
2 S. }. I1 D1 [3 @, |, L                                       lpszState + cChars,
+ H8 U0 V! `# s7 F( i! |' A                                       cchState - cChars);+ g5 J! S9 O/ h3 n
*(lpszState + cChars++) = '','';! s; n8 _/ P* G8 A' B; Z
            }% R2 `* G2 |9 D  J, i' m
        }" p  m/ d" t+ u0 m" r
if(cChars &gt; 1)9 v; w6 w: t8 z4 d7 b/ s" K9 Z# z
*(lpszState + cChars - 1) = ''\0'';5 C2 J$ s! P! J. x# r  P: ]- E. Q9 U
    }  ~; y0 A0 |7 l+ @+ X+ h# g) R$ T
    else if (varRetVal.vt == VT_BSTR)
, u" y+ ?& r& I! Z. l3 N    {! s9 e! ^2 ?+ b, ?2 ~5 D
        WideCharToMultiByte(CP_ACP, # E( d3 p& `- _, _! C
                            0,
5 u- k# n  q2 C6 ~1 B                            varRetVal.bstrVal,
  E+ ~( P: u# ^1 @0 I. m                            -1,
8 P0 }; `6 a" x! |8 M                            lpszState,0 _2 o2 [* x- _* U
                            cchState,
1 v* P. A; B& f, P. y! f+ I2 X                            NULL, 9 j# M  ^5 j" r; h$ {2 L4 @
                            NULL);* b* ?- }3 {  V; l
    }
  v8 E; A. y" ]3 `. R7 Z0 J
8 U: N  h6 R5 [! ]8 p" [    VariantClear(&amp;varRetVal);6 S9 }8 J5 Z+ B+ ?* U: T
5 c+ e8 ~  o' Z+ g' n8 X
    return(lstrlen(lpszState));
  Y9 |/ Y  N0 s5 F+ m  a3 ~1 L/ P5 T! Z}</FONT></TD></TR></TABLE></P># _- A! l# B$ W
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
- ?- E4 |! m2 [; F
" V4 G& B& C7 J3 k& U/ t9 z6 J四、在 IAccessible 接口上执行动作
+ h- Z- c2 h8 h& r$ J    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
- V+ q1 w1 W' w: b$ L, T    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。  P9 P1 N& W9 w2 w# j9 Z
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>1 T* Z! @/ Z4 w( A
<>
  }2 ], A1 G6 X# Y( J. Y" d( d6 g0 f<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
% c7 E3 A* l4 U6 C3 w) P9 B. }  n
! t$ |1 _$ n( O3 K<TR>
* I  X8 l9 I+ S2 n+ U<TD><FONT color=#00309c>//在文本输入框输入"regedit"$ l6 Y  X4 w3 D
if(1 == FindChild (paccMainWindow, "打开(O):", 1 }* }) m2 z& m+ a. X
                   "可编辑文字", # p; X- G# T4 Y/ W9 c' V( L* k
                   "Edit",
+ O- R% x9 v3 ?! c                   &amp;paccControl, 0 L0 {* V7 u" |
                   &amp;varControl))0 H4 C6 ?! P* j7 O1 o5 K3 d
{( ?, x' D4 _. h& m6 l- e
//在这里修改文本编辑框的值
6 G5 L' |# B& ]  u; i) R: t  \hr = paccControl-&gt;put_accValue(varControl,
7 h0 }  `; J9 I                                  CComBSTR("regedit"));
+ n% Z; o( n* j2 L/ `3 dpaccControl-&gt;Release();
" x9 C9 O( g: r  v3 tVariantClear(&amp;varControl);
; }7 w) D% F: i; _  c: k9 ~}& c* ?3 c& P0 w# ?

( F7 o5 _. F3 q" Y/ i. G8 b// 找到确定按钮,并执行默认动作。
2 \5 r0 K" W6 N4 C* u2 i8 Uif(1 == FindChild (paccMainWindow, : r+ }7 j* C- W7 f5 X  w7 W
                   "确定", # t# l7 i' F8 B
                   "按下按钮",   I; N; m( ]( k( t- c
                   "Button", * L5 n5 U# ?  O" Q# ^5 t" Y
                   &amp;paccControl, " c$ E9 v! |6 k3 E/ s  |3 t: r& P( O
                   &amp;varControl))5 ^; n. |2 k7 [  Z5 N6 y
{
+ b1 G! j- y) y3 ^- G1 ?//这里执行按钮的默认动作,即"按下这个按钮"1 F* k# A6 _, I9 L  @! n/ o
hr = paccControl-&gt;accDoDefaultAction(varControl);
2 w- e3 r2 s$ f& U" M! j  a, ApaccControl-&gt;Release();0 `8 _' {1 G/ \; Y' d
VariantClear(&amp;varControl);8 B7 \$ j( D; R- l4 Y! P+ c
}</FONT></TD></TR></TABLE></P>  {. D& b" p: h) [$ E/ H
<>现在,你会发现已经成功启动了注册表编辑器!!
* O; ~9 {1 w( q, i* t+ w: \</P>
7 s! f/ n2 D% P! a# [, d1 ^  x! a<>五、模拟键盘和鼠标输入4 u$ t  x/ f% G7 K- H$ q! H; y
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。& H, I1 x8 ]& b- y2 u5 s+ y
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。2 k& _: l0 m( K% L4 K: `
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
& r9 ~8 e: [4 G& K3 d. ?% Q下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>
  ^) d2 W) @, S& c5 i<>
. J/ o, z1 Q) o! W# k, J4 a- v<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>5 S/ j3 p6 O) g, j5 k

9 W/ o" o/ `0 L) L- L; N2 [  c; _<TR>, a6 v2 n$ B. W* d, F; u$ f+ p9 v
<TD><FONT color=#00309c>INPUT input[4]; 8 i& q: S0 P0 V
memset(input, 0, sizeof(input));
. s2 I( i% V& O& m& a
3 {. z1 O% h$ r//设置模拟键盘输入( g. X- O" J8 {3 ~( h) _6 L" k
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;: [; p" R7 X2 ^* E2 p% C
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
, ]* u$ e! ?. v$ f- ]% D, zinput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
7 P. t8 F1 ?# F6 Q6 S
, @0 _7 ~% A; a: h// 释放按键,这非常重要- [) F+ v0 d" P
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
8 P* `/ D# n& C. s$ ^! x5 a  W- i' G  m6 u' x9 _( m$ k
SendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>. Q* n: b" o9 C+ ^4 v5 a+ s
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)+ A# M* m7 s/ o5 Y0 _# F5 ^
</P>
3 Y% ^6 f# d( A( y<>六、监视WinEvents. d, c' a6 Y# U, v; s& o, N. |
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。. {- t- ~# I& d. _' E& j1 y1 D
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
$ _7 r! ?  @$ V6 c5 F    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>1 \, s4 Z4 s6 N7 V. x+ [
<>
$ e2 Q9 {8 I, V<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* g$ @2 K  Q! i4 G8 d  K2 a* {

& G4 H9 {1 v2 i<TR>
( X  g: v' d/ G3 d<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
& p# E# q' ]9 p9 R( W8 v{
6 T: ~: i6 \8 K8 |hEventHook = SetWinEventHook(7 I3 h0 v% C6 K2 U
EVENT_MIN, // eventMin ID
% B# `: V/ `4 d# _EVENT_MAX, // eventMax ID
- c" {: \1 ?2 c) J; g( V. QNULL, // always NULL for outprocess hook
3 D$ m  `2 Z5 H* Q* K# h. vWinCreateNotifyProc, // call back function
1 `% W" @; z0 ^0, // idProcess
0 q1 p6 O; b; V1 Z% D" F0, // idThread
: b7 u( U" B$ ^. b( U" B! @; `" }2 F6 W$ j         // always the same for outproc hook
( E# O1 C# i4 U9 f  w7 L3 h  TWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
1 V. O1 b4 Y; q; r7 X}  </FONT></TD></TR></TABLE></P>+ W! k# N+ ]$ Z) H

0 u, a3 b8 X/ _5 \$ G6 \" L+ L" l<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
2 P0 p9 G$ m( h# e下面是回调函数:</P>) ?9 E- N+ Z+ i7 D- c) t
<>8 Y" V& z9 Z; H3 u8 ?0 B  D
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
$ ~: C/ O' \" Y) C7 a; `4 q- T7 J* D9 E
<TR>
; C3 N$ n5 c$ ^+ }7 O& N0 j<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(# `  G" {; e" C; i- e5 T
HWINEVENTHOOK  hEvent,6 s4 {# N7 X( y7 C( M: m; o
DWORD   event,
8 b  z# l4 `, R  q/ LHWND    hwndMsg,1 _8 A: O  b& w' [: M' \) L
LONG    idObject,) K7 P) m. x* R
LONG    idChild,
" r- E$ Q; p- Q4 Q0 C! B, dDWORD   idThread,) X. b0 P0 ~) a& `3 f* J
DWORD   dwmsEventTime! m, ~. o  _% R) F5 i
)/ X+ f. \0 i( G5 ~7 R) {: q- y
{
, Z- N$ O4 G& j' M7 q0 r2 E: b  a0 W. Q
if( event != EVENT_OBJECT_CREATE)
# A' h. K( m! X+ C  d0 {0 wreturn;: ^$ B- E! A: c6 l" E
2 C( o+ O/ X- v2 b( |
char bufferName[256];) @' t  H9 G1 w1 D: c
IAccessible *pacc=NULL;. s0 Z8 q2 \% _) X. g; M/ i
VARIANT varChild;$ s* }1 q5 d" P
    VariantInit(&amp;varChild);
- [& {/ e6 a7 y//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
) {# T0 v% E5 a! V& iHRESULT hr= AccessibleObjectFromEvent(hwndMsg,   J' m  X& D( E" r) a
                                      idObject,
+ l/ o1 L8 x( x* N                                      idChild, 9 _  ]9 q# L6 p7 g
                                      &amp;pacc,
' C6 `. X5 Q7 i                                      &amp;varChild);
2 s1 y0 ?- `* j, ^  t/ e" s
+ X) h' X8 k/ J" R8 M. N* Dif(!SUCCEEDED(hr))3 V- Z5 H3 j- v  v) \
{
. n! [' X! `+ S! @3 g9 S4 tVariantClear(&amp;varChild);
+ t2 V9 w  P8 g: [& K: s5 preturn;
% o2 T0 A4 W) G# J0 D/ c4 a}' v% c, ^% }- b, v/ e
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
# `' a% U. i0 |, i7 W. L/ O. }GetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));6 b9 H# ]9 y, Z  l" O% d
if(strstr(bufferName, szMainTitle))0 K7 F" g6 T1 n6 ]
PostThreadMessage(GetCurrentThreadId(), 1 [' O8 @8 x  N- l% u6 }
                  WM_TARGET_WINDOW_FOUND, 6 K1 v3 ~; h, A$ g+ o  P& g8 e1 D
                  0,
8 }' G* p. @1 V( R                  0);
7 X1 W3 j% \% h, Y5 _  c9 R0 I. _
. L! l1 p' ~/ c- V2 sreturn;; F7 G8 g& w: b9 l* h) V. n
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
: a2 o+ ~! U/ N0 ^' ?
  k2 P7 t$ n* Q9 @附录:
7 u* p$ o0 o8 x( A$ j. ^
8 M. ~  A$ |5 |- D2 {关于IAccessible 接口/子ID对:
/ o! B" f. P, D7 J: x3 V. U    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
+ s" k" M# A- U    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
4 c$ v( F1 ^, ~2 @7 [+ r$ O    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。& g. b2 N' h9 X9 H" \0 L4 \
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
3 P' u7 W! J, P& Y1 R9 i' @    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!2 X2 A& ^# H% x1 J# E4 H

+ z' P3 m3 t9 z7 Z注:3 _5 q. x2 H$ R# z
    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)* `: V$ C% {- r8 L2 g( H+ v" `
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。
, b" Y' \9 x) r! I2 I: a7 n& q$ E) v
参考资料: 4 o1 X8 Y8 H& [3 N! r' ~$ G
<UL>" u/ \0 s; N4 K+ W. S- I
<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 o0 U0 \$ O9 v3 w( Q
<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-9 03:04 , Processed in 2.674220 second(s), 58 queries .

回顶部