程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 活動的可訪問兼容型應用給程序員帶來使用軟件的新工具

活動的可訪問兼容型應用給程序員帶來使用軟件的新工具

編輯:關於VC++

摘要Active Accessibility推出的目標是方便身患殘疾的人士使用電腦——可 用於放大器、屏幕閱讀器,以及觸覺型鼠標。它還可用來開發驅動其它軟件的應用程序,最 後,其模擬用戶輸入的能力尤其適合測試軟件的開發。

本文從Active Accessibility 的 基本概念出發,帶你領略一個測試應用程序軟件的開發過程。你將會看到這個測試程序是如 何與常用的控件以及其它UI元素交互,並處理隨後的WinEvents的。

Microsoft® Active Accessibility®是一種相對較新的技術(1.0版在1997年5月份推出),它通過提 供一種從任意UI元素提取信息的有效的方法,實現了對UI元素的程序式訪問。有了這種功能 ,程序員就能獲得有關UI元素的信息從而與之完成交互。例如,你可以從程序裡按下一個按 鈕,從一個列表中選擇一項,或者拉下一個組合框。最初,這種技術是為了方便缺陷人群使 用軟件(例如視力很差的人),而實際上它得到了更為廣泛的應用,例如放大器,屏幕閱讀 器,以及觸覺型鼠標。

Active Accessibility可以運行於Windows98®和 Windows2000®。為了在Windows95®(英文版)和windows NT4.0®(Service Pack 6及其以上)下使用,你可以安裝Active Accessibility RDK和SDK,它們可以從下面的 站點下載:http://msdn.microsoft.com/isapi/gomscom.asp?TARGET=/enable/msaa/.

有關Active Accessibility的其它信息,也可以在此站點找到。

Active Accessibility的實質使得它非常適用於設計驅動其它軟件產品的程序。一類能夠很好的利用 Active Accessibility的程序是測試程序。另一類可能是執行一系列用戶定義的響應UI元素 狀態確認的鍵盤和鼠標輸入的程序——例如,一個使得Windows Media™播 放器自動響應“Windows Media Player Error”消息,從而不間斷的嘗試連接到 一個繁忙的服務器的程序,這種自動響應包括關閉這個對話框,單擊適當的菜單而繼續中斷 的連接過程。

有兩種Active Accessibility有關的程序:兼容Active Accessibility 的程序,操縱它們的工具。本文研究後一類型:開發Active Accessibility工具去控制其它 軟件產品。作為重點,我將集中討論測試程序的開發。這些不僅是有用的,而且它們展示了 Active Accessibility的幾乎所有特性。

Active Accessibility基礎

Active Accessibility的主要思想是提供以編制程序方式訪問UI元素以得到這些元素信息的功能。稱 支持這種功能的UI元素是可訪問的。在大多數情況下,這意味著一個UI元素支持Iaccessible 接口。你也可以說在Active Accessibility的世界裡,一個可訪問的UI元素可表示為 Iaccessible接口。

每當你需要有關一個元素的信息,在其上執行一個動作,或者使 用Active Accessibility做其它的什麼,你只需要通過使用代表這個元素的Iaccessible接口 的一種方法或者屬性來引用這個元素。後面,我將說明一個Iaccessible接口/子 ID對如何表 示一個UI元素。現在,我們暫時集中討論Iaccessible接口。

有幾種方法取得代表一 個可訪問UI元素的Iaccessible接口的指針。最普通的方法是使用Active Accessibility提供 的一種函數,例如AccessibleObjectFromPoint, AccessibleObjectFromWindow等等,或者 使用Iaccessible支持的方法,例如get_accChild,get_accParent。這些功能和方法將在後 面詳細描述。

Iaccessible接口支持允許你得到各UI元素信息的屬性,而其中對於測 試軟件最重要的屬性是名字、角色和狀態。它們大多數將通過例子加以描述。讓我們來看一 下Windows NT 4.0 Windows Explorer Find:所有的文件對話框顯示在圖1中,包括一個標示 為“Look in:”的組合列表框,它的名字是Look in:,角色是combobox,狀態是 可見。UI元素的狀態反映了元素的當前狀態。

Figure 1 The IAccessible Example

Active Accessibility SDK提供 了一些方便的工具,你可以用來熟悉Active Accessibility,以及開發有關的應用程序。 Object Inspector即是其中的一種,它能顯示光標指向的UI元素的屬性。Object Inspector 顯示了Active Accessibility的世界如何因為具有支持一個選定窗口內的Iaccessible接口的 控制而變得通用了。除了搜索有關元素的信息和通過Iaccessible接口控制元素以外,Active Accessibility®還有兩種對於測試程序非常有用的特性:監視UI元素發生的事件和模擬 鍵盤、鼠標輸入。由可訪問的元素激發的事件稱為WinEvents,當可訪問的元素創建或者名字 、狀態、位置或者鍵盤焦點發生變化時,就激發這些事件。這些事件的清單見文件WINABLE.H 。每個事件的名字以EVENT_OBJECT或EVENT_SYSTEM開始。這些事件在Active Accessibility SDK文檔中也進行了討論。

