/*在原创的基础上做了部分修改*/

一、【观察】

模仿前最重要的一步就是观察,经过半天对QQ的摆弄和摸索,总结出了以下一些特点:

1、窗口开始粘附时,检测的是鼠标坐标与桌面边界的距离,特别地,粘附在下面的时候,检测的是与任务栏的距离;

2、在向上移动窗口时,窗口边界永远不会超出桌面上面边界;

3、窗口是个 TopMost 风格;

4、当窗口粘附在上面、左边或右边并显示时,你把鼠标移动到最顶端,光标变成改变窗口大小的图标,而单单是把窗口的top坐标设置为0是不行的;

5、粘附在下面的时候,当处于移动状态,那么窗口的底边是与任务栏顶边对齐的,但从隐藏到显示的时候,窗口的底端是与屏幕底边对齐的;

6、隐藏后显露出来的那条线可能是一个Border,但肯定的是绝不包含Client区域;

7、关于响应鼠标的进入与移出窗口,绝对不是WM_MOUSEMOVE、WM_MOUSELEAVE。证明:你以及其慢的速度接触隐藏状态的QQ边界,你会发现几乎是“一触即发”,你又以及其慢的速度移出显示状态的QQ,你会发现它的收缩反而不是“一触即发”的,而是离边缘10象素左右。而WM_MOUSEMOVE,WM_MOUSELEAVE,只有在进入、移出Client区域才响应,明显和QQ不同,其实从第6点也可以知道;

8、粘附在两边的时候,高度会调整为桌面上边界到任务栏下边界的距离;

9、在“拖动时显示窗口内容”模式下(桌面属性-外观-效果),粘附在两边的拖动出来时;如果收缩之前高度比收缩后小则回复原来高度,在非“拖动时显示窗口内容”模式下,光栅会回复原来高度,但释放左键时,高度却是收缩时调整后的高度,一开始我以为这是个BUG,但我编写时同样出现这个问题,发现这两种模式会影响WM_MOVING参数的意义;

10、粘附在两边的时候当你设置任务栏自动隐藏,QQ窗口会自动调整高度充满屏幕高度;

11、窗口显示或隐藏不是一瞬间的,这点在第9点提到的两种模式下,会有所不同;

12、任务栏并不显示QQ窗口;

二、【编写代码】

1、观察完毕,就开始编写了。
首先新建一个基于对话框的MFC程序,命名为QQHideWnd,在对话框属性的styles页把border改为Resizing,你也可同时把Entended styles 的 tool window 钩上,对于这点我在程序了动态修改了。
在QQHideWndDlg.h头文件添加以下成员函数:

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

2、继续添加成员变量:

private:BOOL m_isSizeChanged;   //窗口大小是否改变了 BOOL m_isSetTimer;      //是否设置了检测鼠标的TimerINT  m_oldWndHeight;    //旧的窗口宽度INT  m_taskBarHeight;   //任务栏高度INT  m_edgeHeight;      //边缘高度INT  m_edgeWidth;       //边缘宽度INT  m_hideMode;        //隐藏模式BOOL m_hsFinished;      //隐藏或显示过程是否完成BOOL m_hiding;          //该参数只有在!m_hsFinished才有效//真:正在隐藏,假:正在显示/*public:BOOL m_topMost;         //是否使用TopMost风格BOOL m_useSteps;        //是否使用抽屉效果BOOL m_toolWnd;         //是否使用Tool Window 风格*/

3、增加消息响应,需要注意的是有些消息你只有把右下角的 Filter for message设置为window才能看到。
WM_ NCHITTEST

WM_MOVING

WM_CREATE

WM_TIMER

WM_SIZING

4、然后来到Dlg对应的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 //窗口最小高度

5、然后在构造函数初始化成员变量:

CQQHideWndDlg::CQQHideWndDlg(CWnd* pParent /*=NULL*/): CDialogEx(CQQHideWndDlg::IDD, pParent)
{m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-1-*///构造函数初始化成员变量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;/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-2-*/
}

6、完成了一些初始的工作,那么就开始进入关键的函数实现了。首先是在OnCreate做些窗口的初始化和获得一些系统信息。

//添加消息函数 WM_CREATE
int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{if (CDialogEx::OnCreate(lpCreateStruct) == -1)return -1;// TODO:  在此添加您专用的创建代码//获得任务栏高度CWnd* p;p = this->FindWindow(_T("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;
}

