QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5766|回复: 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>& ^; w0 E7 [/ w$ Q- s) s  K: m

; Z  P0 |) N9 d) q: M, m8 M<TR>7 z& E  J5 f: K: Z- g
<TD align=left width="83%" bgColor=#efefef>文章标题:<B>MS Active Accessibility 接口技术编程尝试</B>
: ]. D; ]/ J8 P$ [6 I原 作 者:崔传凯
2 p+ O7 A9 _- A& R: M* H原 出 处:vckbase$ R. x% I: |# w; C9 [7 R
发 布 者:loose_went9 r! F0 H4 ]& Z  H8 E
发布类型:转载. a; T/ ?; a0 d# K7 q
发布日期:2004-11-12- V3 h+ v7 E+ w. _2 E# z
今日浏览:6
* y+ ]5 x( r8 a总 浏 览:234
# w6 G0 a2 `) t& R: y! N</TD>/ v* S: l# u! X" ~/ w: _& t8 e
<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>
+ ?: ~5 I8 c! [0 I" g" D; b+ ^<TABLE cellSpacing=0 cellPadding=0 width="96%" align=center border=0>% D) E6 b" t. w: N6 \
6 \/ u9 j; N! U- ?& l) O
<TR>
3 N) V. U/ r* _8 L; D<TD vAlign=top align=left bgColor=#ffffff>6 Z) k: V/ J9 f9 q# d3 C9 I) J" y
% T  ?  ~. Q' B' ~
<>. I, K; C  v7 g9 w& z
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>6 I/ d8 W8 q% E: N+ F

; s  H( {1 B' |2 p' o8 U' S<TR>/ P9 P8 Y2 c) x( e, B  k
<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 k, ~. R" i: A
way accessibility aids work with applications running on Microsoft Windows?. It
- ^, `0 J0 |5 |# Kprovides dynamic-link libraries that are incorporated into the operating system
6 r. V8 s8 E8 Has well as a COM interface and application programming elements that provide
6 P+ L; a. \( j5 R. r, O5 Ereliable methods for exposing information about user interface elements.      </FONT></TD></TR></TABLE></P>- i9 ?/ m+ ?! R2 s  n2 E  W. y$ @
<>一、基础
( q" X6 ~# E' t  T0 D; r    Microsoft<FONT face="Times New Roman">&copy;</FONT> Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
% N* O! B) l4 t4 J    Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。
# t. C/ f0 y  y% e1 v    每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。</P>
& d3 _+ d" l9 E0 c, j<>二、Active Accessibility 原理
  _' j# B) ~3 M* R' g    Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。
: c' M- H9 x5 M+ b, `* z    当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
: `0 ~/ {* u6 q    内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。
  ?: [6 }7 G- v$ ~+ q8 S    如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
3 l' P( g+ |. u8 {- U$ Q& F0 b    Active Accessibility 名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。</P>; G/ |1 L4 K$ K+ `
<>三、如何得到 IAccessible 接口指针
4 c) Z% c- D) c$ d( U$ f* Z    每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。& O1 O) ?' q& ?" j
    有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