事件機制類似於標准的Windows的hook機制。監視事件和模 擬特征將在例子代碼中進行討論。為了便於理解Iaccessible方法和屬性是怎樣被支持的,讓 我們先看一下Active Accessibility的內部工作原理。

Active Accessibility的內部 原理

Active Accessibility®的核心功能由OLEACC.DLL.提供的。每次當你調用一 個返回到Iaccessible接口(該接口與一個UI元素相對應)的指針的函數,OLEACC.DLL就檢查 此元素是否內在的支持Iaccessible。內在的支持意思是該元素的Iaccessible是用程序實現 的。

當一個UI元素不能內在的支持Iaccessible時,OLEACC.DLL檢查該元素的Windows 類名。如果該類是一個USER或者COMCTL32支持的類,OLEACC.DLL就創建一個代表UI元素實現 Iaccessible的代理。大多數——但不是全部——COMCTL32控制都有被 OLEACC.DLL支持的Iaccessible。

內在的支持IAccessible 的UI元素的例子是定制控 件,ownerdrawn和無窗口的控制。因為包含這些UI元素的軟件的開發者同時實現了這些元素 的接口,為方法和屬性提供正確的支持,自然也是他們的責任。實際上,有些方法或屬性可 能實現得有問題,甚至干脆沒有實現,這還意味著,一個軟件開發商定義了它的屬性,例如 名字和角色。

如果一個UI元素不能內在的支持Iaccessible,並且OLEACC.DLL也不認 識它的類名,OLEACC.DLL就創建一個缺省的代理來提供最小的基於HWND的Iaccessible支持, 例如位置,窗口是否有效以及是否可見。缺省代理不提供任何特定控件的信息。

理解 運行時的Active Accessibility®如何工作對於開發使用標准控件的應用來說是至關重要 的,這些應用自動與Active Accessibility兼容。這也意味著你不必重寫你的應用。Active Accessibility名字是基於Win32®控件的名字給出的,角色基於控件的功能定義。

理解了在Active Accessibility世界,UI元素是如何表示和支持的,讓我們來考慮UI 元素是如何互相聯系的。

UI元素的父/子關系

正象我前面已經提到過的, Active Accessibility的世界由於Iaccessible接口的存在而通用了。每個UI元素有一個指向 Iaccessible接口的指針和一個叫做子ID的標示符。

所有可訪問的UI元素在Active Accessibility術語裡都有父子關系。例如,Look in:組合列表框的祖先是Name & Location屬性頁,盡管不是直接祖先。圖2表示了Windows NT 4.0下它們的關系。在本例中, 元素名字排在開頭,其次是角色;Name & Location是元素的名字,屬性頁是它的角色。 注意不是所有的子接口都顯示出來了,只是那些對於說明必要的才列了出來。本圖可能與其 它操作系統有所不同。例如,在Windows 98下,一些名字是不同的,而且有其它的中間子。

Figure 2 Levels of IAccessible

圖2反映了一 個原則:每個基於HWND的UI元素有兩級IAccessible:代表全部HWND的父親,代表客戶和非客 戶區(例如窗口滾動條、菜單等等)的子。一個控件稱為是被標示的,如果它跟在一個標簽 之後。在本例中,Look in: combobox是一個被標示的控件,標簽是Look in:。對於被標示的 控件,父/子樹包括兩個入口:標簽的名字和角色窗口。與靜態文本和控件相關的接口是這些 入口的子。屬性頁的祖先是Find: All Files對話框。最高層的父親是桌面窗口。

通 常,基於HWND的控件的父/子等級由OLEACC.DLL在HWND體系基礎上創建並支持。可訪問的無窗 口控件的供應商必須手工的創建和管理它們的內在體系。因為在一棵父/子樹中通常有很多中 間的IAccessible接口,該樹經常是很復雜的。

在我們討論如何使用Active Accessibility設計測試軟件之前,還有一個問題應該搞清楚。為什麼用一個Iaccessible接 口/子ID對,而不是僅用一個Iaccessible接口表示一個UI元素?下一節我們來看一下這個問 題。

Iaccessible接口/子ID對

讓我們來考慮一個支持Iaccessible接口並有許 多子的控件,例如一個有許多條目的列表框,有兩種方法使這個控件可被訪問。一個是支持 列表框自身的Iaccessible接口和每一個條目的Iaccessible接口,另一個是只支持一個控件 的Iaccessible接口,這個控件將提供基於某種識別方法訪問它的子的功能。

