A-A+

VS/MFC C++窗口靠边自动收缩隐藏功能的实现

2015年07月05日 C/C++ 阅读 1,899 views 次

对于很多应用程序希望窗口被拖到边缘时能够自动实现收缩隐藏功能,当鼠标再次停靠边缘时窗口能够自动伸出。这不仅能够简化电脑界面,而且能够很方便地对程序进行各种操作。本文将介绍在VS/MFC C++中实现这种功能的方法。文章中的程序代码来源于《VC 仿QQ窗口靠边自动收缩隐藏效果》,并对在程序调试过程中出现的一些小的问题进行了修正。

首先新建一个基于对话框的MFC程序,命名为QQHideWnd,在对话框属性的styles页把border改为Resizing,你也可同时把Entended styles 的 tool window 钩上,也可以在程序中作动态修改。

在CQQHideWndDlg类中创建以下成员函数和变量:

protected:
//修正移动时窗口的大小
void FixMoving(UINT fwSide, LPRECT pRect);
//从收缩状态显示窗口
void DoShow();
//从显示状态收缩窗口
void DoHide();
//重载函数,只是为了方便调用,实际调用CWnd的SetWindowPos(…)
BOOL SetWindowPos(const CWnd* pWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);

private:
bool m_isSizeChanged;//窗口大小是否改变了

bool m_isSetTimer;//是否设置了检测鼠标的Timer

int m_oldWndHeight;//旧的窗口宽度

int m_taskBarHeight;//任务栏高度

int m_edgeHeight;//边缘高度

int m_edgeWidth;//边缘宽度

int m_hideMode;//隐藏模式

bool m_hsFinished;//隐藏或显示过程是否完成

bool m_hiding;//该参数只有在!m_hsFinished才有效,真:正在隐藏,假:正在显示

为CQQHideWndDlg类创建以下消息响应(有些消息你只有把右下角的 Filter for message设置为window才能看到):

WM_ NCHITTEST
WM_MOVING
WM_CREATE
WM_TIMER

在QQHideWndDlg.cpp文件中定义以下宏:

//收缩模式
#define HM_NONE     0   //不收缩
#define HM_TOP      1   //向上收缩
#define HM_BOTTOM   2   //向下收缩
#define HM_LEFT     3   //向左收缩
#define HM_RIGHT    4   //向右收缩

#define CM_ELAPSE   200 //检测鼠标是否离开窗口的时间间隔
#define HS_ELAPSE   5   //隐藏或显示过程每步的时间间隔
#define HS_STEPS    10  //隐藏或显示过程分成多少步

#define INTERVAL    20  //触发粘附时鼠标与屏幕边界的最小间隔,单位为象素
#define INFALTE     10  //触发收缩时鼠标与窗口边界的最小间隔,单位为象素
#define MINCX       200 //窗口最小宽度
#define MINCY       400 //窗口最小高度

在CQQHideWndDlg类的构造函数中对以下变量进行初始化:

m_isSizeChanged = FALSE;
m_isSetTimer = FALSE;
m_hsFinished = TRUE;
m_hiding = FALSE;
m_oldWndHeight = MINCY;
m_taskBarHeight = 30;
m_edgeHeight = 0;
m_edgeWidth=0;
m_hideMode = HM_NONE;

做完以上准备工作后,下面对函数和消息进行代码的实现。

首先是在OnCreate函数中做些窗口的初始化和获得一些系统信息,代码如下:

int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: Add your specialized creation code here //获得任务栏高度
CWnd* p;
p = this->FindWindow(L"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_ NCHITTEST这个消息,在这个消息响应函数中用了两个SetTimer,一个用于检测鼠标是否离开,一个用于伸缩过程,考虑的效率问题,在不需要的时候关闭这些Timer就好了。WM_ NCHITTEST消息的实现代码如下:

UINT CQQHideWndDlg::OnNcHitTest(CPoint point)
{
// TODO: Add your message handler code here and/or call default
if(m_hideMode != HM_NONE && !m_isSetTimer &&
//防止鼠标超出屏幕右边时向右边收缩造成闪烁
point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE)
{ //鼠标进入时,如果是从收缩状态到显示状态则开启Timer
SetTimer(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 default
if(nIDEvent == 1 )
{
POINT curPos;
GetCursorPos(&curPos);

CRect tRect;
//获取此时窗口大小
GetWindowRect(tRect);
//膨胀tRect,以达到鼠标离开窗口边沿一定距离才触发事件
tRect.InflateRect(INFALTE,INFALTE);

if(!tRect.PtInRect(curPos)) //如果鼠标离开了这个区域
{
KillTimer(1); //关闭检测鼠标Timer
m_isSetTimer = FALSE;

m_hsFinished = FALSE;
m_hiding = TRUE;
SetTimer(2,HS_ELAPSE,NULL); //开启收缩过程
}
}

if(nIDEvent == 2)
{
if(m_hsFinished) //如果收缩或显示过程完毕则关闭Timer
KillTimer(2);
else
m_hiding ? DoHide() : DoShow();
}
CDialog::OnTimer(nIDEvent);
}

暂时不管OnTimer中的DoHide(); DoShow();先来看看核心的函数之一的 FixMoving,该函数在OnMoving中被调用,FixMoving通过检测鼠标位置和窗口位置来决定窗口的收缩模式,并修正粘附边界时窗口的位置,从而达到像移动QQ时出现的效果。FixMoving函数,代码如下:

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;
}
}

收缩模式和位置决定后,剩下的工作就由最后两个核心函数完成了:实现收缩的DoHide(),实现伸展的DoShow()。在这两个过程中 m_hsFinished,m_hiding 这两个变量起到很重要的控制作用。由于伸缩过程没完成时,hsFinished始终为FALSE,所以Timer 2 不会关闭,于是在OnTimer中会重复调用这两个函数之一,在这两个函数体内,窗口位置有规律地递减或递增就可以达到QQ的“抽屉”效果了,有趣的是即使伸缩过程还没完成,你也可以在这个过程中改变m_hiding这个值来决定他是伸还是缩,正如QQ一样。你可以把Timer 2 的事件间隔调大一点,然后在窗口伸缩时,鼠标来回地进出窗口就会很容易看到这样有趣的效果(还没缩进去又被拉了出来,或者还没拉出来又缩进去了)。

DoHide()函数代码如下:

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);
}

DoShow()函数代码如下:

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);
}

下面是SetWindowPos和OnMoving两个函数的代码

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);
}

void CToolBoxDlg::OnMoving(UINT fwSide, LPRECT pRect)
{ CDialog::OnMoving(fwSide, pRect);
// TODO: 在此处添加消息处理程序代码
FixMoving(fwSide,pRect); //修正pRect    
CDialog::OnMoving(fwSide, pRect);
}

至此,实现窗口靠边自动收缩隐藏功能的工作就全部完成了。

参考文献:

《VC 仿QQ窗口靠边自动收缩隐藏效果》

个人公众号“数字化设计CAX联盟”,欢迎关注,共同交流
为您推荐:

给我留言

© 坐倚北风 版权所有 严禁镜像复制 苏ICP备15034888号. 基于 Ality 主题定制 AliCMS
联系邮箱:leanwind@163.con,微信公众号:数字化设计CAX联盟

用户登录

分享到: