心念旧安,夙夜忧叹。

MS Active Accessibility 接口技术编程尝试

上一篇 / 下一篇  2007-07-12 15:21:55 / 个人分类:转贴好文

51Testing软件测试网[8`E U;[8U

http://msdn2.microsoft.com/en-us/library/aa286482.aspx51Testing软件测试网.d:o]!e6iEP4~$f

Microsoft©Active Accessibility 2.0 is a COM-based technology that improves the 
way accessibility aids work with applications running on Microsoft Windows?. It 
provides dynamic-link libraries that are incorporated into the operating system 
as well as a COM interface and application programming elements that provide 
reliable methods for exposing information about user interface elements.
51Testing软件测试网9jiYlJ!^0Y Q+e

基础
,LsTMO#_,j)CB0   Microsoft©Active Accessibility 是一种相对较新的技术(1.0版在1997年5月份推出)。目的是方便身患残疾的人士使用电脑——可用于放大器、屏幕阅读器,以及触觉型鼠标。同样还可以用来开发驱动其它软件的应用程序,其模拟用户输入的能力尤其适合测试软件的开发。
:_!JN{t~G5M0   Active Accessibility 的主要思想是提供一种以程序方式访问UI元素信息或操作这些UI元素的功能。支持这种功能的 UI(User Interface) 元素是可访问的。在大多数情况下,这意味着一个UI元素支持 IAccessible 接口。你也可以说在 Active Accessibility 的世界里,一个可访问的UI元素可表示为 IAccessible 接口。51Testing软件测试网^}^(C$k1u.{F!b8x*{`
   每当你需要得到有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你通常需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。51Testing软件测试网V#Z~+Q@

oJM(X{ b[0Active Accessibility 原理
aSl!g-t$X;TB0   Active Accessibility? 的核心功能由 OLEACC.DLL 提供的。每次当你调用一个函数来返回一个 IAccessible 接口指针,其与一个UI元素相对应,OLEACC.DLL就检查此元素是否内在支持 IAccessible。内在的支持意思是该元素的 IAccessible 是用程序实现的。51Testing软件测试网F2mIZ z1d V
   当一个UI元素不能内在的支持 IAccessible 时,OLEACC.DLL 检查该元素的Windows 类名。如果该类是一个 USER 或者 COMCTL32 支持的类,OLEACC.DLL 就创建一个代理为 UI 元素实现 IAccessible 接口。大多数--但不是全部--COMCTL32 控件都具有被 OLEACC.DLL 支持的 IAccessible 接口。
2uk5B7O3A/M?q0   内在支持 IAccessible 的 UI 元素的例子是定制控件,owner-drawn 和无窗口的控件。因为开发者创建的程序包含这些UI元素,同样就实现了这些元素的接口,他们有责任为这些方法和属性提供正确的支持。51Testing软件测试网"V)YU*Q#p;^/D7Ku
   如果你用标准控件,这也意味着你不必重写你的应用,这些应用自动与Active Accessibility兼容。
"tR8R%O%X,c3v;g's5AU0   Active Accessibility名字是基于 Win32 控件的名字给出的,角色基于控件的功能定义。
S&ELDk z#?6A'T d(k'b051Testing软件测试网F}O&HB WA,@L
如何得到 IAccessible 接口指针
SI;c8K:T4L G.t l m0   每当你需要有关一个元素的信息,在其上执行一个动作,或者使用 Active Accessibility 做其它的什么,你只需要通过使用代表这个元素的 IAccessible 接口的一种方法或者属性来引用这个元素。
3@V,c8u`o1G S0   有几种方法取得代表一个可访问 UI 元素的 IAccessible 接口的指针。最普通的方法是使用 Active Accessibility 提供的一种函数,例如 AccessibleObjectFromPoint,AccessibleObjectFromWindow 等等,或者使用 IAccessible 支持的方法,例如 get_accChild,get_accParent。
4V+?6y!~9m9X(t$Nqt0   IAccessible 接口支持允许你得到各 UI 元素信息的属性,而其中对于例子程序最重要的属性是名字、角色和状态。51Testing软件测试网A Kr8k6i6`.S3m-o
   Active Accessibility SDK提供了一些方便的工具,其中的 Object Inspector 能显示光标指向的UI元素的属性。Object Inspector 显示了Active Accessibility 的世界如何因为具有支持一个选定窗口内的 IAccessible 接口的控制而变得通用了。除了搜索有关元素的信息和通过 IAccessible 接口控制元素以外,Active Accessibility? 还有两种对于例子程序非常有用的特性:监视UI元素发生的事件和模拟键盘、鼠标输入。由可访问的元素激发的事件称为 WinEvents,当可访问的元素创建或者名字、状态、位置或者键盘焦点发生变化时,就激发这些事件(事件机制类似于标准的 Windows 的 hook 机制。监视事件我们将在后面介绍。)。这些事件的清单见文件 WINABLE.H。每个事件的名字以 EVENT_OBJECT 或 EVENT_SYSTEM 开始。
)ez7Q0Gx Y7qw5`0   好,我们言归正传,来介绍如何得到 IAccessible 接口指针。前面已经提到过 AccessibleObjectFromWindow 这个 Active Accessibility 提供的函数,从字面上大家可以看出是通过窗口来得到对应的 IAccessible 接口指针。51Testing软件测试网.z['@c*?Y
   因为 IAccessible 接口的数量比窗口要多(因为大多数--但不是全部--COMCTL32 控件都有被 OLEACC.DLL 支持的 IAccessible 接口。),使用 Win32 函数来搜索一个窗口将会比使用 Active Accessibility 树搜索与该窗口相应的 IAccessible 接口要占用少得多的时间。这就意味着为了提高性能,你应该使用 FindWindow 和 EnumWindows 这样的 Win32 函数来找到与希望的UI元素最接近的窗口。当然,在权衡 Win32 函数和 Active Accessibility 函数时,上面的规则只是使用它们的一般标准而不能盲目的遵照执行,重要的是理解它们的本来意义。51Testing软件测试网6I{5R[ w
下面结合代码介绍一下它的用法。
)Jq E;``yu0我们来得到下面运行窗口的 IAccessible 接口指针。
&i`k0Rv G#W0i8V[0
7xW-xd,h _"Z0
3pr,mB U["x0图一51Testing软件测试网/v)E,ug;TK"D
51Testing软件测试网F%Gw*L!V U

HWND hWndMainWindow;
IAccessible *paccMainWindow = NULL;
HRESULT hr;
//得到标题为"运行"的窗口的句柄
if(NULL == (hWndMainWindow = FindWindow(NULL, "运行")))
{
	MessageBox(NULL, "没有发现窗口!", "错误", MB_OK);
}
else
{
	//通过窗口句柄得到窗口的 IAccessible 接口指针。
	if(S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, 
	                                            OBJID_WINDOW, 
	                                            IID_IAccessible,
	                                            (void**)&paccMainWindow)))
	{
		//……我们可以通过这个指针paccMainWindow进行操作。
		paccMainWindow->Release();
        }
}
51Testing软件测试网cFz N)Q`

    现在我们已经得到窗口的 IAccessible 接口指针了(paccMainWindow),那么,我们可以干什么呢?我们怎么得到窗口中某个控件的 IAccessible 接口指针呢?我们就以上面的运行窗口为例。看看如何得到文本框的 IAccessible 接口指针!!51Testing软件测试网l]7yw!P5V"v1~c \
    首先我们启动 inspect32.exe,什么?你不知道这是什么东西?赶紧先下载个Active Accessibility SDK看看吧……51Testing软件测试网%@DlY4K#Rs2d"p
    然后,把鼠标放到所关注的控件上(即上图中的文本输入框),你会得到如下信息:51Testing软件测试网 J9swf*O"n9NW
51Testing软件测试网+{#L$cMRp)^
51Testing软件测试网;U ~5jvB2B\'a2\
图二
/M)krW5u(?"|0
|C'SJ(T.s4DK0我们现在主要关注的信息是:Name、Role、Window className。

t[X r L/OF0
Name = "打开(O):"
Role = "可编辑文字"
Window className = "Edit"

p*l.iN_p-u/rakd0    当开发自定义、owner drawn 或者无窗口的控件时,为同一窗口的每个"角色-名字"指定独一无二的表示是一个非常好的编程习惯。然而,如果由于某种原因,同一窗口中的2个 UI 元素具有同样的"角色-名字"对,那么就需要增加一个参数--windows 类--以唯一的来表示这个元素。51Testing软件测试网lB%nX/r7^
    FindChild 函数显示了一个基于 Active Accessibility 父/子(你可以理解成父窗口/子窗口的关系,只是为了便于理解:-P)导航的搜索例程的实现。这个函数有6个参数。前4个包含传递给函数的信息,后2个包含了 IAccessible 接口/子ID对(见附录)。
6Xjv%teaf0下面我们开始取文本输入框的 IAccessible 接口指针。

8b/tk+^W:PvO~0
IAccessible*	paccControl = NULL;//输入框的 IAccessible 接口
VARIANT		varControl;    		//子ID。

FindChild( paccMainWindow, 
           "打开(O):", 
           "可编辑文字", 
           "Edit", 
           &paccControl, 
           &varControl )
51Testing软件测试网'V,akOR6? xW

第一个参数是先前得到的窗口 IAccessible 接口指针。51Testing软件测试网1N0i9`MMt.\*LO)T
第二、三、四个参数分别是名字、角色、类。51Testing软件测试网$vk1x kJ&O
后2个为返回参数包含了 IAccessible 接口/子ID对。下面是FindChild的实现。

0KR"`3O&F(ct0
BOOL FindChild (IAccessible* paccParent, 
                         LPSTR szName, LPSTR szRole, 
                         LPSTR szClass, 
                         IAccessible** paccChild, 
                         VARIANT* pvarChild)
{
	HRESULT hr;
	long numChildren;
	unsigned long numFetched;
	VARIANT varChild;
	int index;
	IAccessible* pCAcc = NULL;
	IEnumVARIANT* pEnum = NULL;
	IDispatch* pDisp = NULL;
	BOOL found = false;
	char szObjName[256], szObjRole[256], szObjClass[256], szObjState[256];
	
		//得到父亲支持的IEnumVARIANT接口
	hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);
	
	if(pEnum)
		pEnum -> Reset();
	
//取得父亲拥有的可访问的子的数目
	paccParent -> get_accChildCount(&numChildren);
	
//搜索并比较每一个子ID,找到名字、角色、类与输入相一致的。
	for(index = 1; index <= numChildren && !found; index++)
	{
		pCAcc = NULL;		
		// 如果支持IEnumVARIANT接口,得到下一个子ID
//以及其对应的 IDispatch 接口
		if (pEnum)
			hr = pEnum -> Next(1, &varChild, &numFetched);	
		else
	{
		//如果一个父亲不支持IEnumVARIANT接口,子ID就是它的序号
			varChild.vt = VT_I4;
			varChild.lVal = index;
		}
		
		// 找到此子ID对应的 IDispatch 接口
		if (varChild.vt == VT_I4)
		{
			//通过子ID序号得到对应的 IDispatch 接口
			pDisp = NULL;
			hr = paccParent -> get_accChild(varChild, &pDisp);
		}
		else
			//如果父支持IEnumVARIANT接口可以直接得到子IDispatch 接口
			pDisp = varChild.pdispVal;
		
		// 通过 IDispatch 接口得到子的 IAccessible 接口 pCAcc
		if (pDisp)
		{
			hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);
			hr = pDisp->Release();
		}
		
		// Get information about the child
		if(pCAcc)
		{
			//如果子支持IAccessible 接口,那么子ID就是CHILDID_SELF
			VariantInit(&varChild);
			varChild.vt = VT_I4;
			varChild.lVal = CHILDID_SELF;
			
			*paccChild = pCAcc;
		}
		else
			//如果子不支持IAccessible 接口
			*paccChild = paccParent;
		
		//跳过了有不可访问状态的元素
		GetObjectState(*paccChild, 
		               &varChild, 
		               szObjState, 
		               sizeof(szObjState));
		if(NULL != strstr(szObjState, "unavailable"))
		{
			if(pCAcc)
				pCAcc->Release();
			continue;
		}
		//通过get_accName得到Name
		GetObjectName(*paccChild, &varChild, szObjName, sizeof(szObjName));
		//通过get_accRole得到Role
		GetObjectRole(*paccChild, &varChild, szObjRole, sizeof(szObjRole));
		//通过WindowFromAccessibleObject和GetClassName得到Class
		GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
		//以上实现代码比较简单,大家自己看代码吧。

			//如果这些参数与输入相符或输入为NULL
		if ((!szName || 
		     !strcmp(szName, szObjName)) && 
		     (!szRole || 
		      !strcmp(szRole, szObjRole)) && 
		     (!szClass || 
		      !strcmp(szClass, szObjClass)))
		{
			found = true;
			*pvarChild = varChild;
			break;
		}
		if(!found && pCAcc)
		{
			// 以这次得到的子接口为父递归调用
			found = FindChild(pCAcc, 
			                  szName, 
			                  szRole, 
			                  szClass, 
			                  paccChild, 
			                  pvarChild);
			if(*paccChild != pCAcc)
				pCAcc->Release();
		}
	}//End for
	
	// Clean up
	if(pEnum)
		pEnum -> Release();
	
	return found;
}