用第一 種方法,為控件和它的每個子創建單獨的COM對象,和第二種方法相比,這將增加內存消耗, 在第二種方法裡,每個子並不支持自己的Iaccessible接口,而是通過其父親的接口來訪問, 另一個參數——子ID——同父親的Iaccessible接口一起使用來表示一 個子。子ID總是VT_I4類型的變量,包含一個由程序決定的獨特的值,或者只是子的一個序號 。編號意味著第一個子有為1的ID,第二個子的ID是2,依次增長。

這樣,如果一個子 不支持自己的Iaccessible接口,而它的父親支持,那麼這個子就可以Iaccessible接口/子ID 對來表示。通常,支持Iaccessible接口的UI父親元素也由這樣的對來表示,其中子ID定義為 CHILDID_SELF,且其值為0。

記住,子ID總是相對於Iaccessible接口的。例如,一個 可訪問的元素可以有一個非CHILDID_SELF的子ID,相對於它的父親的Iaccessible接口(該接 口的子ID將是CHILDID_SELF,相對於它自身的Iaccessible接口,如果支持的話)。

如果你使用Active Accessibility功能,你應該總能提供Iaccessible接口/子ID對與一個可 訪問的UI元素交互。幾乎所有的Active Accessibility功能和Iaccessible方法需要這兩個參 數,因此不要忘記提供正確的子ID。對於支持Iaccessible的UI元素來說,這個ID將是 CHILDID_SELF。

如果一個子不支持Iaccessible,而父親支持Ienum類型的變量接口, 子ID就能通過叫做Next的標准Ienum變量接口方法檢索。如果父親不支持Ienum類型的變量接 口,子id只是它的序號而已。應該指出,如果一個子支持Iaccessible接口,Next能夠返回一 個子的ID或者與這個子的Iaccessible接口相關的一個Idispatch。Next的實現取決於供應商 ,對於不同的UI元素可以不同。

一個UI元素是可訪問的事實並不必然意味著它有自己 的Iaccessible接口。如果一個UI元素支持它自己的Iaccessible接口,子的Iaccessible接口 /CHILDID_SELF對用來訪問它。如果一個UI元素不支持它自己的Iaccessible接口,表示該元 素的對將是Iaccessible父接口/子ID對,其中子ID不等於CHILDID_SELF,並由程序決定。你 應該總是調用get_accChild來檢查一個UI元素是否支持它自己的Iaccessible接口。

開發測試軟件

為了編寫測試程序,你必須獲得UI元素信息,使用WinEvent hooks來監 視事件,使用Iaccessible接口指針來訪問控件,在控件上執行動作並且模擬鍵盤和鼠標輸入 。這些和其它問題我將在下一節中用一個例程來解決。例程模擬了一個用戶與Find: All Files對話框的交互過程,它使用了標准Win32 FindWindow功能來檢查Find: All Files對話 框是否存在。如果不存在,程序就監控WinEvents,等待其出現。一當對話框出現,程序獲得 指向與對話框相對應的IAccessible接口的指針,跳到Containing text:編輯框和Named:組合 列表框,分別進行設置,按下Find Now按鈕。最後,模擬Alt-F4的鍵盤輸入關閉對話框。注 意在Windows 2000下,對應的窗口及其域與Windows NT 4.0 和Windows 9x下有所不同。為了 解決這個問題,例程檢查當前的操作系統,如果必要就初始化bIsWin2K變量以允許將 Windows2000單獨對待。

自動測試軟件必須通過UI控件,對軟件產品執行一系列的操 作,然後檢查這些操作的結果。因此,你在編寫測試程序時首要解決的問題是如何有效的操 作UI控件。你必須能夠在合理的時間內找到與UI元素對應的IAccessible interface/child ID對,你才能獲得它們的信息並執行一系列動作。

為了檢查結果,你可以檢索UI元素 的有關屬性(例如值、狀態或名字),或者使用鍵盤輸入模擬來選擇文本,將其放入剪切板 ,然後與期望的值進行比較。

在UI元素上執行一個動作然後檢查結果比搜索可訪問的 元素花的時間短。因此,你的注意力應該放在減少搜索時間上。

獲得元素信息

為了獲得UI元素的信息,你需要相應的IAccessible interface/child ID對。我將簡 短的討論如何獲得這個對,但現在我們暫時認為已經知道了。

