问题引出:
今天看了一下 深入解析MFC,第40页到第50页之间,有一句
这里面说到了一个函数,,, GetActiveWindow,,根据直观意思很容易理解 就是获取 活动窗口的句柄,但是什么才是活动窗口。
Retrieves a pointer to the active window.
//获取一个指向活动窗口的CWnd指针。 |
---|
static CWnd* PASCAL GetActiveWindow( ); |
Return Value //返回值
The active window or NULL if no window was active at the time of the call. The pointer may be temporary and should not be stored for later use.
//返回活动窗口的句柄,如果在调用这个函数时没有活动窗口,则返回NULL。这个指针可能是指向临时对象 因此不要 将它保存起来以备后来之用。
Remarks//说明
The active window is either the window that has the current input focus or the window explicitly made active by the SetActiveWindow member function.
//活动窗口是 拥有输入焦点的窗口。显示使用 SetActiveWindow 这个函数来指定的窗口也是活动窗口。
上面是MSDN中给出的解释 ,我做了简单的翻译。其实软件技术本身并不复杂,复杂的是我们弄不清楚概念。但是上面 说明处 也给出了概念。那么我们就来测试一下吧。
用VS2008 新建一个MFC 对话框程序。
然后再上面 对话框上放 2 个 编辑框(这样就可以有输入焦点了)。
然后 在 OnPaint 中添加如下代码
void CTestDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
//新添加
HWND hwndEdit1 = ((CEdit *)GetDlgItem(IDC_EDIT1))->m_hWnd;
HWND hwndEdit2 = ((CEdit *)GetDlgItem(IDC_EDIT2))->m_hWnd;
CString str;
str.Format("edit1:%x edit2:%x dlg:%x",hwndEdit1,hwndEdit2,m_hWnd);
CPaintDC dc(this);
dc.TextOut(0,0,str);
CDialog::OnPaint();
}
}
其实功能就是在 对话框的客户区上输出 两个编辑框和主对话框的句柄值。
运行效果如下:
下面我们应该如何获取一下 活动窗口的句柄呢,就要用那个函数了,,我们来试试,问题是在哪里使用这个函数呢?
因为要输入焦点,而我们还想要切换焦点,所以,我想到了计时器,我们启动一个计时器,比如10秒一次,在这里面来调用函数。
在 BOOL CTestDlg::OnInitDialog() 里面加上 SetTimer(1,10000,NULL);
为对话框添加 WM_TIMER 消息响应函数
void CTestDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: Add your message handler code here and/or call default
switch (nIDEvent)
{
case 1:
{
CWnd * pWnd = CWnd::GetActiveWindow();
ASSERT(pWnd != NULL);
CString str;
str.Format("%x",pWnd->m_hWnd);
AfxMessageBox(str);
}
break;
default:
break;
}
CDialog::OnTimer(nIDEvent);
}
这样子,我们的程序会每隔10秒钟就弹出一个值,我们来测试一下,在测试过程中,我们可以对比弹出的值与对话框中输出的三个值的关系,我们还可以点击两个编辑框,以让它们获取输入焦点。
如果不出意外,那么这个弹出的值会是 对话框的句柄,那么什么时候会出意外呢,就是你把其它窗口点开了,比如你切换到了浏览器窗口或者其它应用程序。这个时候 程序会出现 断言错误,就是
ASSERT(pWnd != NULL);
这时候,pWnd获取的值是 NULL。
这里很奇怪。
1、为什么总是 对话框的句柄呢
2、为什么一切换到其它窗口就命中断言了呢
我们去看一下这个函数的实现吧。
_AFXWIN_INLINE CWnd* PASCAL CWnd::GetActiveWindow()
{ return CWnd::FromHandle(::GetActiveWindow()); }
这是先调用API 函数 GetActiveWindow ,以获取 句柄值,然后通过句柄值检索对象或者构造临时对象。
那么我们就去看一下 这个API 的说明吧。
The GetActiveWindow function retrieves the window handle to the active window attached to the calling thread's message queue.
// 函数 GetActiveWindow 获取 关联到 调用线程的消息队列 的 活动窗口的句柄值。
Syntax
HWND GetActiveWindow(VOID);
Return Value
The return value is the handle to the active window attached to the calling thread's message queue. Otherwise, the return value is NULL.
//意思跟上面差不多,但这里我要说明一点知识,那就是一个线程只有一个消息队列,一个线程可以创建多个窗口,一个线程最多有一个活动窗口,也可能没有,明白这一点后你再看这点就可以理解了。
Remarks
To get the handle to the foreground window, you can use GetForegroundWindow.
Windows 98/Me and Windows NT 4.0 SP3 and later: To get the window handle to the active window in the message queue for another thread, use GetGUIThreadInfo.
//如果要获取前台窗口的句柄,你应该使用 GetForegroundWindow 。
//如果要获取其它线程中的活动窗口,你应该使用 GetGUIThreadInfo。
好了,说了这么多,我们终于有点眉目了。
虽然编辑框也是窗口,但这个函数中说明的窗口是指
,而不是
其实窗口还有另外两种类型
好了,这样我们就明白了,编辑框是窗口不假,但是它是子窗口,而且在MFC对这个函数的说明不是太明确。
现在也就是 如果 我们再开一个工作线程,在这里来 用 GetActiveWindow ,这是肯定不行的,因为我们的这个工作线程 自身 并没有创建窗口。当我们激活一个窗口时,就是在任务栏点击一个窗口程序,这个时候这个程序就拥有了输入焦点,它就是活动窗口。
还是之前的例子,我们在 程序的 标题栏上右击,选择关于 ,此时会再弹出一个模态对话框,如下图
可以看出不再是主对框了,因为在没有弹出Test 那个对话框之前,About Test 才是 这个线程 中的 活动窗口。如果再等 几十秒(不要点确定),会发现 他还会弹出 提示框,而且每次的结果都不一样,
这是因为当第一个提示框弹出后,它就变成了活动窗口了,第2个弹出的就是第一个的句柄了,第3个弹出的就是第二个的句柄了。
当我们一切到其它窗口的时候,程序会因为断言宏 的问题 而中断。
还有一个问题 就是,我们可以开一个线程,来获取 其它线程中的 活动窗口。
定义一个线程
UINT __cdecl Worker( LPVOID pParam )
{
CTestDlg * pDlg = (CTestDlg *)pParam;
ASSERT(pDlg != NULL);
DWORD tid = GetWindowThreadProcessId(pDlg->m_hWnd,NULL);
GUITHREADINFO guiTI = {0};
guiTI.cbSize = sizeof(GUITHREADINFO);
GetGUIThreadInfo(tid,&guiTI);
CString str;
str.Format("%x\n",guiTI.hwndActive);
AfxMessageBox(str);
return 0;
}
然后将 计时器 中 换成
switch (nIDEvent)
{
case 1:
{
AfxBeginThread(Worker,(LPVOID)this);
}
break;
这样子依旧可以获取 其 活动窗口,当然可以利用这种方式 来获取 其它 软件 的 活动窗口哦。
如果还有不明白的地方,那就是前台窗口 与 活动窗口 这两个概念的 区别了。
不妨来做个测试。
我们随便写个小按钮响应事件
//点击开始菜单,点击运行,,输入 notepad 回车即可
HWND hwndNotepad = ::FindWindow(NULL,"无标题 - 记事本");
ASSERT(hwndNotepad != NULL);
::SetForegroundWindow(hwndNotepad);
功能就是将记事本 置成前台,我们发现 记事本 果然就 跑前面来了,而且也被激活了。
那么它们到底有什么区别吗?
1、设置都没有什么好说的
2、获取,一个是针对某一线程进行获取,一个是针对整个系统中的所有窗口进行获取
3、反上去,其它设置也是范围不同,一个针对线程,另一个针对系统。
什么时候应该哪个,要取决于你具体的目的是什么,然后再做一个判断。
其实说到这里应该结束了,但是其实还有一点,就是如何 在一个线程中,激活另一个线程中的 窗口,说白了就是 线程A 没有窗口,线程B有3个窗口,我想用A 激活B中的 2号窗口,怎么办。 获取是 使用的GetGUIThreadInfo ,设置 可没有 SetGUIThreadInfo 这个函数的,
这个时候 ,我们应该行看看MSDN中有没有办法
By using the AttachThreadInput function, a thread can attach its input processing to another thread. This allows a thread to call SetActiveWindow to activate a window attached to another thread's message queue.
可以看到 可以使用 AttachThreadInput 这个函数可以将 一个线程 的输入处理 关联到 另一个线程,进而 完成 设置 其它线程中的 活动窗口的 目的。
可能有的人还不清楚 一个程序到底有多少个窗口,哈哈,这个是初学者最不容易理解的地方,一个程序可以有多个窗口的,举一个例子吧,VS2008 等类似的软件有吧,,知道怎么设置行号吗,知道怎么改变字体大小吗,知道怎么新建工程吗,知道怎么添加文件吗,好吧,这些功能,每一个都是一个对话框哦,再比如,一个记事本程序,里面有搜索功能吧,这也是一个对话框,还有 最后一个例子,
直接上图
好了,我想大家对活动窗口应该理解了吧。。。。
转载于:https://blog.51cto.com/qianqianquege/1427059