// UI元素的状态也表示成整型形式。因为一个状态可以有多个值,
//例如可选的、可做焦点的,该整数是反映这些值的位的或操作结果。
//将这些或数转换成相应的用逗号分割的状态字符串。
UINT GetObjectState(IAccessible* pacc, 
                    VARIANT* pvarChild, 
                    LPTSTR lpszState, 
                    UINT cchState)
{
    HRESULT hr;
    VARIANT varRetVal;
	
    *lpszState = 0;
	
    VariantInit(&varRetVal);
	
    hr = pacc->get_accState(*pvarChild, &varRetVal);
	
	if (!SUCCEEDED(hr))
        return(0);
	
	DWORD dwStateBit;
	int cChars = 0;
    if (varRetVal.vt == VT_I4)
	{
		// 根据返回的状态值生成以逗号连接的字符串。
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; 
               dwStateBit < STATE_SYSTEM_ALERT_HIGH; 
               dwStateBit <<= 1)
        {
            if (varRetVal.lVal & dwStateBit)
            {
                cChars += GetStateText(dwStateBit, 
                                       lpszState + cChars, 
                                       cchState - cChars);
				*(lpszState + cChars++) = '','';
            }
        }
		if(cChars > 1)
			*(lpszState + cChars - 1) = ''\0'';
    }
    else if (varRetVal.vt == VT_BSTR)
    {
        WideCharToMultiByte(CP_ACP, 
                            0, 
                            varRetVal.bstrVal, 
                            -1, 
                            lpszState,
                            cchState, 
                            NULL, 
                            NULL);
    }
	
    VariantClear(&varRetVal);
	
    return(lstrlen(lpszState));
}

sV9{3K:Y_YQ0好了!!我们已经成功得到文本框的 IAccessible 接口指针了!!现在你可以用这个接口指针为所欲为了!!!呵呵:)51Testing软件测试网aQk(J;b/U`
51Testing软件测试网/v|+P9LR_/nl
在 IAccessible 接口上执行动作51Testing软件测试网g!fH9rEE8D v
    有了表示一个可访问的 UI 元素的 IAccessible 接口/子ID对,你也有了搜索该元素一个名字(get_accName)、角色(get_accRole)、类和状态(get_accState)的方法。让我们看看你还可以干什么!get_accDescrīption 能取得UI元素的描述,get_accValue 能取得一个值。51Testing软件测试网dC,\YY#A!}
    最重要的函数之一是 accDoDefaultAction。每个可访问的UI元素都有一个缺省定义的动作。例如,一个按钮的缺省动作是"按下这个按钮",一个检查框的缺省动作是"不选"。为了确定一个元素的缺省动作,请参考 Active Accessibility 文档或者调用 get_accDefaultAction。
L0rS-LvW0    如果我想起动注册表编辑器,该怎么办呢?如果是我们手动做的话,无非是在文本输入框输入"regedit",然后按确定按钮,就这么简单。下面我们来看看用 Active Accessibility 是怎么来实现的。

\(x/M+},Md6x2A2ni0
//在文本输入框输入"regedit"
if(1 == FindChild (paccMainWindow, "打开(O):", 
                   "可编辑文字", 
                   "Edit", 
                   &paccControl, 
                   &varControl))
{
	//在这里修改文本编辑框的值
	hr = paccControl->put_accValue(varControl, 
	                                  CComBSTR("regedit"));
	paccControl->Release();
	VariantClear(&varControl);
}
		
// 找到确定按钮,并执行默认动作。
if(1 == FindChild (paccMainWindow, 
                   "确定", 
                   "按下按钮", 
                   "Button", 
                   &paccControl, 
                   &varControl))
{
	//这里执行按钮的默认动作,即"按下这个按钮"
	hr = paccControl->accDoDefaultAction(varControl);
	paccControl->Release();
	VariantClear(&varControl);
}

]2H8Dv`2mS0现在,你会发现已经成功启动了注册表编辑器!!
0ioT2eK0
d.Q4S&I2y+{X6|t0模拟键盘和鼠标输入51Testing软件测试网 ?-Je2q4w3r4B.@1wP
    让我们假设你需要操作一个新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最简单的解决办法就是模拟键盘和鼠标输入。例如,你可以用Tab模拟转移到期望的控件。51Testing软件测试网 x@.z}:P)g'@6jg
    使你能够实现这些的函数就是 SendInput 一个一般的USER API。虽然不属于Active Accessibility,把他们联合使用很自然。