圖3所示的前4個函數顯 示了如何得到名字,角色,狀態以及和UI元素對應的WINDOWS類。前3個,GetUIElementName, GetUIElementRole和GetUIElementState包裝了IAccessible方法get_accName, get_accRole 和get_accState。它們以UI元素相應的IAccessible interface/child ID對為輸入參數,返 回包含請求信息的串(及其長度)。GetUIElementRole和GetUIElementState為角色和狀態分 別返回一個DWORD的值。當使用get_accRole的時候,函數GetRoleText用來將整型的角色表示 轉換為字符串表示:

hr = pacc->get_accRole(*pvarChild, &varRetVal);
if (hr == S_OK && varRetVal.vt == VT_I4)
{
GetRoleText(varRetVal.lVal, lpszRole, cchRole);
}
else
lstrcpyn (lpszRole, "unknown role", cchRole);

 

UI元素的狀態也表示成 整型形式。因為一個狀態可以有多個值,例如可選的、可做焦點的,該整數是反映這些值的 位的或操作結果。GetUIElementState函數將這些或數轉換成相應的用逗號分割的狀態字符串 ,用一個循環實現:

for(dwStateBit = 1; cCount; cCount—, dwStateBit <<= 1)
{
•••
}

 

在循環之中你可以 檢查從最小到最大的所有狀態值的哪個狀態位包含在變量varRetVal.lVal中,並用 GetStateText函數返回這些狀態的字符串。一般狀態(varRetVal.vt == 0)的情況單獨對待。

第四個函數,GetWindowClassForUIElement,獲得到IAccessible 的指針,使用 WindowFromAccessibleObject函數得到與該指針對應的窗口的句柄。 WindowFromAccessibleObject只使用IAccessible而非IAccessible interface/child ID對的 事實意味著當一個可訪問的元素自己不支持IAccessible時,父親的IAccessible表示了該元 素,而該元素具有和父親相同的類。調用Win32函數GetClassName來搜索Window類名。

監視WinEvents

監視WinEvents非常類似於使用Windows hooks的Windows消息 監視。然而重要的區別是當監視WinEvents的動作被UI元素從另一個進程中啟動時,你不必創 建一個單獨的DLL加載進程地址空間。

下面是hooking的兩種選擇:通過選擇相應的 SetWinEventHook函數的最後參數,你可以在上下文之外或者之中設置掛鉤。當在上下文之外 掛鉤時,其它DLL是沒有必要的,回調函數的代碼在目標進程之外運行。當在上下文之中掛鉤 時,回調函數的代碼在要求的其它DLL中,而此DLL加載到目標進程的地址空間中。第二種情 況寫代碼比較困難,但是效率更高一些。

SetWinEventHook函數使用WinEventProc回 調函數來設置WinEvent hook。一旦設置了這個掛鉤,每次產生WinEvent時,WinEventProc函 數都將被系統調用。

在我的例程裡,我檢查是否Find: All Files窗口存在。如果不 存在,我就設置WinEvent 掛鉤去守候直到該窗口創建。如果窗口存在,我就發送 WM_TARGET_WINDOW_FOUND消息通知主線程可以訪問該窗口了。下面從WinMain取出的代碼演示 了本方法:

if(NULL == (hWndMainWindow = FindWindow(NULL, szMainTitle)))
{
hEventHook = SetWinEventHook(
EVENT_MIN,     // eventMin ID
EVENT_MAX,    // eventMax ID
NULL,       // always NULL for outprocess hook
WinEventProc,   // call back function
0,        // idProcess
0,        // idThread
WINEVENT_SKIPOWNPROCESS |  // always the same for
WINEVENT_OUTOFCONTEXT);    //outproc hook
}
else
PostThreadMessage(GetCurrentThreadId (),
WM_TARGET_WINDOW_FOUND, 0, 0);

 

因為我將0作為第5、6個參數傳 給SetEventHook,回調函數WinEventProc將因為系統中所有激發WinEvent的進程或線程而被 調用。

不是所有的UI元素在激發WinEvents時都表現出相同的行為,有些在創建之時 可能並不激發自己的EVENT_ OBJECT_CREATE事件,而是激發一個告訴你其父親正在被創建的 事件。有些窗口只會在第一次創建時激發自己的EVENT_ OBJECT_CREATE事件,接著在關閉時 激發EVENT_OBJECT_HIDE事件,在重新開始後激發EVENT_OBJECT_SHOW事件。在開發WinEvent 支持時使用ActiveAccessibilitySDK中的AccessibleEventWatcher工具來檢查事件,通常是 個好主意。在例程中,我對EVENT_OBJECT_ CREATE和EVENT_OBJECT_SHOW事件感興趣是因為它 們在一個可訪問元素(在此例中是一個窗口)創建或者變得可見時被激發。因為我希望回調 函數只看到窗口彈出來,它在觸發的事件不是EVENT_OBJECT_ CREATE和EVENT_OBJECT_SHOW, 或者相應元素的窗口指針是NULL時返回,