7、接着如何知道鼠标进入或移出窗口呢?在前面我已经证明了WM_MOUSEMOVE和WM_MOUSELEAVE不符合我们的要求,于是我用了WM_ NCHITTEST这个消息,你可以看到我在这个消息响应函数中用了两个SetTimer,一个用于检测鼠标是否离开,一个用于伸缩过程,不管你喜欢不喜欢,要达到第7点和第11点,这个是必须的,考虑的效率问题,在不需要的时候关闭这些Timer就好了。

//添加消息函数 WM_ NCHITTEST
LRESULT CQQHideWndDlg::OnNcHitTest(CPoint point)
{// TODO:  在此添加消息处理程序代码和/或调用默认值CString str;str.Format(_T("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 CDialogEx::OnNcHitTest(point);
}

8、紧接着 OnTimer函数

//添加消息函数 WM_TIMER
void CQQHideWndDlg::OnTimer(UINT_PTR nIDEvent)
{// TODO:  在此添加消息处理程序代码和/或调用默认值if (nIDEvent == 1){POINT curPos;GetCursorPos(&curPos);CString str;str.Format(_T("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(_T("Timer Off"));m_hsFinished = FALSE;m_hiding = TRUE;SetTimer(2, HS_ELAPSE, NULL); //开启收缩过程}}if (nIDEvent == 2){if (m_hsFinished) //如果收缩或显示过程完毕则关闭TimerKillTimer(2);elsem_hiding ? DoHide() : DoShow();}CDialogEx::OnTimer(nIDEvent);
}

9、暂时不管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(_T("Timer off"));}
}

10、收缩模式和位置决定后,剩下的工作就由最后两个核心函数完成了:实现收缩的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);
}

11、NEXT写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);
}

12、添加OnSizing响应函数

//相应消息函数 WM_SIZING
void CQQHideWndDlg::OnSizing(UINT fwSide, LPRECT pRect)
{FixSizing(fwSide, pRect); //修正pRectCDialogEx::OnSizing(fwSide, pRect);// TODO:  在此处添加消息处理程序代码
}

13、添加OnMoving 响应函数

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

