在Qt中使用WindowsAPI來控制其他應用程式
有時候我們可能想要讓一些桌面應用程式自動地執行一些操作,但是這類程式又沒有提供一些批量操作的方法或者程式設計介面。這時最容易的辦法恐怕就是使用WindowsAPI來模擬滑鼠或鍵盤的動作來操作這類應用程式了。
假設我們想操縱的應用程式是一個上傳檔案到網路的程式,整個流程是:登入--->選擇要上傳的檔案--->填寫檔案資訊--->上傳。原本這個程式一次只能選擇一個檔案,只能填寫一個檔案的資訊,只能上傳一個檔案。當檔案很多,比如你要上傳整個資料夾時,這顯然很麻煩。我們可以先將待上傳的檔案及其檔案資訊做成一個待上傳檔案表,然後控制這個程式迴圈執行上傳單一檔案時的操作,講待上傳檔案表中的檔案批量上傳。
要實現這些只需在Qt程式的標頭檔案中包含windows.h檔案,如果程式中使用了Windows Common Controls[1],那麼還需包含commctrl.h這個標頭檔案。
使用windows7+Qt4.7+Mingw+QtCreator。要使用的函式有以下幾個:
1.HWND FindWindowExA(HWND hwndParent, HWND hwndChildAfter, LPCTSTR lpszClass, LPCTSTR lpszWindow);[2]
函式功能:該函式獲得一個視窗的控制代碼,該視窗的類名和視窗名與給定的字串相匹配。這個函式查詢子視窗,從排在給定的子視窗後面的下一個子視窗開始。在查詢時不區分大小寫。
引數:
hwndParent:要查詢子視窗的父視窗控制代碼。如果hwndParent為0,則函式以桌面視窗為父視窗,查詢桌面視窗的所有子視窗。
hwndChildAfter:子視窗控制代碼。查詢從在中的下一個子視窗開始(該引數可列舉子視窗)。如果HwndChildAfter為0,查詢從hwndParent的第一個子視窗開始。如果 hwndParent 和 hwndChildAfter 同時為0,則函式查詢所有的頂層視窗。
lpszClass:指向一個指定的類名。
lpszWindow:指向一個指定的視窗標題。
返回值:如果函式成功,返回值為具有指定類名和視窗名的視窗控制代碼。如果函式失敗,返回值為0。
2.HWND GetDlgItem(HWND hDlg, int nIDDlgItem);[3]
函式功能:該函式檢索指定的對話方塊中的控制元件控制代碼。
引數:
hDlg:標識含有控制元件的對話方塊。
nlDDlgltem:指定將被檢索的控制元件識別符號。
返回值:如果函式呼叫成功則返回值為給定控制元件的視窗控制代碼。如果函式呼叫失敗,則返回值為NULL,表示為一個無效的對話方塊控制代碼或一個不存在的控制元件。若想獲得更多錯誤資訊,請呼叫GetLastError函式。
備註:可以對任何父子視窗來使用GetDlgltem函式,而不僅只是對話方塊。只要hDlg引數指定一個父視窗,且子視窗有一個獨立的識別符號(象CreateWindow中hMenu引數指定的或建立子視窗的CreateWindowEx指定的那樣),GetDlgltem就會返回一個有效的控制代碼到子視窗。
3.LRESULT SendMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM IParam);
函式功能:該函式將指定的訊息傳送到一個或多個視窗。此函式為指定的視窗呼叫視窗程式,直到視窗程式處理完訊息再返回。而和函式PostMessage不同,PostMessage是將一個訊息寄送到一個執行緒的訊息佇列後就立即返回。
引數:
hWnd:其視窗程式將接收訊息的視窗的控制代碼。如果此引數為HWND_BROADCAST,則訊息將被髮送到系統中所有頂層視窗,包括無效或不可見的非自身擁有的視窗、被覆蓋的視窗和彈出式視窗,但訊息不被髮送到子視窗。
Msg:指定被髮送的訊息。
wParam:指定附加的訊息特定資訊。
IParam:指定附加的訊息特定資訊。
返回值:返回值指定訊息處理的結果,依賴於所傳送的訊息。
很簡單吧?僅此3個函式。反覆呼叫即可實現模擬滑鼠鍵盤動作操縱其他程式的功能。呼叫這些函式時的控制代碼、標示符、視窗名、訊息附加資訊等引數從哪兒來呢?這還需要Visual Studio中的Spy++[4]這款工具。相關用法可以看《如何使用spy ++ (How to use Spy ++)》(http://www.cnblogs.com/index/archive/2005/03/29/127619.html)等文章。
有了以上準備我們一步步來控制這個上傳檔案的程式。整個程式在QtCreator中完成,原始檔使用System編碼。在main.cpp中加入"QTextCodec::setCodecForTr(QTextCodec::codecForName("GBK"));"、"QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));"、"QTextCodec::setCodecForCStrings(QTextCodec::codecForName("GBK"));"三句來解決亂碼問題。
1)根據程式所在路徑開啟應用程式:
QString fileName=ui->lineEdit_Path->text();//獲取程式可執行檔案的路徑
QProcess *process=new QProcess;
QDir::setCurrent(QFileInfo(fileName).path());//進入程式所在資料夾
process->start(QFileInfo(fileName).fileName());//執行程式
if(process->state()!=QProcess::NotRunning)//如果程式執行成功返回true,失敗返回false
return true;
else
return false;
2)自動登入(假設使用者名稱和密碼已儲存在被操縱的程式中,點選“登入”即可):
HWND pWnd = NULL;
//程式啟動可能較慢,需要嘗試多次才能找到登入視窗的控制代碼
while(pWnd == NULL)
{
Sleep(3000);//每次檢測間隔3秒鐘
pWnd = ::FindWindowExA(NULL,NULL,0,"某程式 登入視窗");
}
//向目標視窗傳送點選"登入"按鈕的訊息
::SendMessageA(::GetDlgItem(pWnd,0x00013C76),BM_CLICK,0,0);
其中引數"0x00013C76"就是該程式登入框中“登入”按鈕的識別符號。是通過Spy++看到的。
3)選擇要上傳的檔案
3.1)點選“上傳檔案”按鈕(實際是啟用"上傳檔案"對話方塊):
//找到"上傳檔案"對話方塊
//"上傳檔案"對話方塊用的是另一個視窗,與程式主視窗不一樣,程式主視窗只是負責將其啟用
//因此要使用其他的方法獲取其控制代碼,並激活
//由於程序中可能包含多個對話方塊類,所以僅僅靠"#32770"不一定能找到通過主程式啟用的那個對話方塊
//因此要進一步通過對話方塊中的內容來判斷是否是我們要找的對話方塊
//因為該程式開啟的對話方塊中包含一個“同意”按鈕,我們就以認為包含這個按鈕的對話方塊就是我們要找的對話方塊
bool isFind=false;
HWND curWnd=NULL;
while(!isFind){
curWnd=FindWindowExA(0,curWnd,"#32770",0); //尋找對話方塊類
//若該類中包含"Button"類,且Button名為"同意",則curWnd即為"上傳檔案"對話方塊的控制代碼
HWND tWnd=NULL;
tWnd=FindWindowExA(curWnd,0,"Button","同意");
if(tWnd!=NULL)
isFind=true;
}
//啟用"上傳檔案"對話方塊
ShowWindow(curWnd,SW_SHOWNORMAL);
//點選"新增檔案"按鈕
::PostMessage(curWnd,WM_COMMAND,0x0000CFB5,0);
這裡為何要用WM_COMMAND呢?因為當時用Spy++監測到的就是這個。。。之後會彈出“開啟”對話方塊
3.2)選擇檔案
//尋找"開啟"對話方塊
HWND childHWND=NULL;
while(childHWND == NULL)
{
Sleep(3000);
childHWND=FindWindowExA(0,0,"#32770","開啟");
}
//尋找"ComboBoxEx32"控制元件,在這個控制元件中可輸入要開啟的檔案路徑、檔名
HWND childHWND_ComboBoxEx32=NULL;
childHWND_ComboBoxEx32=FindWindowExA(childHWND,0,"ComboBoxEx32",0);
//尋找"Button","開啟(&O)"控制元件
HWND childHWND_Button=NULL;
childHWND_Button=FindWindowExA(childHWND,0,"Button","開啟(&O)");
//輸入要上傳的檔案所在的目錄
QString filePath=QDir::toNativeSeparators(_filePath_);
QByteArray t1=filePath.toLocal8Bit();
char *_filePath=t1.data();//以上3行將QString轉換為char*。為保證不出現中文亂碼,必須這麼轉換
::SendMessageA(childHWND_ComboBoxEx32,WM_SETTEXT,0,(LPARAM)_filePath);//填寫待上傳檔案所在路徑
//點選"開啟"對話方塊的"開啟"Button,開啟要上傳的檔案所在的目錄
::SendMessageA(childHWND_Button,BM_CLICK,0,0);
//輸入要上傳的檔名
QByteArray t2=fileName.toLocal8Bit();
char *_fileName=t2.data();
::SendMessageA(childHWND_ComboBoxEx32,WM_SETTEXT,0,(LPARAM)_fileName);
//點選"開啟"對話方塊的"開啟"Button,開啟要上傳的檔案
::SendMessageA(childHWND_Button,BM_CLICK,0,0);
這樣選擇待上傳檔案的過程就完成了。“開啟”對話方塊會關閉,“上傳檔案”對話方塊再次被啟用。這裡為何要先進入檔案所在目錄再選擇檔案呢?因為這樣比直接輸入檔案的絕對路徑然後再點選“開啟”要穩定。
4)填寫檔案資訊
4.1)選擇檔案型別(找到對應的單選框,點選)
QByteArray t4=Category.toLocal8Bit();
char *_Category=t4.data();
//獲取相應Category的控制代碼,並選擇
childHWND=::FindWindowExA(curWnd,0,"Button",_Category);
::SendMessageA(childHWND,BM_CLICK,0,0);
4.2)輸入檔案描述資訊QByteArray t10=Introduce.toLocal8Bit();
char *_Introduce=t10.data();
//獲取相應的Introduce(介紹)控制代碼,並輸入介紹
childHWND=::GetDlgItem(curWnd,0x0000CF91);
::SendMessageA(childHWND,WM_SETTEXT,0,(LPARAM)_Introduce);
5)點選“開始上傳”按鈕,上傳檔案
childHWND=::GetDlgItem(curWnd,0x0000CFB3);
::SendMessageA(childHWND,BM_CLICK,0,0);
只要在程式中不斷重複這幾步,就可以自動批量上傳檔案啦。簡單來說就是找到要控制的控制代碼,然後向它傳送滑鼠點選或者文字輸入等訊息。當你知道控制代碼的識別符號時,可以直接用GetDlgItem函式獲取控制代碼。當你只知道控制代碼的類名或者視窗名時,可用FindWindowExA函式來獲取控制代碼。至於傳送訊息,則可用SendMessageA或者PostMessage函式。
Win32 API中本身提供了Windows下許多常用的控制元件,稱為Common Controls。 這些控制元件與Button、ComboBox等控制元件不同,不是在user32.dll中實現,而是在Comctrl32.dll中實現,相關的C++原型宣告在commctrl.h中。所以,在使用Win32 API編寫Windows視窗應用程式時,如果在介面上用到了Common Controls,則必須在連結選項中包含comctrl32.lib庫,並在程式初始化時呼叫InitCommonControls()函式,確保控制元件被載入。InitCommonControls()函式在commctrl.h中宣告,因此程式中需包含該標頭檔案。Common Controls列表如下:Animation、ComboBoxEx、Date_and_Time_Picker、Drag_List_Box、Flat_Scroll_Bar、Header、HotKey、ImageList、IPAddress、List_View、Month_Calendar、Pager、Progress_Bar、Property_Sheets、Rebar、Status Bars、SysLink、Tab、Toolbar、ToolTip、Trackbar、TreeView、Up_and_Down。