另一種途徑是通過使用SetEventHook函數的 第1、2個參數來過濾掉將在回調函數中進行處理的WinEvents。這兩個參數定義了一個包含應 被處理的WinEvents的事件范圍。當你確切的知道這個范圍時,本方法才可用。但如果你還沒 有完成程序的最終設計,我建議你不要在回調函數中過濾事件。

有兩個例外。第一個 是是否存在性能問題,但這通常不是問題。另一個發生在可訪問的元素(或者COM服務器,更 准確的說)在處理特定的事件時,就不處理從回掉函數中執行的Active Accessibility函數 調用。在使用OLEACC.DLL支持的控件時這種情形通常不會發生。當UI元素內在的支持 IAccessible時,程序開發者完全負責這種支持的實現。不良實現會導致問題,因此你可以過 濾掉在SetWinEventHook調用中會導致問題的事件。

一個回調函數當它在上下文之外 運行時被異步的調用,此時,從回調函數中調用IAccessible方法時要非常小心,因為當調用 IAccessible方法時,相應的UI元素也許不再存在了。特別是要留心標志著激發此事件的UI元 素已經不存在的EVENT_OBJECT_DELETE事件。此時,後面將要討論到的 AccessibleObjectFromEvent函數將返回一個錯誤。

同時,當你從一個回調函數中調 用Iaccessible方法而此IAccessible方法代表一個激發此事件的基於HWND的元素時,你可能 會想首先調用Win32的IsWindowVisible函數,因為隱藏的UI元素可能並沒有完全的初始化。

獲得IsWindowVisible的指針

有4種方法得到IsWindowVisible接口的指針。一 個是從WinEventProc函數中調用Active Accessibility函數AccessibleObjectFromEvent。 AccessibleObjectFromEvent函數中的所有參數被傳遞給WinEventProc。Out參數由激發此事 件的UI元素相應的IAccessible 接口/子 ID對組成。

在例子代碼中,當程序檢測到 EVENT_OBJECT_CREATE 或者EVENT_OBJECT_SHOW 事件被一個窗口激發時調用 AccessibleObjectFromEvent。與此對相應的可訪問的元素的名字是激活的窗口的標題。如果 此標題和szMainTitle——你正在等待的標題相符合——就向當前線程 發送WM_TARGET_WINDOW_FOUND消息通知它窗口出現了。

另外兩種得到Iaccessible接 口指針的方法是利用AccessibleObjectFromPoint 和 AccessibleObjectFromWindow函數。 AccessibleObjectFromPoint函數為處於第一個POINT參數定義的位置的UI元素返回一個 Iaccessible接口/子 ID對。AccessibleObjectFromWindow返回一個表示一個窗口、客戶區域 或者非客戶區控制的指針,到底代表什麼,取決於第二個參數:對象ID。此參數中的對象ID 值可以是一個標准的對象標示常量或者一個自定義的對象ID。AccessibleObjectFromWindow 返回的指針中用到的子ID總是等於CHILDID_SELF。例程中演示了 AccessibleObjectFromWindow的用法,它是在得到Find: All Files 對話框的窗口句柄後 WinMain的第一個調用,這個函數返回表示發現的窗口的IAccessible的指針。

if (S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow,
OBJID_WINDOW,IID_IAccessible,(void**)&paccMainWindow)))
{
•••
}

 

因為Iaccessible接口的數量比窗口要多,使 用Win32函數來搜索一個窗口將會比使用Active Accessibility樹搜索與該窗口相應的 IAccessible 接口要占用少得多的時間。這就意味著為了提高性能,你應該使用FindWindow 和EnumWindows 這樣的Win32函數來找到與希望的UI元素最接近的窗口,然後使用Active Accessibility導航樹得到IAccessible接口/子ID對。

當然,在權衡Win32函數和 Active Accessibility函數時,上面的規則只是使用它們的一般標准而不能盲目的遵照執行 ,重要的是理解它們的本來意義。例如,如果你需要訪問屬於同一對話框的幾個控件,你會 嘗試搜索各控件的窗口句柄,接著使用AccessibleObjectFromWindow,如果這些控件是基於 HWND的。然而,一個更為一般的方法是找到此對話框的窗口句柄。一旦父窗口找到了,調用 AccessibleObjectFromWindow函數得到該窗口對應的IAccessible接口的指針,然後做為一個 可訪問的父親順籐摸瓜找到屬於這個對話框的UI元素。