w f5u{"R&SU9H0    SendInput 接受三个参数:要执行的鼠标键盘动作个数、INPUT结构数组和结构数组的大小。每个INPUT结构描述一个要执行的动作。注意,按下一个按钮和释放一个按钮是两个不同的动作,所以必须创建两个不同的INPUT结构。51Testing软件测试网-na i5m&qW'gk+u%PA
下面的代码将模拟 ALT+F4 按键来关闭窗口。51Testing软件测试网o.`J"N"ny3v

INPUT input[4];	
memset(input, 0, sizeof(input));

//设置模拟键盘输入
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
input[0].ki.wVk  = input[2].ki.wVk = VK_MENU;
input[1].ki.wVk  = input[3].ki.wVk = VK_F4;

// 释放按键,这非常重要
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;

SendInput(4, input, sizeof(INPUT));
51Testing软件测试网t/U$qNb

具体用法大家还是查MSDN吧,这里就不罗嗦了!!:)51Testing软件测试网!?^8Mp9X BRp'r
51Testing软件测试网*yC0e&p!j9Ye%k
监视WinEvents51Testing软件测试网u]7S!qXm
    监视 WinEvents 非常像通过 Windows Hook 监视 Windows 消息。最重要的区别就是从另一个进程监视 UI 元素发出的 WinEvents 时,你不需要创建一个单独的DLL来注入那个进程的地址空间。51Testing软件测试网kO {*v#VQ ri&L
    监视 WinEvents 有两种选择:通过设置 SetWinEventHook 函数的最后一个参数来确定是在上下文之外还是之内监视。如果是在上下文之外,不需要额外的DLL,回调函数运行在目标进程之外。如果是在上下文之内,回调函数必须放在额外的DLL,并注入目标进程的地址空间。第二种方法写代码比较麻烦,但是运行效率高。
ywe9k*w0    好,现在回到上面的例子。上面例子能够执行的前提条件是能够找到标题为"运行"的窗口。现在可以先检查运行窗口是否存在,如果不存在就设置WinEvents 钩子去监视,直到"运行"窗口被创建。看下面代码:

0C/{!e'k@/]0
if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
{
hEventHook = SetWinEventHook(
	EVENT_MIN,	// eventMin ID
	EVENT_MAX,	// eventMax ID
	NULL,		// always NULL for outprocess hook
	WinCreateNotifyProc,		// call back function
	0,				// idProcess
	0,				// idThread 
         // always the same for outproc hook
	WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
}

2} i gW4u.v1I9I0第一、二个参数用来指定监视事件的范围。第四个参数是定义的回调函数。
F/c8XEuh2{L0下面是回调函数:51Testing软件测试网 X-lFa"^

void CALLBACK WinCreateNotifyProc(
 HWINEVENTHOOK  hEvent,
 DWORD   event,
 HWND    hwndMsg,
 LONG    idObject,
 LONG    idChild,
 DWORD   idThread,
 DWORD   dwmsEventTime
 )
{
	
	if( event != EVENT_OBJECT_CREATE)
		return;
	
	char bufferName[256];
	IAccessible *pacc=NULL;
	VARIANT varChild;
    VariantInit(&varChild);
	//得到触发事件的 UI 元素的 IAccessible 接口/子ID对
	HRESULT hr= AccessibleObjectFromEvent(hwndMsg, 
	                                      idObject, 
	                                      idChild, 
	                                      &pacc, 
	                                      &varChild);
	
	if(!SUCCEEDED(hr))
	{
		VariantClear(&varChild);
		return;
	}
	//得到 UI 元素的Name,并比较,如果是"运行"就发送消息给主线程。
	GetObjectName(pacc, &varChild, bufferName, sizeof(bufferName));
	if(strstr(bufferName, szMainTitle))
		PostThreadMessage(GetCurrentThreadId(), 
		                  WM_TARGET_WINDOW_FOUND, 
		                  0, 
		                  0);
	
	return;
}

/][u-|7QV0恩…………,一个应用基本成型了,虽然比较简单。就先写这么多吧,请关注后续介绍。
K|A!p4yxK8O}5x!n$aI051Testing软件测试网-Sb3v8H-y%~
附录:51Testing软件测试网/{(nbt4s1x Z2_+j(B/u
51Testing软件测试网poExw+yC
关于IAccessible 接口/子ID对:51Testing软件测试网mutF(D
    让我们来考虑这样一个控件,他支持 IAccessible 接口并且包含一些子控件,比如 listbox 就包含很多 items 。有两种方法让他可以被访问:第一种,提供listbox的 IAccessible 接口和每一个 item 自己的 IAccessible 接口。另一种是只提供一个控件的 IAccessible 接口,这个接口能够提供基于某种识别方法来访问每一个子控件的功能。51Testing软件测试网9{Oa8O7]0XT0`
    第一种方法,需要为这个控件和每一个子控件创建单独的 COM 对象,这会比第二种方法(每一个子控件不支持自己的 IAccessible 接口,而是通过父接口来访问)增加内存消耗。第二种方法里,通过增加一个参数--子ID--同父的IAccessible 接口一起表示这个子控件。子ID 是一个 VT_I4 型的 VARIANT 值,包含一个由程序决定的独特的值,或只是一个子控件的序号。序号意味着第一个子控件的ID为1,第二个子控件的ID为2,依次增长!
v(G*\e(YDLi"G N0    这样,如果一个子控件不支持自己的 IAccessible 接口,而其父控件支持,那么这个子控件可以用它的父控件的 IAccessible 接口/子ID 对来表示。通常,一个支持 IAccessible 接口的父UI元素也是通过这样的 IAccessible 接口/子对表示的,这时候其子ID号为 CHILDID_SELF (就是0)。51Testing软件测试网|(zO(PR ^,i
    记住,子ID号总是相对于 IAccessible 接口的。例如,一个可访问的元素可以同相对于其父 IAccessible 接口的一个非子 CHILDID_SELF 的 ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相对于自己 IAccessible 接口的CHILDID_SELF。51Testing软件测试网:vi-^L?(LG@O
    呵呵,翻译的有点别扭,意思就是说,如果这个控件支持 IAccessible 接口,那么它的子ID就是0(CHILDID_SELF),可以用它自己的 IAccessible 接口和0这个对来表示这个控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一个相对于父 IAccessible 接口的子ID来表示。哎呀!!不知道说明白没有。郁闷!!!!51Testing软件测试网4zMD*c8u*EE[JI
51Testing软件测试网+[5g"k3OE
注:
JD\#JY.P0    我也是刚开始学习怎么使用MSAA,但是苦于很难找到中文资料。希望这篇文章对大家能有所帮助。由于了解的还很肤浅,错误难免,望谅解!!:)51Testing软件测试网j JeNWM'g
    还有,这篇文章基本编译自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新编排了一下,如果觉得不符合自己的学习习惯可以看原文。并且我的文章省略了很多东西,呵呵。51Testing软件测试网 s.z8y3BKV&{qN9?
51Testing软件测试网M!vzVW8R6Z#Y
参考资料:51Testing软件测试网3w8PEP0[d7T

  • 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、 MSDN中的相关章节。

TAG: 转贴好文

 

评分:0

我来说两句

日历

« 2024-04-18  
 123456
78910111213
14151617181920
21222324252627
282930    

数据统计

  • 访问量: 453642
  • 日志数: 138
  • 图片数: 4
  • 建立时间: 2006-11-26
  • 更新时间: 2013-08-30

RSS订阅

Open Toolbar