$ u' A/ y' }, l8 {+ w: T    IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。; Z( j8 h# Z% Z9 B
    Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。; r8 A  N& }7 L% ?0 G
    好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。
. a  K  n) `! H    因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。2 O& m  m  K! h
下面结合代码介绍一下它的用法。
# C: ?9 l) K5 C7 E" q9 V% [我们来得到下面运行窗口的 IAccessible 接口指针。. k7 c! N! C' E. y9 ?4 f; @3 z& }
7 q, \1 ]  {" Q' B4 E9 @/ K
<IMG src="http://www.vczx.com/article/img/20041112092538_vczx_msaa_actaccblty1.gif" align=baseline border=0></P>+ i# b& w' N2 J; A* K
<>图一</P>
4 E  D  Z3 o, N7 v- c<>
1 s& z0 p. n3 g2 A$ {<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
" ~3 v- o6 h1 ^9 b- o1 E* w$ z$ v' C) q2 Q7 y. f5 `2 `" g
<TR>
7 R- z( Q8 I# Y* V7 {% n7 Z<TD><FONT color=#00309c>HWND hWndMainWindow;
; N! w8 g* ?( c' UIAccessible *paccMainWindow = NULL;  e8 f# _. B/ m/ L( I! L, {
HRESULT hr;
/ _$ C( g- i" y//得到标题为"运行"的窗口的句柄
* r: V6 x0 ]" a  Z& f1 C0 Fif(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
* e& j- v  S+ e+ _{5 m  e( j6 B( J( U7 [0 l: G5 b! W' E3 I
MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
" l2 H+ `, r/ P5 b}9 I, k) p7 I6 @: I9 X
else# z. k. X8 }9 P# E0 K2 Q% D
{
( b: c" m5 `) l//通过窗口句柄得到窗口的 IAccessible 接口指针。
" G# ]. \: G+ ^  t5 Q5 Uif(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
3 A2 o9 ^6 W! }/ h2 R                                            OBJID_WINDOW,
5 ?, {1 k$ t$ t. r                                            IID_IAccessible,# x& i3 J& |/ U7 F8 z; g
                                            (void**)&amp;paccMainWindow)))8 w2 \) h* {1 A  l. ^
{' \% O, a: B0 E
//……我们可以通过这个指针paccMainWindow进行操作。
9 I& }3 n. x9 [! ~& ipaccMainWindow-&gt;Release();
; b0 q( j# G5 _0 i! u        }# u& N0 R) W+ s; Z' u
}    </FONT></TD></TR></TABLE></P>
7 [( Q4 S4 s- S, I; L# g# B0 w8 M9 X0 D<>   现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!
# L! L9 S5 _( Z4 C+ u9 M    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……
; Y2 y) V6 v7 Y8 u: W4 K. G, N    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:</P>
$ w$ D( G; l* j  T, r7 o  K  Q<><IMG src="http://www.vczx.com/article/img/20041112092648_vczx_msaa_actaccblty2.gif" align=baseline border=0></P>
6 d% `6 _- P. ]3 p* k<>图二</P>/ E& K- ~  P$ f" r7 |
<>我们现在主要关注的信息是:Name、Role、Window className。</P>
# s. w6 Q3 b6 T4 x0 j8 R% z<>$ K+ j6 \$ j/ a5 s2 Y+ n0 G
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>5 [8 Y' [/ @. S5 n
7 t& j. n7 c$ r0 A! ?# M
<TR>
* n# v3 l( E6 ?6 e/ [# T<TD><FONT color=#00309c>Name = "打开(O):"
( [0 f0 W  y1 FRole = "可编辑文字"" `; W5 }, U1 B5 G
Window className = "Edit"  </FONT></TD></TR></TABLE></P>
4 }- j5 u" I* C<>    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。% m* ^7 U  F+ W6 B3 I% y9 H( H# b
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
# X# @. |! V. A, e5 t) ?, k8 Z下面我们开始取文本输入框的 IAccessible 接口指针。 </P>
& d0 {2 g& M/ J$ Y3 ~& d<>/ f' F1 z6 o/ ], j
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
4 l# g/ j8 F$ ~; m* x- W7 L9 C
' S% g# v! r; V5 t- _5 x) n) c<TR>9 K, i& E0 Z: d1 i$ C3 Q
<TD><FONT color=#00309c>IAccessible* paccControl = NULL;//输入框的 IAccessible 接口
" [" E4 m" C/ sVARIANT varControl;    //子ID。
* |8 p9 L0 f! g+ z+ X. K3 U1 V
1 g! s% W, j; u& W) D1 lFindChild( paccMainWindow, : M6 u2 V+ c& L
           "打开(O):", 1 ?. K6 y' R' e  D" c
           "可编辑文字", 2 T3 g0 P6 Q* Z6 E1 \
           "Edit", 7 [$ N/ N& [) L( N% M
           &amp;paccControl, * ?. j; ~4 d1 m8 Q# H
           &amp;varControl ) </FONT></TD></TR></TABLE></P>
8 D* X$ y0 V0 j# C$ g7 R/ l- E<>第一个参数是先前得到的窗口 IAccessible 接口指针。
- [$ M: q, E9 P6 |- I: S第二、三、四个参数分别是名字、角色、类。! p3 w* }- z7 ?/ X. f
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。 </P>+ v, |; y8 D( W" j7 b- D  O1 ^: I
<>; C* W2 W) I$ x% l7 Z( n9 y  ^
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>* p) [& p' Y. B: z9 V6 `4 K
$ c3 l, x, Y. H9 E; J0 S' Y% \
<TR>
/ j" W& ~, J/ C+ W  a<TD><FONT color=#00309c>BOOL FindChild (IAccessible* paccParent, 6 d& F, F6 B7 }
                         LPSTR szName, LPSTR szRole,
$ G2 Q6 `. {5 a- ^                         LPSTR szClass, ' M& V! f6 P- B! {  `# m9 e
                         IAccessible** paccChild, % _1 _; Q+ \, n9 A: d0 k; ]
                         VARIANT* pvarChild)* j- U9 S6 ~. L- z' X
{' g9 S& j. g9 j* @
HRESULT hr;
$ |* V, Y3 f6 n6 c- U7 Clong numChildren;: M+ b) s0 T8 L3 y- n3 V* H& C: D$ i
unsigned long numFetched;( @2 r: H8 j; J
VARIANT varChild;% a* R5 g& b& Z: l( a1 x
int index;3 T" F8 L2 m. ?/ U
IAccessible* pCAcc = NULL;8 s# T  B8 k. b* c0 C- t7 T
IEnumVARIANT* pEnum = NULL;. g' q  o7 o' e7 X# ~
IDispatch* pDisp = NULL;9 Z2 S( R9 |. F" m. c( Z. g) c& ?
BOOL found = false;& b# \; r5 l) Z" }0 y
char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];& w5 ?+ E1 Z# B( ?* D

; P" p' Q9 w% Q1 g" v9 N  P//得到父亲支持的IEnumVARIANT接口3 J( ~& W# n) C0 x2 T% m. F
hr = paccParent -&gt; QueryInterface(IID_IEnumVARIANT, (PVOID*) &amp; pEnum);
3 K7 f, T1 {4 e4 ?$ W
% M# K5 R/ t4 [if(pEnum)
. U: {2 v: u7 v2 u& k9 ypEnum -&gt; Reset();
! x! c+ F' x& _# X! \  a" L- O  D* a  K
//取得父亲拥有的可访问的子的数目
+ B! f- Y+ P& B* d! W% z) EpaccParent -&gt; get_accChildCount(&amp;numChildren);
; r# w! m6 B7 r  p! I3 S) p0 n/ T& a* r3 H+ O, K; J8 D1 Z* l
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。' z4 P9 V4 q' p" Z5 @
for(index = 1; index &lt;= numChildren &amp;&amp; !found; index++)
) M+ u+ v- B1 }! |% d: K{
# t2 D! q& N: w+ \$ r* j: h7 g) C9 ipCAcc = NULL;
& d3 Z- N' {( S0 H4 ~( `: h; F// 如果支持IEnumVARIANT接口,得到下一个子ID) H* N4 d1 U$ N. ^' b# @
//以及其对应的 IDispatch 接口
% V: X6 E& d. x9 eif (pEnum)/ g3 L; c" G( ?! r/ @- _2 r
hr = pEnum -&gt; Next(1, &amp;varChild, &amp;numFetched); 7 V3 Y# y2 U. n5 _2 t- t2 u
else& c& t9 ~- M4 X. y: z8 A! s
{- D; C" M& X  E+ h) E
//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
7 A& Y0 Q# o, E8 I7 pvarChild.vt = VT_I4;7 s$ ]. y3 g6 K) [1 b! ?3 R0 T
varChild.lVal = index;, w' y/ W$ x# [* c& n
}
+ {4 S! A7 P4 g$ ], j- E' U; g2 d- C# x1 l' I7 U
// 找到此子ID对应的 IDispatch 接口
. T3 H& H8 i2 C' T, y: |if (varChild.vt == VT_I4)* S' y5 e; x; Z
{
! P3 a3 o) I* e% N//通过子ID序号得到对应的 IDispatch 接口' x! h5 O/ c- e- a5 v1 e
pDisp = NULL;4 S4 Y. [+ m; k, G8 s3 h+ t
hr = paccParent -&gt; get_accChild(varChild, &amp;pDisp);
" s: M$ n! P5 n7 j' l3 U8 @6 Q- H}
/ n' @- o% }: ^( X0 H, qelse
$ v0 C; _% u- G0 W4 v4 l//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
* i: O5 }! }: {9 t; SpDisp = varChild.pdispVal;
  ~5 Q  ^1 T$ I. b8 F
' F0 g) M& u# i" z4 C( G// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc: @% |9 C6 s- K/ w" u
if (pDisp)
  V$ T6 M  [; i7 f( {/ `+ ?1 O{& g  V- c6 O- X
hr = pDisp-&gt;QueryInterface(IID_IAccessible, (void**)&amp;pCAcc);" e" ]  S4 h. J
hr = pDisp-&gt;Release();
$ o9 ^* v; P8 x' h}+ c# c$ @5 t+ @* b! ~

3 ?! U0 X7 d. g) `// Get information about the child! d2 ~/ E2 D% Q: d$ L
if(pCAcc)$ v' }8 U! q$ I' B% r
{0 k! X5 B0 ?% b, U
//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF& y  b" h# ~" y8 T  C
VariantInit(&amp;varChild);  P3 d7 r2 B  Q9 U
varChild.vt = VT_I4;# K/ _* L  i( m( j# }( h5 G7 b
varChild.lVal = CHILDID_SELF;: r3 g4 b2 x0 c4 b
6 _8 p) I& h$ W& @6 [. H# R
*paccChild = pCAcc;
+ d1 `% U8 C0 r7 j+ f( \4 `) `}
4 N( z) s8 o2 l! J; _else( f# F% H/ P  C; d) B1 f1 O
//如果子不支持IAccessible 接口! i4 [+ z; w+ P4 V3 \
*paccChild = paccParent;* G+ v1 P( J% j0 H8 r8 q' O
* e+ f7 n* d/ h8 n
//跳过了有不可访问状态的元素
$ V# a6 Y; ?! ^( t( [GetObjectState(*paccChild, 9 v  Y/ _% @2 v# |* t* e
               &amp;varChild,
6 B  g/ l5 X, l* L7 F8 x8 {               szObjState, ' Y( O# M* t$ n6 d- u0 c
               sizeof(szObjState));
+ d* o4 ~+ @; j" T9 cif(NULL != strstr(szObjState, "unavailable"))
" _) R$ a8 A* k$ s# i$ ^7 k' l: u# @{3 a+ z' {. e/ t; G6 u
if(pCAcc)# L' A+ r6 U4 R
pCAcc-&gt;Release();
% u. Z8 M4 ~+ L4 \( Jcontinue;
3 t/ P2 ?; A, @/ |9 K8 d}
) @; X. L1 q6 y% v5 j( q+ n//通过get_accName得到Name! W" x! I9 z" o( B; z
GetObjectName(*paccChild, &amp;varChild, szObjName, sizeof(szObjName));# U- {9 B; h" a$ n- p  S# \
//通过get_accRole得到Role
; L" t( r3 _( }! e, M; L, x( A8 f2 @GetObjectRole(*paccChild, &amp;varChild, szObjRole, sizeof(szObjRole));
# A& o8 a9 ], O9 Z5 X% `# a//通过WindowFromAccessibleObject和GetClassName得到Class
% S) [/ J, Y, [GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
! \6 p! f! k) N. }: k$ G//以上实现代码比较简单,大家自己看代码吧。, b7 K9 b6 J, N8 ]3 s" s

1 ?) d% m5 E0 R* k& l( b( L4 T//如果这些参数与输入相符或输入为NULL
1 z+ g% }$ W( H$ p6 d* Qif ((!szName ||
4 H5 L) H3 w& n# g2 t0 R     !strcmp(szName, szObjName)) &amp;&amp; 1 z% {" c& W2 x
     (!szRole || : n  M& A3 F6 R2 Z  J
      !strcmp(szRole, szObjRole)) &amp;&amp; 7 z$ U# |; Y  Z
     (!szClass ||
8 k% Q) l( S: b4 X9 v$ D1 I8 m      !strcmp(szClass, szObjClass)))! D& s7 P7 R# `# c3 o9 @9 T
{
( F: A% R* b0 _* {* X6 r2 Ffound = true;" Z& V4 x6 P: w! h
*pvarChild = varChild;
( t8 |3 R" {2 ]# J6 X& x- U: s4 N6 Mbreak;
8 C0 ]9 V! a( P( f! t}8 Z: ~% r9 _% u' u+ v
if(!found &amp;&amp; pCAcc)
) V! x) N0 I$ y& B7 ]$ @{
. K1 d8 [6 Y& ?5 U* y4 s// 以这次得到的子接口为父递归调用( F+ |7 X/ F2 M2 A8 R7 J* p7 r$ j
found = FindChild(pCAcc, & m, u, h1 P' `2 }
                  szName, 2 U$ }, ]8 Y4 V; n. p! T
                  szRole,
. B# ]. m! u) [6 M                  szClass, ( J" m4 S5 f3 L" o1 n' ]! B
                  paccChild, ! e- l3 I! G# F1 D
                  pvarChild);
9 u/ x+ l2 U9 D8 R4 c3 Bif(*paccChild != pCAcc)
8 X* g9 J! V1 ^' z- z: t( `. b) ~pCAcc-&gt;Release();
* f# t% Y. e+ v, Y}# G- c! A8 ]" r: \0 Q. B# R; [
}//End for
6 x; {; K: [, X% N1 F1 r
6 M! z6 ^8 N+ s& i0 l// Clean up- ]" e3 L9 D! F
if(pEnum)% c8 }& q' U- H9 d% n. ^
pEnum -&gt; Release();
; I2 g3 \$ ^* c2 e1 I/ q
* N* g, k' B0 areturn found;
" ^- Q/ N3 E( B, V4 l7 x. z}
5 [( z! P8 {, d' _; P; O) E; U2 j$ N  G6 J% [( R% k, J
// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
% v( v$ D# \7 |* m" q# ]//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。$ V, A# D$ R8 j- f/ P7 d
//将这些或数转换成相应的用逗号分割的状态字符串。
- u- G- s( h* k3 `4 Z: bUINT GetObjectState(IAccessible* pacc, ' M6 t6 |. ^/ g* s* M
                    VARIANT* pvarChild, : _; V1 \2 m" c2 I
                    LPTSTR lpszState,
9 X  G2 T; ^" o. j- O                    UINT cchState)
7 k% W( I8 n) d1 h! Z( p7 t{
' V1 d- s) \# M8 |- }# @4 m6 |3 O    HRESULT hr;5 e/ r7 y8 N9 n0 t7 f1 `$ l
    VARIANT varRetVal;
, W. T) K, W$ y8 l, T+ _: ]* h1 O+ `& S2 I- P- I, j
    *lpszState = 0;7 m! Z* ^" \, \0 Y9 h7 B

3 w5 O1 g2 Q0 q3 x6 F' Q- y$ D    VariantInit(&amp;varRetVal);3 T2 y0 e  A% `- A1 J+ J
) s6 J+ k7 z8 m* q
    hr = pacc-&gt;get_accState(*pvarChild, &amp;varRetVal);6 l2 `( W  _7 k2 g  ]+ L

- j' K+ B5 L' yif (!SUCCEEDED(hr))# N$ M3 T6 F$ {5 }+ J% \
        return(0);! u/ s. Y7 c/ @6 w
9 t* j0 D/ Z2 O* t, t0 k2 {
DWORD dwStateBit;
; L: S  a6 m; Y0 }& @% [: l9 Dint cChars = 0;4 |% D4 F6 l9 c* h: z4 M! S5 A
    if (varRetVal.vt == VT_I4)
3 j8 u' d1 t" {# B3 _9 H{! b4 b9 z7 w: C& Y- f
// 根据返回的状态值生成以逗号连接的字符串。
8 K# K5 i3 }. R4 H( `6 S        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE;
0 {1 l2 m1 Y2 b! U3 V: S/ `# u               dwStateBit &lt; STATE_SYSTEM_ALERT_HIGH;
4 D, K, g' h: B" C               dwStateBit &lt;&lt;= 1)
' c0 }2 g# M: k4 t/ i$ D5 p        {* k$ `( k1 P7 t  E; C9 J4 I& L
            if (varRetVal.lVal &amp; dwStateBit)
) |9 |% ?& A0 P: u2 [7 a            {& R& j) E+ t- h/ w1 \9 u/ j
                cChars += GetStateText(dwStateBit,
7 Y% @; @3 M+ |  o$ m3 }                                       lpszState + cChars, , h$ a: Y2 q2 e: b* F
                                       cchState - cChars);$ u+ w: T! n0 U3 K$ a
*(lpszState + cChars++) = '','';7 w+ M: o6 f6 T- Y4 R
            }! C6 C. t7 ^: S7 j9 {9 d
        }# c' m3 p# q4 k/ D* Q
if(cChars &gt; 1)
0 O; h( m# p! j0 V, Z6 i*(lpszState + cChars - 1) = ''\0'';& E6 f7 [5 ^& C6 A$ ^
    }" D) G, y9 h) |) N9 W7 B- ?. x! {! k% A9 L6 B
    else if (varRetVal.vt == VT_BSTR)
+ w" F/ h1 ?4 Q! b4 r$ a6 f  W    {
5 E$ @: B. q( ^( h        WideCharToMultiByte(CP_ACP, ; ?6 P7 Q  `/ J' o9 I7 O
                            0,
, J$ l- x  y9 ?; I2 r                            varRetVal.bstrVal, & t% J8 r+ i4 O7 a+ i  Q- \5 L
                            -1, / J1 J3 \0 \, \$ v4 X% B& F- ]# d
                            lpszState,
( V. h! F+ z6 C                            cchState, 8 P! c" \; E) h4 a0 |
                            NULL, 2 F2 G1 V# M/ z# K$ y* R
                            NULL);/ O5 J" m  l; C8 H
    }
6 z+ s/ R8 ^% ?' `" _9 R# U' E/ S' w7 J" T7 q6 t5 k' p$ K% e# t
    VariantClear(&amp;varRetVal);
4 i/ O# K! ?8 m: R0 b* X5 _3 U8 X
    return(lstrlen(lpszState));
) M0 @! E) n' [3 S  [) i# R# |}</FONT></TD></TR></TABLE></P>- V6 Q4 V0 w" }/ _
<>好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)
# r7 G8 K- _5 d5 q3 G
+ k$ @. Z3 E+ L+ `四、在 IAccessible 接口上执行动作% s" S7 V! A+ q4 S* ^5 W
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescription 能取得UI元素的描述,get_accValue 能取得一个值。
3 }* a3 E7 A8 Y" P2 j    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。9 x! G( R1 L8 s7 b
    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。 </P>
5 {8 ?4 Y  |) k& |0 I1 T* Z4 t<>; I7 r2 o3 B6 h
<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>' j/ W$ G9 ?& t6 ]6 Q  X/ K

. L+ s& I: o' _: t& a3 q<TR>+ G; }3 u- |" a0 l0 T
<TD><FONT color=#00309c>//在文本输入框输入"regedit"
' }2 p+ k+ \# |5 k" \+ x4 g8 H& w! lif(1 == FindChild (paccMainWindow, "打开(O):",
5 `- |4 b2 J* n1 i                   "可编辑文字", * Q% R3 u& k/ `( k+ H. H
                   "Edit",
$ Z+ J2 W) \* b                   &amp;paccControl, % M- m& x, @4 L2 S
                   &amp;varControl))* O4 `" O8 t5 b1 S, }) Y
{
. q. H, |  Y! O% v& Z, o9 [) g//在这里修改文本编辑框的值- D6 p; {5 u& v9 z' S" L
hr = paccControl-&gt;put_accValue(varControl,
! j* L; D! t6 D/ F$ `                                  CComBSTR("regedit"));
5 ~$ p6 \' l8 I/ vpaccControl-&gt;Release();
+ P* l- K% l  O2 M, G2 ~% wVariantClear(&amp;varControl);/ i3 C0 O. _# ?3 \4 Z
}
1 q' Q, e% m5 g/ i! F  s$ v2 w2 G' f- ^2 k
// 找到确定按钮,并执行默认动作。
1 C$ I( R) a  a# c7 }) |if(1 == FindChild (paccMainWindow, 5 J& b! {! L7 t, ?& Q
                   "确定",
6 _& \) F; \9 p' N                   "按下按钮",
7 `  d- j/ q+ \                   "Button",
6 I3 ?. S8 [; {% V5 r                   &amp;paccControl, 9 X& W; w  v# g2 t+ @" S  R
                   &amp;varControl))
' C2 t2 r: ^* q- l( ^- A: B- ]+ P* l: d{
$ R" _2 z% H3 S( @$ D4 f& w# C//这里执行按钮的默认动作,即"按下这个按钮"" W" j& Q3 Z( J+ w$ l% x- ?3 O
hr = paccControl-&gt;accDoDefaultAction(varControl);
0 y0 R  {# T% l) l6 f1 }% I- _paccControl-&gt;Release();
5 ?* Z; o, e. A  H1 @! W' B7 GVariantClear(&amp;varControl);
# M4 m) y5 r* }9 Z  |3 Z}</FONT></TD></TR></TABLE></P>% `* p# a( g- h3 k: p
<>现在,你会发现已经成功启动了注册表编辑器!!2 r% N: k1 L5 [/ |  X- y
</P>, T. N2 W$ H: C! u  a! `+ G
<>五、模拟键盘和鼠标输入/ O7 c9 _  U; u# s+ W1 i
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。- v6 d/ |$ T1 {! T* o# E
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。8 A7 ]( C5 P) ^
    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。
; i- |5 y+ N- g( O6 G% A4 c下面的代码将模拟 ALT+F4 按键来关闭窗口。 </P>1 [' \7 w& @  u5 `; U& e- L7 O
<>
8 |  o3 v/ V$ [% t5 R- f+ I: w# T<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>
6 l4 P' S: v6 |) O
2 d/ s2 t6 h4 \) b4 o<TR>
" y( h1 p! C* l& Q<TD><FONT color=#00309c>INPUT input[4]; # |2 U4 g. q$ t; Y: W
memset(input, 0, sizeof(input));
! R! D4 X6 x' r: A7 l" T% o: v1 J1 I5 K3 M
//设置模拟键盘输入; J) |5 m  r5 q
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
, w: _/ ~1 j% X; N; v9 e$ g& `input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
/ r* s; z9 ~- X, E/ c" s6 ]; U- Ainput[1].ki.wVk  = input[3].ki.wVk = VK_F4;
% s) A" C. [; P4 g9 Q
/ z6 _$ b$ g# K8 g// 释放按键,这非常重要; n, k7 i( f* D) I; {6 |, J
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;
2 V* q/ M7 k! l: B8 F
$ ]  _+ H8 T- f8 e! t" I& kSendInput(4, input, sizeof(INPUT));</FONT></TD></TR></TABLE></P>/ z4 v+ P1 l4 }
<>具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)
* R% n7 z# q% I" e8 e$ N) f& Y</P>
0 [! n# Q( I/ L6 G: C<>六、监视WinEvents
0 W- ]3 h# c8 V* {    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。( J0 j' E! Q( `6 o5 B0 f% ?8 z
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。/ d5 o% _) u, Z+ J. o& _9 {
    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:</P>7 n. ^: a" o# s
<>
' r- c: h0 i8 D" a1 `0 N9 h9 o. U<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>3 `% i" W1 s: E. a

4 Y# y" v$ t; A. |* p<TR>- z: }7 L. z+ h$ L8 p
<TD><FONT color=#00309c>if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
; S2 H! j4 A( }7 R" E, {{% f6 J! i9 b' r( k4 d% Y+ G
hEventHook = SetWinEventHook(
& ^! p& p* U5 v* dEVENT_MIN, // eventMin ID
9 R6 D" `5 ]7 @6 o& {EVENT_MAX, // eventMax ID
; f2 V+ c1 a- m; z6 G5 m8 ?0 GNULL, // always NULL for outprocess hook
3 U  C: y  r- e, `7 uWinCreateNotifyProc, // call back function
. _2 I0 _* u1 W0 E- s0, // idProcess# J0 E8 G# n/ q! p: g/ x4 u
0, // idThread ! y' E. e9 q0 y% S) d2 _
         // always the same for outproc hook
0 {" N% @* N  XWINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
$ w, z' }5 m" H6 c  x) ~: `; T}  </FONT></TD></TR></TABLE></P>
) A3 R, ?' v& B3 L6 Z
6 I2 v) U" i: v8 O2 X, E9 M<>    第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。3 v) T5 _6 i, M
下面是回调函数:</P>" U" M/ j9 v6 b7 a% ]) U8 L
<>
! B8 ?6 ]9 I/ b<TABLE cellSpacing=0 cellPadding=15 width="95%" align=center bgColor=#e6e6e6 border=0>; Q9 S* S3 z2 Q9 O& [/ k  t
- {5 M1 b7 `8 k4 J! F+ i
<TR>
6 F5 R: q) f/ [, c3 N' O5 W<TD><FONT color=#00309c>void CALLBACK WinCreateNotifyProc(4 c; N  u% j2 V/ Q
HWINEVENTHOOK  hEvent,
* I6 E# J& S' P# WDWORD   event,
! l' _2 S4 s2 e3 k6 iHWND    hwndMsg,- o3 q4 F* ~) j6 r' E" n9 X
LONG    idObject,
+ }5 k# R/ `4 }9 g: d: ZLONG    idChild,, c( S! r& B* p5 t
DWORD   idThread,8 U, A1 I$ Q$ m8 h; n
DWORD   dwmsEventTime
1 p3 c2 d3 M8 p1 c  |# V): u( q* U% ^- m! T+ h
{
# Q+ ^; i7 l4 k3 }* w3 {3 h4 g! U- G" T# [
if( event != EVENT_OBJECT_CREATE)3 t+ |' ?& G5 V. @, _  O
return;
, u& s$ k- x3 t( e, _! q; q9 P" o7 Z0 u8 X/ M8 S
char bufferName[256];
' l0 O4 n# Y2 e8 |0 U* PIAccessible *pacc=NULL;
4 p6 w1 Y, a& |7 ]3 tVARIANT varChild;: k- \. @' O9 i# p$ t+ y
    VariantInit(&amp;varChild);
) H* m, @; O8 Q! v" R//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
! t' Z) I; T5 W) {8 hHRESULT hr= AccessibleObjectFromEvent(hwndMsg, - u6 I. p5 n. a/ l
                                      idObject, % `6 ?2 ?4 {) r3 i
                                      idChild, + E! j- B) d( J1 y! g
                                      &amp;pacc,
! C0 n$ j- x2 Y% W5 B: _1 x/ ~                                      &amp;varChild);
7 |) ~1 V- b- q, b. d) g
4 y, M; e0 j5 i# ]4 d) }0 F: L3 y% [if(!SUCCEEDED(hr))
# f) y9 U$ {* ]9 e3 v4 E{
" w+ w, X- s$ c. x% l/ ~. FVariantClear(&amp;varChild);
/ j# k0 q, j; A5 Y8 z  Jreturn;
' A2 v! A( C8 r0 X& J2 @$ C}2 N0 Y4 H9 c, s2 |
//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
# O* R# |! @4 c: r8 t, xGetObjectName(pacc, &amp;varChild, bufferName, sizeof(bufferName));4 `( [/ M, M" t* d/ `
if(strstr(bufferName, szMainTitle))$ l0 S- D6 q8 Y" A. Y; ?5 o" R
PostThreadMessage(GetCurrentThreadId(), 3 O, x. X2 O  B0 D
                  WM_TARGET_WINDOW_FOUND,
# G/ {- Y( d' n% H* T& r* X2 U2 D                  0,
- e2 r4 O  x% J9 [( ]/ [1 k; `                  0);
+ y6 O/ W8 [0 Q9 }" k# ?' W: h4 H5 ]8 p* ]6 A0 |& k
return;6 j; C2 A& f) S9 U
}    </FONT></TD></TR></TABLE></P>恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。- r/ t# n2 ^, ~5 ~# M

6 T9 c  x  z8 I附录:
6 l9 O! |9 e% q9 D
% }: P+ d" L- m9 B* B) a关于IAccessible 接口/子ID对:
8 H2 x5 S1 U+ v& T- [9 D$ x    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。
. N% J5 Z1 o4 m( t$ I; F8 |    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
2 @9 Q. Q9 s$ a    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。. }3 @3 ?1 V3 K+ x7 p
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。
+ t4 v' t# s' U0 B) ~    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!/ I, f3 F& A, n' S. F" c  g. t3 B

5 V) W  ?0 I: p. T7 q6 U. @注:
0 @: {$ T) w2 a  M/ G; J# G: Y  {    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)% h9 @6 q' O; k$ U* m
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。8 K7 K9 \4 F$ j& g# E! s
) ]8 C% s( O0 H9 j. t+ w4 g0 v/ _
参考资料: * ?  C3 N: ?: m+ J, g
<UL>
9 T: \- D+ P0 s! \' X5 t<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 , O5 I( Y& S4 `9 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-10-8 05:42 , Processed in 4.395786 second(s), 57 queries .

回顶部