搜索Iaccessible接口/子ID對 的第四種方法借助父親/子Active Accessibility樹導航。當開發自定義、owner drawn或者 無窗口的控件時,為同一窗口的每個角色-名字指定獨一無二的表示是一個非常好的編程習慣 。然而,如果由於某種原因,同一窗口中的2個UI元素具有同樣的角色-名字對,那麼就需要 增加一個參數——windows類——以唯一的來表示這個元素。 FindChild函數顯示了一個基於Active Accessibility父/子導航的搜索例程的實現。這個函 數有6個參數。前4個包含傳遞給函數的信息,後2個包含了Iaccessible接口/子ID對。

第一個參數是指向父親的Iaccessible接口的指針。當FindChild被第一次調用時,這 是UI元素所屬的窗口對應的Iaccessible接口的指針。FindChild函數接著就被支持自己的 Iaccessible接口的可訪問的元素遞歸調用。接下來的3個參數描述了你正搜索的UI元素的名 字、角色和類。如果這些參數之一為NULL,搜索例程跳過基於此參數的檢查。

FindChild包括下列步驟。首先,它找到父親的子個數。接著,搜索子ID,在此ID基 礎上,找到Iaccessible接口/子ID對。有了這個對,FindChild進一步得到每個子的名字、角 色和類的名字,並將其與請求的值加以比較。如果匹配,FindChild就返回。注意,比較角色 和狀態正確的方法是作為整數,而非字符串。接下來你的程序在角色和狀態字符串變化時也 能正確的工作,在本地平台上運行時亦然。這些屬性的字符串值對於你調試一個程序是非常 方便的,例如利用在例子代碼中演示的OutputDebugString。

如果希望的可訪問的元 素沒有找到,子支持它自己的Iaccessible接口,遞歸的調用FindChild,並將子的 Iaccessible指針作為第一個參數。

列舉子的第一個步驟是檢查可訪問的UI元素(其 Iaccessible指針作為父親送往FindChild)支持IEnumVARIANT接口。如果支持,進行查詢並 重置:

hr = paccParent -> QueryInterface
(IID_IEnumVARIANT, (PVOID*) & pEnum);
if(hr == S_OK && pEnum) pEnum -> Reset ();

 

下一步是調用get_accChildCount函數,取得父親擁有的可訪問的子的數 目。然後,對每個子,找到其ID並檢查是否支持Iaccessible接口,這通過獲取Idispatch接 口的指針,向它查詢Iaccessible接口完成。

如果一個父親支持IEnumVARIANT接口, 可以調用Next方法,它或者返回一個指向Idispatch接口的指針,或者返回一個子ID。如果一 個父親不支持IEnumVARIANT接口,子ID就是它的序號或者索引號。對於後一種情況,或者 Next方法返回了一個ID號,你就可以調用get_accChild。這個函數有兩個用途,第一,檢查 一個子是否支持Iaccessible接口,第二,檢索該Iaccessible接口相應的Idispatch的指針。 為了使你的程序正確工作,你應該總是檢查是否一個UI元素支持自己的Iaccessible接口。如 果支持,就使用它的接口,而非父親的。圖4的代碼演示了這個思路。

一旦你有了 Idispatch的指針,或者從Next,或者從get_accChild,查詢其Iaccessible接口。如果該接 口的指針不為NULL,那麼IAccessible接口/child ID對將作為第一個組件,而第二個組件就 是CHILDID_SELF。如果該接口的指針為NULL,那麼在定義子的對中使用父親的IAccessible接 口。此時,子ID或者是Next方法返回的一個值,或者是子的序號。下面的代碼取自FindChild ,說明了這一點:

if(paccChild)
{
VariantInit (&varChild);
varChild.vt = VT_I4;
varChild.lVal = CHILDID_SELF;
}
else
{
paccChild = paccParent;
paccChild->AddRef();
}

 

從這裡開始,編程變得容易了。你有了表示一個可訪問的UI元素的 IAccessible 接口/子 ID對,你也有了搜索該元素一個名字、角色、類和狀態的方法,你要 做的只是調用這些方法以及加以正確的檢驗。在我的例程中,跳過了有不可訪問狀態的元素 。

在元素上執行的動作

IAccessible 接口/子 ID對打開了調用Iaccessible方 法的大門,例如get_accDescription能取得UI元素的描述,get_accValue能取得一個值,等 等。最重要的函數之一是accDoDefaultAction。每個可訪問的UI元素都有一個缺省定義的動 作。例如,一個按鈕的缺省動作是“按下這個按鈕”,一個檢查框的缺省動作是 “不選”。為了確定一個元素的缺省動作,請參考Active Accessibility文檔或 者調用get_accDefaultAction。

例程通過調用accDoDefaultAction點擊Find Now按鈕 ,如下所示:

if(FindChild (paccMainWindow, lpstrName, "push button",
"Button", &paccControl, &varControl))
{
hr = paccControl->accDoDefaultAction(varControl);
paccControl- >Release();
VariantClear(&varControl);
}

 