14、接着SetWindowPos和FixSizing函数

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 CQQHideWndDlg::FixSizing(UINT fwSide, LPRECT pRect)
{CRect curRect(pRect);if (curRect.Width() < MINCX || curRect.Height() < MINCY){   //小于指定的最小宽度或高度switch (fwSide){case WMSZ_BOTTOM:pRect->bottom = pRect->top + MINCY;break;case WMSZ_BOTTOMLEFT:if (curRect.Width() <= MINCX)pRect->left = pRect->right - MINCX;if (curRect.Height() <= MINCY)pRect->bottom = pRect->top + MINCY;break;case WMSZ_BOTTOMRIGHT:if (curRect.Width() < MINCX)pRect->right = pRect->left + MINCX;if (curRect.Height() < MINCY)pRect->bottom = pRect->top + MINCY;break;case WMSZ_LEFT:pRect->left = pRect->right - MINCX;break;case WMSZ_RIGHT:pRect->right = pRect->left + MINCX;break;case WMSZ_TOP:pRect->top = pRect->bottom - MINCY;break;case WMSZ_TOPLEFT:if (curRect.Width() <= MINCX)pRect->left = pRect->right - MINCX;if (curRect.Height() <= MINCY)pRect->top = pRect->bottom - MINCY;break;case WMSZ_TOPRIGHT:if (curRect.Width() < MINCX)pRect->right = pRect->left + MINCX;if (curRect.Height() < MINCY)pRect->top = pRect->bottom - MINCY;break;}}
}

15、到此,程序终于完成了。上面源代码中有对WM_SIZING的处理和定义了与之相关的宏,这些主要是控制窗口在调整大小时不能超过最小的宽度和高度,与QQ的自动伸缩无关,所以在这里就提一下。

三、【结束语】

虽然还不能算是完美的模仿,但效果已经非常非常的接近了。也许有人会奇怪为什么要用Tool Window 风格,这是因为,这样在任务栏中不会显示窗口。从QQ的标题栏高度也可以判断出他也是这种风格,但这样一来就不能拥有最小化、最大化按键了。实际上QQ的最大化、最小化和关闭按键都是用DC画上去的。如何在Caption上增加按键,外国一些开源网站有源代码,我下载并看了一下,发现里面有个知识点很有趣,那就是更改消息路由,有兴趣的可以去下载来学习一下。

1、下载源代码(百度云盘)

2、http://download.csdn.net/detail/u013100446/8281311(CSDN下载)

MFC仿QQ窗口靠边自动收缩隐藏效果(附源代码)相关推荐

  1. VC 仿QQ窗口靠边自动收缩隐藏效果

    一.观察 模仿前最重要的一步就是观察,经过半天对QQ的摆弄和摸索,总结出了以下一些特点: 1.窗口开始粘附时,检测的是鼠标坐标与桌面边界的距离,特别地,粘附在下面的时候,检测的是与任务栏的距离: 2. ...

  2. electron+vue+elementui实现类似QQ窗口靠边自动边缘隐藏,electron+vue桌面应用项目框架搭建

    1.与普通vue项目一样安装node.淘宝源等,然后再 electron官网:https://www.electronjs.org/ 安装electron: npm install electron ...

  3. C#实现仿QQ窗口靠边自动隐藏

    今晚做网络通讯的实验,想实现下类似QQ滴窗口靠近屏幕上方自动隐藏,上网搜了下代码,发现这个比较简单,而且又很好的实现了所需功能,特记录如下,主要用了三个计时器来实现,开始时设置计时器1启用,计时器2 ...

  4. iOS之仿QQ好友列表展开收缩效果的实现

    使用UICollectionView实现 思路 很明显整体它是一个列表,它的分组是一个列表,它里面的好友列表也是一个列表,所以就可以使用组头来设置分组列表,使用cell设置好友列表: 当点击组头的时候 ...

  5. 一段仿QQ窗口抖动的代码(VC++)

    本来是想写个邮槽通信的东西,做好了框架发现邮槽单向收发数据确实不好搞,     于是不搞了.把里面实现的仿QQ窗口抖动的代码拿出来保留下来. /**************************** ...

  6. android 自定义园动画,Android 自定View实现仿QQ运动步数圆弧及动画效果

    在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件,和QQ运动的界面有点类似,还有动画效果,下面就来讲一下这个View是如何绘制的. 1.先看效果图 2.效果图分析 功 ...

  7. 仿花瓣网html模板,仿花瓣网的自动悬浮导航效果 jquery

    仿花瓣网的自动悬浮导航效果 body, h1, ul { margin:0; } ul li { list-style-type:none; } #header { width:100%; borde ...

  8. android气泡聊天消息背景,Android使用贝塞尔曲线仿QQ聊天消息气泡拖拽效果

    本文实例为大家分享了Android仿QQ聊天消息气泡拖拽效果展示的具体代码,供大家参考,具体内容如下 先画圆,都会吧.代码如下: public class Bezier extends View { ...

  9. c语言 qq窗口抖动,仿QQ窗口抖动

    昨天晚上闲着没事,看到了一篇技术论文,是跟珊瑚虫版QQ有关的,其中有反编译QQ的功能模块,有个是QQ窗口抖动功能类,感觉还是很简单的,代码比较少,废话不多说,上代码,具体实现的exe在我的资源里,可以 ...

最新文章

  1. CentOS 不区分大小写的搜索
  2. onpropertychange事件
  3. sqlserver 查看进程以及查看死锁代码
  4. python fieldnames_csvreader.fieldnames在python中未被识别为csv reader对象的属性
  5. c#里有遍历目录及子目录以树的形式展现
  6. primefaces_Primefaces CommandLink
  7. 时间进度表html,网页的设计进度表.doc
  8. saas,pass介绍
  9. 用拼音输入法字典库实现同音字模糊查询
  10. Unity外部资源无法拖入Unity
  11. java 多用组合_java 为什么说多用组合,少用继承?
  12. modules node 太大了_解决node_modules文件名太长无法删除的两个方法-文件名太长
  13. 2019 ICPC 沈阳站 游记
  14. pcsc linux,Linux 下集成 pcsc ccid libusb
  15. 海龟画图 python太阳花_《海》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
  16. JavaScript 为什么能活到现在?
  17. 【功能安全】【ISO26262】支持过程
  18. PCA针对TE过程(田纳西伊斯曼过程)数据的故障监测处理
  19. 中高级Java面试题解析,剑指BATJ,提前祝大家程序员节快乐
  20. 前端市场又“饱和”了,还有必要学吗?

热门文章

  1. 学计算机的女生,是一种怎样的存在?
  2. 六年级上册计算机知识点总结,六年级上册知识点总结.doc
  3. python 更换桌面壁纸 播放音乐
  4. Java基础学习总结记录
  5. CF1795C Tea Tasting (前缀和,差分,二分)
  6. java监听oracle aq,JMS监听Oracle AQ
  7. stm32hal库红外激光雷达测距
  8. Python基础学习-1-廖雪峰课程-Python的数据类型和面向对象
  9. 系统资源不足,无法完成请求服务。
  10. 使用Python玩赤色要塞 初初初级版