| 導購 | 订阅 | 在线投稿
分享
 
 
 

Visual C++设计超强仿QQ自动伸缩窗口

2008-06-01 02:11:00  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
 
  摘要:

  某天在论坛上看到有人发帖询问QQ自动伸缩窗口是怎么实现的,我也好想知道,于是到百度一搜索,结果不多,来来去去都是那几篇,下载那些demo运行一下,发觉效果与QQ相差很大,于是决定自己动手做个,要求要近乎完美地模拟这个功能。由于是些效果的东西,贴图也看不出来,所以文章里就不截图了,想看效果的就直接运行源代码的demo吧。

  一、观察

  模拟前最重要的一步就是观察,经过半天对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窗口;

  二、编写代码

  观察完毕,就开始编写了。

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

  在QQHideWndDlg.h头文件添加以下成员函数:

  

  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

  INTm_oldWndHeight;//旧的窗口宽度INTm_taskBarHeight;//任务栏高度INTm_edgeHeight;//边缘高度

  INTm_edgeWidth;//边缘宽度

  INTm_hideMode;//隐藏模式

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

  BOOL m_hiding;//该参数只有在!m_hsFinished才有效

  //真:正在隐藏,假:正在显示

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

  

  WM_ NCHITTEST

  WM_MOVING

  WM_CREATE

  WM_TIMER

  然后来到对应的cpp文件,在头部定义一些宏:

  

  //收缩模式#define HM_NONE0//不收缩

  #define HM_TOP1//向上收缩

  #define HM_BOTTOM2//向下收缩

  #define HM_LEFT3//向左收缩

  #define HM_RIGHT4//向右收缩

  #define CM_ELAPSE200 //检测鼠标是否离开窗口的时间间隔

  #define HS_ELAPSE5//伸缩过程每步的时间间隔

  #define HS_STEPS10//伸缩过程分成多少步完成

  #define INTERVAL20//触发粘附时鼠标与屏幕边界的最小间隔,单位为象素

  #define INFALTE10//触发收缩时鼠标与窗口边界的最小间隔,单位为象素

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

  

  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("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 default

  CString 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)

  { //鼠标进入时,假如是从收缩状态到显示状态则开启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);

  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); //关闭检测鼠标Timer

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

  else

  m_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后深有感受),而这些界面实现起来原理也许很简单,难的是观察东西心要细、设计东西心要密、开发东西心要异。

  查阅关于VC的全部文档
 
 
 
  摘要:   某天在论坛上看到有人发帖询问QQ自动伸缩窗口是怎么实现的,我也好想知道,于是到百度一搜索,结果不多,来来去去都是那几篇,下载那些demo运行一下,发觉效果与QQ相差很大,于是决定自己动手做个,要求要近乎完美地模拟这个功能。由于是些效果的东西,贴图也看不出来,所以文章里就不截图了,想看效果的就直接运行源代码的demo吧。   一、观察   模拟前最重要的一步就是观察,经过半天对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窗口;   二、编写代码   观察完毕,就开始编写了。   首先新建一个基于对话框的MFC程序,命名为QQHideWnd,在对话框属性的styles页把border改为Resizing,你也可同时把Entended styles 的 tool window 钩上,对于这点我在程序了动态修改了。 在QQHideWndDlg.h头文件添加以下成员函数: 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 INTm_oldWndHeight;//旧的窗口宽度INTm_taskBarHeight;//任务栏高度INTm_edgeHeight;//边缘高度 INTm_edgeWidth;//边缘宽度 INTm_hideMode;//隐藏模式 BOOL m_hsFinished;//隐藏或显示过程是否完成 BOOL m_hiding;//该参数只有在!m_hsFinished才有效 //真:正在隐藏,假:正在显示   增加消息响应,需要注重的是有些消息你只有把右下角的 Filter for message设置为window才能看到。 WM_ NCHITTEST WM_MOVING WM_CREATE WM_TIMER   然后来到对应的cpp文件,在头部定义一些宏: //收缩模式#define HM_NONE0//不收缩 #define HM_TOP1//向上收缩 #define HM_BOTTOM2//向下收缩 #define HM_LEFT3//向左收缩 #define HM_RIGHT4//向右收缩 #define CM_ELAPSE200 //检测鼠标是否离开窗口的时间间隔 #define HS_ELAPSE5//伸缩过程每步的时间间隔 #define HS_STEPS10//伸缩过程分成多少步完成 #define INTERVAL20//触发粘附时鼠标与屏幕边界的最小间隔,单位为象素 #define INFALTE10//触发收缩时鼠标与窗口边界的最小间隔,单位为象素   然后在构造函数初始化成员变量: 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("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 default CString 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) { //鼠标进入时,假如是从收缩状态到显示状态则开启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); 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); //关闭检测鼠标Timer m_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); else m_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后深有感受),而这些界面实现起来原理也许很简单,难的是观察东西心要细、设计东西心要密、开发东西心要异。   查阅关于VC的全部文档
󰈣󰈤
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
王朝网络微信公众号
微信扫码关注本站公众号 wangchaonetcn
 
  免责声明:本文仅代表作者个人观点,与王朝网络无关。王朝网络登载此文出于传递更多信息之目的,并不意味著赞同其观点或证实其描述,其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有