你可以將 Active Accessibility技術與標准的基於Windows的編程技術結合起來。設置Containing text:編輯框和Named:組合列表框的一個值的代碼說明了這一點:

if(FindChild (paccMainWindow, "Containing text:", "editable
text", "Edit", &paccControl, &varControl))
{
WindowFromAccessibleObject(paccControl, &hWnd);
SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)"My Text");
paccControl->Release();
VariantClear(&varControl);
}

 

模擬鍵盤和鼠標輸入

讓 我們假設你需要操作一個新的不完全支持Windows消息和Iaccessible接口方法的UI元素。如 果它不支持你需要的消息和方法,最簡單的解決之道是鍵盤和鼠標輸入模擬。例如,你可以 使用Tab模擬轉移到期望的控件。

使你做到這一點的函數是SendInput,它是一個一般 的USER API。盡管不是Active Accessibility的一部分,將它與Active Accessibility結合 使用非常自然。SendInput需要三個參數:要執行的鍵盤和鼠標動作的個數,INPUT結構數組 ,INPUT結構數組的大小。每個INPUT元素詳細描述了一個要執行的動作。注意,按下一個按 鈕接著釋放它是兩個不同的動作,因此要建立兩個INPUT元素。在例程中,SendInput函數用 於模擬關閉Find: All Files 對話框的Alt-F4鍵盤序列:

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;
input[0].ki.time = input[1].ki.time =
input[2].ki.time = input[3].ki.time = GetTickCount();
SendInput(4, input, sizeof (INPUT));

 

在這個代碼片斷中,input[ ].type = INPUT_KEYBOARD表明要模擬 鍵盤輸入,input[ ].ki.dwFlags和input[ ].ki.wVk表明要執行的動作以及此動作的受鍵。 特別要注意下面的語句:

input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;

沒有這條語句,按下的鍵永遠不會自動釋放。

每次你調用 SendInput模擬鍵盤輸入,都可以使用GetAsyncKeyState來檢查一個鍵的狀態,使用 BlockInput來阻塞可能干擾SendInput的鍵盤和鼠標輸入。

問題和對策

因為 Active Accessibility是一個相對來說比較新的技術,在實際運用中還有一些缺陷,大多數 問題的原因是有許許多多的控件只部分支持——或者干脆不支持 ——Active Accessibility。本節最適合於內在的支持Iaccessible接口,以及那 些不被OLEACC.DLL支持的控件。因為Iaccessible接口支持取決於軟件產品的開發者,所以不 能保證一個具體的產品與Active Accessibility完全兼容。為了達到完全的兼容,一個程序 必須使用支持Active Accessibility的控件,而且它的UI元素必須支持3個關鍵特性:

●獲得UI元素信息(Iaccessible特性)的方法

●在UI元素上執行一個動作的 方法

●適時的激發WinEvents,例如當它的狀態或者值變化時

Active Accessibility設計來方便殘疾人士使用軟件,獲得UI元素信息應該得到最普遍的支持,這是 因為,諸如Microsoft Narrator(與Window 2000一起提供)這樣的工具的主要目的是提供有 關特定UI元素的信息。

典型的,第三個特性,激發WinEvents往往是只得到部分滿足 的,或者說,只有關鍵基本的事件得到支持。例如,EVENT_OBJECT_FOCUS和 EVENT_OBJECT_SELECTION事件非常重要,因為在這兩個事件基礎上,你才能得到哪一個控件 具有鍵盤焦點,哪一個被選中的信息。

因此,在兩種情形下Active Accessibility不 能被充分利用。第一個是,有些應用具有自定義、owner drawn或無窗口的控件,它們不具內 在支持Iaccessible接口的能力。第二個是,一些程序只支持Iaccessible 的“讀信息 ”方法。在第一種情況下,所有你能做的是象前面描述的那樣模擬鍵盤和鼠標輸入。在 寫的很好的軟件中,你可以使用加速鍵,或者使用熱鍵和Tab鍵導航到控件。當期望的控件擁 有焦點時,你要做的是模擬Enter鍵按下的動作。本方法的缺點是一旦它所驅動的軟件改變了 熱鍵、加速鍵或者Tab鍵的順序,你的程序將不再正確的工作。當新版本的軟件發行時,這種 情況就會發生,如果你在開發一個測試程序,這會成為很嚴重的問題,因為你必須使用軟件 的新版本。

