Active Accessibility (MSAA) 初衷是協助身障人士使用電腦 (螢幕放大、閱讀器、觸覺滑鼠),實際應用更廣:驅動其他軟體、模擬使用者輸入進行軟體測試。本文從基本概念出發,帶領開發一個測試應用程式,展示如何與常見控制項/UI 元素互動並處理 WinEvents。
IAccessible 介面。IAccessible 介面表示。AccessibleObjectFromPoint、AccessibleObjectFromWindow 等。IAccessible 方法:get_accChild、get_accParent。對測試軟體最重要的三個屬性。例:檔案總管「尋找」對話框中「查找範圍:」組合方塊——名稱「查找範圍:」、角色 combobox、狀態「可見」。

顯示游標指向 UI 元素的屬性,幫助熟悉 MSAA 世界。
WINABLE.H,名稱以 EVENT_OBJECT 或 EVENT_SYSTEM 開頭。WINEVENT_OUTOFCONTEXT) 或進程內 (WINEVENT_INCONTEXT) 執行回呼。非 MSAA 一部分,但結合使用極自然。用於操作不完全支援訊息/IAccessible 方法的新 UI 元素。
核心功能由 OLEACC.DLL 提供。調用回傳 IAccessible 指標的函數時:
IAccessible (由應用程式實作)。OLEACC.DLL 建立 Proxy 實作 IAccessible。大多數 COMCTL32 控制項有 Proxy 支援。理解運行時機制對開發使用標準控制項的應用至關重要——這意味著無需重寫應用即可自動相容 MSAA。名稱基於 Win32 控制項名稱,角色基於功能定義。
每個 UI 元素有 IAccessible 指標與 子 ID 識別碼。所有可存取元素在 MSAA 術語中形成父/子樹。
例:「查找範圍:」組合方塊的祖先是「名稱和位置」屬性頁 (非直接)。

原則:每個基於 HWND 的 UI 元素有兩級 IAccessible:代表整個 HWND 的父級,代表客戶區/非客戶區 (捲軸、選單等) 的子級。被標籤標示的控制項 (如「查找範圍:」組合方塊) 在父/子樹中包含兩個條目:標籤名稱/角色、靜態文字與控制項相關介面。最高層父級為桌面視窗。
對於擁有許多子項的控制項 (如列表方塊),兩種設計:
IAccessible → 增加記憶體消耗 (每個子項建立獨立 COM 物件)。IAccessible,子項透過父介面 + 子 ID 存取。子 ID 為 VT_I4 型別,含程式決定的唯一值或序號 (第一個子項 ID=1 遞增)。支援 IAccessible 的父元素自身亦以此對表示,子 ID 定義為 CHILDID_SELF (值 0)。子 ID 總是相對於特定 IAccessible 介面。
使用 MSAA 功能時,必須提供正確的 IAccessible/子 ID 對。若子項支援自身介面,用其介面/CHILDID_SELF;否則用父介面/子 ID。get_accChild 可檢查子項是否支援自身介面。
範例模擬使用者與「尋找: 所有檔案」對話框互動:用 FindWindow 檢查對話框是否存在,不存在則掛鉤 WinEvents 等待;出現後取得 IAccessible,導航至「包含文字:」編輯框、「命名:」組合方塊設定值,按下「立即尋找」按鈕,最後模擬 Alt-F4 關閉。代碼處理 Win2000 與 NT 4.0/9x 視窗結構差異。
測試程式必須在合理時間內找到目標元素的 IAccessible/子 ID 對。策略:
FindWindow/EnumWindows 找到最近的視窗句柄 (比 MSAA 樹搜尋快得多)。AccessibleObjectFromWindow 取得視窗對應 IAccessible。AccessibleObjectFromWindow 取得父 IAccessible,再順藤摸瓜導航子元素。WinEventProc 回呼中調用,參數傳遞自事件,輸出觸發事件元素的介面/子 ID 對。CHILDID_SELF。範例首次調用即用此法。if (S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, OBJID_WINDOW, IID_IAccessible, (void**)&paccMainWindow)))get_accChildCount)。IEnumVARIANT,支援則用 Next 取得 IDispatch 或子 ID;不支援則子 ID 為序號。get_accChild 檢查子項是否支援自身 IAccessible,支援則用其介面/CHILDID_SELF,否則用父介面/子 ID。IAccessible 的子項。accDoDefaultAction 是關鍵:每個可存取元素有預設動作 (按鈕=按下、核取方塊=切換)。範例按下「立即尋找」:
if(FindChild(paccMainWindow, lpstrName, "push button", "Button", &paccControl, &varControl)) {
hr = paccControl->accDoDefaultAction(varControl);
paccControl->Release(); VariantClear(&varControl);
}
結合 Win32 API 設定編輯框/組合方塊值:
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);
}
範例模擬 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; // Alt
input[1].ki.wVk = input[3].ki.wVk = VK_F4; // 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));
注意:KEYEVENTF_KEYUP 必須設定,否則按鍵永不釋放。每次調用可用 GetAsyncKeyState 檢查鍵狀態、BlockInput 阻擋干擾。
IAccessible 讀取資訊方法,用 accLocation 取得座標模擬滑鼠點擊,較鍵盤導航可靠 (名稱/角色變更機率較 Tab 順序低)。IAccessible 方法實現若內用 SendInput 同受限,其他技術或可處理不可見元素。COINIT_MULTITHREADED 時,WindowFromAccessibleObject 對某些控制項失效 (COM 禁止 MTA 呼叫同步輸入方法)。對策:建立 STA 助手線程 (COINIT_APARTMENTTHREADED),主線程失敗時轉交給助手線程處理。GetWindowText 代替 get_accName),較安全雖不適用全自訂控制項。