// TODO: Add your specialized creation code here
//获得任务栏高度CWnd* p;p = this->FindWindow("Shell_TrayWnd",NULL);if(p != NULL){CRect tRect;p->GetWindowRect(tRect);m_taskBarHeight = tRect.Height();}
//修改风格使得他不在任务栏显示ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);//去掉关闭按键(如果想画3个按键的话)//ModifyStyle(WS_SYSMENU,NULL);
//获得边缘高度和宽度m_edgeHeight = GetSystemMetrics(SM_CYEDGE);m_edgeWidth = GetSystemMetrics(SM_CXFRAME);
return 0;}
接着如何知道鼠标进入或移出窗口呢?在前面我已经证明了WM_MOUSEMOVE和WM_MOUSELEAVE不符合我们的要求,于是我用了WM_ NCHITTEST这个消息,你可以看到我在这个消息响应 函数中用了两个SetTimer,一个用于检测鼠标是否离开,一个用于伸缩过程,不管你喜欢不喜欢,要达到第7点和第11点,这个是必须的,考虑的效率问题,在不需要的时候关闭这些Timer就好了。 【代码二】 UINT CQQHideWndDlg::OnNcHitTest(CPoint point) {// TODO: Add your message handler code here and/or call defaultCString str;str.Format("Mouse (%d,%d)",point.x,point.y);GetDlgItem(IDC_CURSOR)->SetWindowText(str);if(m_hideMode != HM_NONE && !m_isSetTimer &&//防止鼠标超出屏幕右边时向右边收缩造成闪烁point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE){ //鼠标进入时,如果是从收缩状态到显示状态则开启TimerSetTimer(1,CM_ELAPSE,NULL);m_isSetTimer = TRUE;m_hsFinished = FALSE;m_hiding = FALSE;SetTimer(2,HS_ELAPSE,NULL); //开启显示过程}return CDialog::OnNcHitTest(point);}
然后在OnTimer中 【代码三】 void CQQHideWndDlg::OnTimer(UINT nIDEvent) {// TODO: Add your message handler code here and/or call defaultif(nIDEvent == 1 ){POINT curPos;GetCursorPos(&curPos);CString str;str.Format("Timer On(%d,%d)",curPos.x,curPos.y);GetDlgItem(IDC_TIMER)->SetWindowText(str);
CRect tRect;//获取此时窗口大小GetWindowRect(tRect);//膨胀tRect,以达到鼠标离开窗口边沿一定距离才触发事件tRect.InflateRect(INFALTE,INFALTE);
if(!tRect.PtInRect(curPos)) //如果鼠标离开了这个区域{KillTimer(1); //关闭检测鼠标Timerm_isSetTimer = FALSE;GetDlgItem(IDC_TIMER)->SetWindowText("Timer Off");
m_hsFinished = FALSE; m_hiding = TRUE;SetTimer(2,HS_ELAPSE,NULL); //开启收缩过程}}
if(nIDEvent == 2){if(m_hsFinished) //如果收缩或显示过程完毕则关闭Timer KillTimer(2);elsem_hiding ? DoHide() : DoShow();}CDialog::OnTimer(nIDEvent);}
暂时不管OnTimer中的DoHide(); DoShow();
先来看看核心的函数之一的 FixMoving,该函数在OnMoving中被调用,FixMoving通过检测鼠标位置和窗口位置来决定窗口的收缩模式,并修正粘附边界时窗口的位置,从而达到像移动QQ时出现的效果。 【代码四】
void CQQHideWndDlg::FixMoving(UINT fwSide, LPRECT pRect){POINT curPos;GetCursorPos(&curPos);INT screenHeight = GetSystemMetrics(SM_CYSCREEN);INT screenWidth = GetSystemMetrics(SM_CXSCREEN);INT height = pRect->bottom - pRect->top;INT width = pRect->right - pRect->left;
if (curPos.y <= INTERVAL){ //粘附在上边pRect->bottom = height - m_edgeHeight;pRect->top = -m_edgeHeight;m_hideMode = HM_TOP;}else if(curPos.y >= (screenHeight - INTERVAL - m_taskBarHeight)){ //粘附在下边pRect->top = screenHeight - m_taskBarHeight - height;pRect->bottom = screenHeight - m_taskBarHeight;m_hideMode = HM_BOTTOM;}else if (curPos.x < INTERVAL){ //粘附在左边 if(!m_isSizeChanged){CRect tRect;GetWindowRect(tRect);m_oldWndHeight = tRect.Height(); }pRect->right = width;pRect->left = 0;pRect->top = -m_edgeHeight;pRect->bottom = screenHeight - m_taskBarHeight;m_isSizeChanged = TRUE;m_hideMode = HM_LEFT;}else if(curPos.x >= (screenWidth - INTERVAL)){ //粘附在右边if(!m_isSizeChanged){CRect tRect;GetWindowRect(tRect);m_oldWndHeight = tRect.Height(); }pRect->left = screenWidth - width;pRect->right = screenWidth;pRect->top = -m_edgeHeight;pRect->bottom = screenHeight - m_taskBarHeight;m_isSizeChanged = TRUE;m_hideMode = HM_RIGHT;}else{ //不粘附if(m_isSizeChanged){ //如果收缩到两边,则拖出来后会变回原来大小//在"拖动不显示窗口内容下"只有光栅变回原来大小pRect->bottom = pRect->top + m_oldWndHeight;m_isSizeChanged = FALSE;}if(m_isSetTimer){ //如果Timer开启了,则关闭之if(KillTimer(1) == 1)m_isSetTimer = FALSE;}m_hideMode = HM_NONE;GetDlgItem(IDC_TIMER)->SetWindowText("Timer off");}}
收缩模式和位置决定后,剩下的工作就由最后两个核心函数完成了:实现收缩的DoHide(),实现伸展的DoShow()。在这两个过程中m_hsFinished,m_hiding 这两个变量起到很重要的控制作用。由于伸缩过程没完成时,hsFinished始终为FALSE,所以Timer 2 不会关闭,于是在OnTimer中会重复调用这两个函数之一,在这两个 函数体内,窗口位置有规律地递减或递增就可以达到QQ的“抽屉”效果了,有趣的是即使伸缩过程还没完成,你也可以在这个过程中改变m_hiding这个值来决定他是伸还是缩,正如QQ一样。你可以把Timer 2 的 事件间隔调大一点,然后在窗口伸缩时,鼠标来回地进出窗口就会很容易看到这样有趣的效果(还没缩进去又被拉了出来,或者还没拉出来又缩进去了)。 【代码五】void CQQHideWndDlg::DoHide(){if(m_hideMode == HM_NONE)return;
CRect tRect;GetWindowRect(tRect);
INT height = tRect.Height();INT width = tRect.Width();
INT steps = 0;
switch(m_hideMode){case HM_TOP:steps = height/HS_STEPS;tRect.bottom -= steps;if(tRect.bottom <= m_edgeWidth){ //你可以把下面一句替换上面的 ...+=|-=steps 达到取消抽屉效果//更好的办法是添加个BOOL值来控制,其他case同样.tRect.bottom = m_edgeWidth;m_hsFinished = TRUE; //完成隐藏过程}tRect.top = tRect.bottom - height; break;case HM_BOTTOM:steps = height/HS_STEPS;tRect.top += steps;if(tRect.top >= (GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth)){tRect.top = GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth;m_hsFinished = TRUE;}tRect.bottom = tRect.top + height;break;case HM_LEFT:steps = width/HS_STEPS;tRect.right -= steps;if(tRect.right <= m_edgeWidth){tRect.right = m_edgeWidth;m_hsFinished = TRUE;}tRect.left = tRect.right - width;tRect.top = -m_edgeHeight;tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;break;case HM_RIGHT:steps = width/HS_STEPS;tRect.left += steps;if(tRect.left >= (GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth)){tRect.left = GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth;m_hsFinished = TRUE;} tRect.right = tRect.left + width;tRect.top = -m_edgeHeight;tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;break;default:break;}
SetWindowPos(&wndTopMost,tRect);}
【代码六】void CQQHideWndDlg::DoShow(){if(m_hideMode == HM_NONE)return;
CRect tRect;GetWindowRect(tRect);INT height = tRect.Height();INT width = tRect.Width();
INT steps = 0;
switch(m_hideMode){case HM_TOP:steps = height/HS_STEPS;tRect.top += steps;if(tRect.top >= -m_edgeHeight){ //你可以把下面一句替换上面的 ...+=|-=steps 达到取消抽屉效果//更好的办法是添加个BOOL值来控制,其他case同样.tRect.top = -m_edgeHeight;m_hsFinished = TRUE; //完成显示过程}tRect.bottom = tRect.top + height;break;case HM_BOTTOM:steps = height/HS_STEPS;tRect.top -= steps;if(tRect.top <= (GetSystemMetrics(SM_CYSCREEN) - height)){tRect.top = GetSystemMetrics(SM_CYSCREEN) - height;m_hsFinished = TRUE;}tRect.bottom = tRect.top + height;break;case HM_LEFT:steps = width/HS_STEPS;tRect.right += steps;if(tRect.right >= width){tRect.right = width;m_hsFinished = TRUE;}tRect.left = tRect.right - width;tRect.top = -m_edgeHeight;tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;break;case HM_RIGHT:steps = width/HS_STEPS;tRect.left -= steps;if(tRect.left <= (GetSystemMetrics(SM_CXSCREEN) - width)){tRect.left = GetSystemMetrics(SM_CXSCREEN) - width;m_hsFinished = TRUE;}tRect.right = tRect.left + width;tRect.top = -m_edgeHeight;tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;break;default:break;}
SetWindowPos(&wndTopMost,tRect);}
BOOL CQQHideWndDlg::SetWindowPos(const CWnd* pWndInsertAfter, LPCRECT pCRect, UINT nFlags){return CDialog::SetWindowPos(pWndInsertAfter,pCRect->left, pCRect->top,pCRect->right - pCRect->left, pCRect->bottom - pCRect->top, nFlags);}
到此,程序终于完成了。在我的源代码中还有对WM_SIZING的处理和定义了与之相关的宏,这些主要是控制窗口在调整大小时不能超过最小的宽度和高度,与QQ的自动伸缩无关,所以不在这里提及了。 三、结束语 虽然还不能算是完美的模仿,但效果已经非常非常的接近了。也许有人会奇怪为什么要用Tool Window 风格,这是因为,这样在任务栏中不会显示窗口。从QQ的标题栏高度也可以判断出他也是这种风格,但这样一来就不能拥有最小化、最大化按键了。实际上QQ的最大化、最小化和关闭按键都是用DC画上去的。如何在Caption上增加按键,外国一些开源网站有源代码,我下载并看了一下,发现里面有个知识点很有趣,那就是更改消息路由,有兴趣的可以去下载来学习一下。 QQ的成功很大部分在于他的界面比较人性化(用了MSN后深有感受),而这些界面实现起来原理也许很简单,难的是观察东西心要细、设计东西心要密、开发东西心要异。