如果驅動程序的控件支持Iaccessible的讀信息方法,你就可以避免這種 問題。這使你使用Active Accessibility導航到一個UI元素,在例程的FindChild函數中我們 已經說明了。一旦你獲得了正在尋找的這個UI元素的Iaccessible接口/子 ID對,你可以調用 Iaccessible方法的accLocation來獲得UI元素的坐標,光標落在該元素上的位置,進而模擬 鼠標動作。當然,如果可訪問的元素的名字、角色和其它屬性改變了,一個測試程序將不再 正確的工作。但是,這種情形比起改變控件或者控件項目的次序來發生的可能要小,因此使 得利用Iaccessible的讀信息方法的途徑較之用鍵盤導航更為可靠。

通常,鼠標動作 是一個按鈕點擊從一個列表框中選擇一個條目,設置一個編輯框為焦點等等。一旦設置了焦 點,你就可以用鍵盤輸入其值了。

圖5的代碼說明了如何單擊一個按鈕,它假定相應 的IAccessible 接口/子 ID對已經確定了。對這段代碼需要說明兩個問題:第一,當 SendInput(n, input, sizeof(INPUT))被調用且n大於1時幾個控件不能正確的工作。換句話 說,每個鍵盤或者鼠標動作應該單獨的發送,應該允許一段時延來處理此動作。代碼段的最 後3行反映了這一點。第二,如果你希望代碼既適用於右手也適用於左手鼠標,你可以添加下 面簡單的代碼:

if (GetSystemMetrics(SM_SWAPBUTTON))
{
input [0].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
input[1].mi.dwFlags = MOUSEEVENTF_RIGHTUP;
}

 

使用此法唯一的限制是動作的承受控件必須是 可見的,並且不被其它窗口遮擋。如果一個Iaccessible方法用於執行這個動作,本限制取決 於方法的具體實現。有些實現可能內在的使用SendInput,因此本限制是適用的。其它的實現 可能使用不同的技術,也許能處理不可見的或被遮擋的元素。

另一個你在使用Active Accessibility時可能遇到的問題是,如果主調線程是用COINIT_MULTITHREADED初始化的, WindowFromAccessibleObject功能對有些控件不工作,這是因為COM不允許從多線程的部件中 發出對同步輸入方法的部件間調用。這些方法在.idl文件中用[input_sync]加以標志。注意 這些方法可以從單線程的部件中調用。

因為WindowFromAccessibleObject調用標以 [input_sync]的方法,所以如果主調線程是用CoInitializeEx(COINIT_MULTITHREADED)初始 化的,它不適用於存在於主調線程之外的UI元素。WindowFromAccessibleObject函數只適用 於被OLEACC.DLL支持的控件。

這一點的一個對策是一個助手線程的用法。這個線程是 用CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)初始化的,在主線程中,如果一個對 WindowFromAccessibleObject的調用失敗了,Iaccessible的指針轉給助手線程。接著,主線 程向助手線程發送一個用戶消息,通知它需要一個給定Iaccessible指針的窗口句柄。然後, 主線程等待一個事件。簡化的主線程代碼(沒有進一步檢驗)顯示在圖6中。在這個函數裡, pAcc是表示一個窗口的Iaccessible的指針,該窗口的句柄必須確定,phwnd就是指向這個句 柄的指針。

當助手線程接受到用戶信息以後,它將指針轉交給Iaccessible,調用 WindowFromAccessibleObject,然後發送一個事件指示它已經結束了。助手進程簡化的代碼 見圖7。在下面的例子裡,STA_WFAO_PARAMS結構聲明為:

struct STA_WFAO_PARAMS
{
IStream *pstm;
HWND *phwnd;
HRESULT hr;
};

 

這個事件的句柄,g_STA_hEv,創建如下:

g_STA_hEv = CreateEvent
( NULL, FALSE, FALSE, TEXT ("STA_ThreadEvent"));

 

這段代碼假定只有一個主(或調用)線程 。在多調用線程情況下,應該加入組織多個一個隊列中的多個請求的代碼。

我將討論 的Active Accessibility的最後一個問題是,有時COM服務器不處理從一個WinEvent 回調函 數中執行的Active Accessibility函數調用,這種情況在WinEvent處理某些事件時發生。在 使用不同的應用時,我曾經遇到過幾次這種情況。最大的可能是服務器的實現問題。

一個對策是過濾導致問題的WinEvent,在監視WinEvent一節中我曾經描述過。另一種解決辦 法是盡可能調用Win32函數,而不是Iaccessible方法。例如你可以調用GetWindowText (hwndMsg, szObjName, sizeof(szObjName)),來代替get_accName。窗口句柄hwndMsg是傳給 WinEvent回調函數的第3個參數。這裡的想法是調用Win32 APIs較之調用Iaccessible方法更 為安全,因為後者可能會是有問題的實現。盡管Win32 APIs不適用於完全自定義的控件,至 少你的應用不會崩潰或者毀掉目標軟件。因此,這個辦法將只適用於HWND控件。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved