Windows窗體數據抓取詳解


最近在客戶項目上剛好遇到一個問題,項目需求是要獲取某台機床的實時狀態,問題點剛好就在於該機床不是傳統意義上的數控機床,也不是PLC控制器,只有一個上傳下載程序文件的應用程序,上面剛好有幾個按鈕可以大概判斷當前工作狀態,轉眼一想,是否可以實時獲取幾個按鈕的狀態,從而簡單分析下就確定機床加工狀態。

說干就干,開始拿起放下已久的Win32API來試試。思路大概如下:

  • 首先,我們知道的是應用程序的進程名稱如:notepad.exe
  • 然后,就要通過進程名獲取窗口句柄(HWND)
  • 其次,通過窗口句柄遍歷子窗口句柄,通過其獲取相關數據,比如:Button是否被可用、Button的Text、CheckButton是否被選中等等一些列想要的操作。此處我們就抓取記事本內容吧(內容在Edit控件中)
  • 最后,就是實時更新、存儲數據即可,進行后期邏輯處理

獲取進程ID

首先當我們知道進程名,通過進程名獲取進程ID,我們需要用到一個Win32的進程快照模塊:CreateToolhelp32Snapshot 可以通過獲取進程信息為指定的進程、進程使用的堆[HEAP]、模塊[MODULE]、線程建立一個快照。

HANDLE WINAPI CreateToolhelp32Snapshot(
  __in          DWORD dwFlags,
  __in          DWORD th32ProcessID
);

參數:

  • dwFlags 用來指定“快照”中需要返回的對象,指定快照中包含的系統內容,這個參數能夠使用下列數值(常量)中的一個或多個。
    1. TH32CS_INHERIT :聲明快照句柄是可繼承的。
    2. TH32CS_SNAPALL : 在快照中包含系統中所有的進程和線程。
    3. TH32CS_SNAPHEAPLIST : 在快照中包含在th32ProcessID中指定的進程的所有的堆。
    4. TH32CS_SNAPMODULE : 在快照中包含在th32ProcessID中指定的進程的所有的模塊。
    5. TH32CS_SNAPPROCESS : 在快照中包含系統中所有的進程。
    6. TH32CS_SNAPTHREAD : 在快照中包含系統中所有的線程。
  • th32ProcessID 指定將要快照的進程ID。如果該參數為0表示快照當前進程。該參數只有在設置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效,在其他情況下該參數被忽略,所有的進程都會被快照。

返回值: 調用成功,返回快照的句柄,調用失敗,返回INVALID_HANDLE_VALUE 。

廢話不多說,直接上代碼:

DWORD	GetProcessIdFromProcessName(std::string processname)
{
	DWORD dwRet = 0;
	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(pe32);
	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProcessSnap != INVALID_HANDLE_VALUE)
	{
		BOOL bMore = ::Process32First(hProcessSnap, &pe32);
		while (bMore)
		{
			if (boost::iequals(pe32.szExeFile, processname))
			{
				dwRet = pe32.th32ProcessID;
			}
			bMore = ::Process32Next(hProcessSnap, &pe32);
		}
		::CloseHandle(hProcessSnap);
	}
	return dwRet;
}

調用測試:

std::string str1 = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(str1);
std::cout << dwProcessId << std::endl; //

進程ID

獲取到了進程,那就進入下一節,獲取窗口句柄。

獲取窗口句柄HWND

通過進程ID獲取窗口句柄,那么就需要遍歷窗口了,找到符合我們需求的進程所對應的窗口句柄了,這個地方就會用到一個函數:EnumWindows 枚舉所有屏幕上的頂層窗口,並將窗口句柄傳送給應用程序定義的回調函數。

BOOL EnumWindows(   
    WNDENUMPROC lpEnumFunc,   
    LPARAM lParam 
); 

參數:

  • lpEnumFunc:指向一個應用程序定義的回調函數指針,請參看EnumWindowsProc。
  • lPararm:指定一個傳遞給回調函數的應用程序定義值。

返回值:如果函數成功,返回值為非零;如果函數失敗,返回值為零。若想獲得更多錯誤信息,請調用GetLastError函數。

回調函數:
BOOL CALLBACK EnumWindowsProc(  
    HWND hwnd,   
    LPARAM lParam 
);

參數:

  • hwnd:頂層窗口的句柄
  • lparam:應用程序定義的一個值(即EnumWindows中lParam)
    返回值:TRUE繼續遍歷,FALSE停止遍歷

注: EnumWindows函數不列舉子窗口。

那么接下來就是開始獲取窗口句柄了。

首先定一個結構體

該機構體由於標記進程和窗口句柄:

typedef struct tagHWNDINFO
{
	DWORD   dwProcessId;
	HWND    hWnd;
} HWNDINFO, *LPHWNDINFO;

枚舉所有窗口

BOOL CALLBACK EnumWindowProc(HWND hWnd, LPARAM lParam)
{
	DWORD dwProcId;
	GetWindowThreadProcessId(hWnd, &dwProcId);
	LPHWNDINFO pInfo = (LPHWNDINFO)lParam;
	if (dwProcId == pInfo->dwProcessId)
	{
		if (GetParent(hWnd) == NULL && IsWindowVisible(hWnd))  //判斷是否頂層窗口並且可見  
		{
			pInfo->hWnd = hWnd; 
			return FALSE;
		}
		else
			return TRUE;
	}

	return TRUE;
}

獲取指定進程ID窗口句柄

HWND GetProcessMainWnd(DWORD dwProcessId)//獲取給定進程ID的窗口HWND
{
	HWNDINFO wi;
	wi.dwProcessId = dwProcessId;
	wi.hWnd = NULL;
	EnumWindows(EnumWindowProc, (LPARAM)&wi);

	return wi.hWnd;
}

調用測試:

std::string processname = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(processname);
std::cout << dwProcessId << std::endl;
if (dwProcessId != 0)
{
    HWND hwnd = GetProcessMainWnd(dwProcessId);
    if (hwnd != NULL)
    {
        char WindowTitle[100] = { 0 };
        ::GetWindowText(hwnd, WindowTitle, 100);
        std::cout << WindowTitle << std::endl;
    }
}	

結果輸出:

11712
無標題 - 記事本

文本內容

現在已經獲取了記事本主窗口句柄了,下一步就是遍歷子窗口了。

遍歷子窗口

我們的目標是抓取窗體中信息,這時候介紹一個工具,相當的好用Spy++(具體怎么用,就自己百度了)

spy++

現在我們要獲取的就是下面的Edit框內容。此處我們又需要遍歷子窗口,需用到一個方法EnumChildWindows 枚舉一個父窗口的所有子窗口。

BOOL EnumChildWindows(          
    HWND hWndParent,
    WNDENUMPROC lpEnumFunc,
    LPARAM lParam
);

參數:

  • hWndParent: 父窗口句柄
  • lpEnumFunc: 回調函數的地址
  • lParam: 自定義的參數

回調函數如下:

BOOL CALLBACK EnumChildProc(          
    HWND hwnd,
    LPARAM lParam
);

參數:

  • hwnd:父窗口指定的一個子窗口句柄
  • lParam:EnumChidWindows指定的參數
    返回值:如果返回TRUE,則枚舉繼續直到枚舉完成;如果返回FALSE,則將會中止枚舉。

直接亮代碼:

BOOL CALLBACK EnumNotepadChildWindowsProc(HWND hWnd, LPARAM lParam)
{
	char szTitle[100] = { 0 };
	::GetWindowText(hWnd, szTitle, 100);

	long lStyle = GetWindowLong(hWnd, GWL_STYLE);
	if (lStyle & ES_MULTILINE)
	{
		long lineCount = SendMessage(hWnd, EM_GETLINECOUNT, 0,0);
		for (int i = 0; i < lineCount; i++)
		{
			//long chCount = SendMessage(hWnd, EM_LINELENGTH, (WPARAM)i, 0);
			char szContent[200] = { 0 };
			szContent[0] = 200; //此處注意下,如果不設置EM_GETLINE無法獲取內容
			long ret = SendMessage(hWnd, EM_GETLINE, (WPARAM)i, (LPARAM)(LPCSTR)szContent);
			if (ret > 0)
			{
				szContent[ret] = '\0';
				std::cout << "line: " << i << ", Content: " << szContent << std::endl;
			}
		}
	}
	else
	{
		std::string title(szTitle);
		if (!title.empty())
			std::cout << title << std::endl;
	}

	return true;
}

調用如下:

std::string processname = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(processname);
std::cout << dwProcessId << std::endl;
if (dwProcessId != 0)
{
    HWND hwnd = GetProcessMainWnd(dwProcessId);
    if (hwnd != NULL)
    {
        char WindowTitle[100] = { 0 };
        ::GetWindowText(hwnd, WindowTitle, 100);
        std::cout << WindowTitle << std::endl;
        EnumChildWindows(hwnd, EnumNotepadChildWindowsProc, NULL);
    }
}

結果如下:

line: 0, Content: 第一行 iceman
line: 1, Content: 第二行 Hello
line: 2, Content: 第三行 World

到此,就完成既定目標了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM