• 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] = '';
    				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
    

    到此,就完成既定目标了

  • 相关阅读:
    MOSS产品概述[转帖]
    学习WF笔记9自定义活动示例(6)
    MOSS之五母版页 布局页 Features[转帖]
    MOSS系列之四用户组和用户[转帖]
    MOSS之六Web Part[转帖]
    MOSS系列之三列表和文档库[转帖]
    学习WF笔记9自定义活动的外观(5)
    学习WF笔记9 自定义活动中事件类型的属性(3)
    MOSS系列之二(MOSS安装)[转帖]
    学习WF笔记9自定义活动的验证方式(4)
  • 原文地址:https://www.cnblogs.com/wqliceman/p/8909530.html
Copyright © 2020-2023  润新知