Active Accessibility(2)

 
 
IAccessible 接口上執行動作
   
有了表示一個可訪問的 UI 元素的 IAccessible 接口/ID對,你也有了搜索該元素一個名字(get_accName)、角色(get_accRole)、類和狀態(get_accState)的方法。讓我們看看你還可以幹什麼!get_accDescription 能取得UI元素的描述,get_accValue 能取得一個值。
   
最重要的函數之一是 accDoDefaultAction。每個可訪問的UI元素都有一個缺省定義的動作。例如,一個按鈕的缺省動作是"按下這個按鈕",一個檢查框的缺省動作是"不選"。爲了確定一個元素的缺省動作,請參考 Active Accessibility 文檔或者調用 get_accDefaultAction
   
如果我想起動註冊表編輯器,該怎麼辦呢?如果是我們手動做的話,無非是在文本輸入框輸入"regedit",然後按確定按鈕,就這麼簡單。下面我們來看看用 Active Accessibility 是怎麼來實現的。

//在文本輸入框輸入"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);

}

現在,你會發現已經成功啓動了註冊表編輯器!!

 
模擬鍵盤和鼠標輸入
   
讓我們假設你需要操作一個新的不完全支持 Windows 消息和 IAccessible 接口方法的 UI 元素。如果它不支持你需要的消息和方法,最簡單的解決辦法就是模擬鍵盤和鼠標輸入。例如,你可以用Tab模擬轉移到期望的控件。
   
使你能夠實現這些的函數就是 SendInput 一個一般的USER API。雖然不屬於Active Accessibility,把他們聯合使用很自然。
    SendInput
接受三個參數:要執行的鼠標鍵盤動作個數、INPUT結構數組和結構數組的大小。每個INPUT結構描述一個要執行的動作。注意,按下一個按鈕和釋放一個按鈕是兩個不同的動作,所以必須創建兩個不同的INPUT結構。
下面的代碼將模擬 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;

 

SendInput(4, input, sizeof(INPUT));

具體用法大家還是查MSDN吧,這裏就不羅嗦了!!:)

 
監視WinEvents
   
監視 WinEvents 非常像通過 Windows Hook 監視 Windows 消息。最重要的區別就是從另一個進程監視 UI 元素髮出的 WinEvents 時,你不需要創建一個單獨的DLL來注入那個進程的地址空間。
   
監視 WinEvents 有兩種選擇:通過設置 SetWinEventHook 函數的最後一個參數來確定是在上下文之外還是之內監視。如果是在上下文之外,不需要額外的DLL,回調函數運行在目標進程之外。如果是在上下文之內,回調函數必須放在額外的DLL,並注入目標進程的地址空間。第二種方法寫代碼比較麻煩,但是運行效率高。
   
好,現在回到上面的例子。上面例子能夠執行的前提條件是能夠找到標題爲"運行"的窗口。現在可以先檢查運行窗口是否存在,如果不存在就設置WinEvents 鉤子去監視,直到"運行"窗口被創建。看下面代碼:

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);

}   

第一、二個參數用來指定監視事件的範圍。第四個參數是定義的回調函數。
下面是回調函數:

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;

}   

…………,一個應用基本成型了,雖然比較簡單。就先寫這麼多吧,請關注後續介紹。

 
附錄:

關於IAccessible 接口/ID對:
   
讓我們來考慮這樣一個控件,他支持 IAccessible 接口並且包含一些子控件,比如 listbox 就包含很多 items 。有兩種方法讓他可以被訪問:第一種,提供listbox IAccessible 接口和每一個 item 自己的 IAccessible 接口。另一種是隻提供一個控件的 IAccessible 接口,這個接口能夠提供基於某種識別方法來訪問每一個子控件的功能。
   
第一種方法,需要爲這個控件和每一個子控件創建單獨的 COM 對象,這會比第二種方法(每一個子控件不支持自己的 IAccessible 接口,而是通過父接口來訪問)增加內存消耗。第二種方法裏,通過增加一個參數--ID--同父的IAccessible 接口一起表示這個子控件。子ID 是一個 VT_I4 型的 VARIANT 值,包含一個由程序決定的獨特的值,或只是一個子控件的序號。序號意味着第一個子控件的ID1,第二個子控件的ID2,依次增長!
   
這樣,如果一個子控件不支持自己的 IAccessible 接口,而其父控件支持,那麼這個子控件可以用它的父控件的 IAccessible 接口/ID 對來表示。通常,一個支持 IAccessible 接口的父UI元素也是通過這樣的 IAccessible 接口/子對錶示的,這時候其子ID號爲 CHILDID_SELF (就是0)。
   
記住,子ID號總是相對於 IAccessible 接口的。例如,一個可訪問的元素可以同相對於其父 IAccessible 接口的一個非子 CHILDID_SELF ID 及其父IAccessible 接口表示,如果他支持 IAccessible 接口,此元素的子ID就是相對於自己 IAccessible 接口的CHILDID_SELF
   
呵呵,翻譯的有點彆扭,意思就是說,如果這個控件支持 IAccessible 接口,那麼它的子ID就是0CHILDID_SELF),可以用它自己的 IAccessible 接口和0這個對來表示這個控件。如果控件不支持 IAccessible 接口,就用它父控件的 IAccessible 接口,和一個相對於父 IAccessible 接口的子ID來表示。哎呀!!不知道說明白沒有。鬱悶!!!!

 
注:
   
我也是剛開始學習怎麼使用MSAA,但是苦於很難找到中文資料。希望這篇文章對大家能有所幫助。由於瞭解的還很膚淺,錯誤難免,望諒解!!:)
   
還有,這篇文章基本編譯自Dmitri Klementiev的《Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software》,只是按自己的理解重新編排了一下,如果覺得不符合自己的學習習慣可以看原文。並且我的文章省略了很多東西,呵呵。

 
參考資料:

  • 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中的相關章節。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章