打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
仿QQ悬挂窗口的实现

上过QQ的朋友们都知道,当QQ窗口位于桌面的左边界、右边界或顶部的时候,QQ会自动隐藏起来;而一旦鼠标再次接触到上述边界的时候,QQ窗口又会自动展开。QQ的这种特效在一定程度上大大的节约了桌面资源,给使用者带来的方便。

QQ悬挂窗口主要特点就是结合窗口以及鼠标的位置,并通过鼠标事件来调整窗口的显示方式。其中,窗口以及鼠标的位置可以通过GetWindowRect和GetCursorPos这两个函数来获取,故如何获取鼠标事件成为QQ悬挂窗口实现的关键。

对于一个窗口来说,按鼠标事件的触发位置,鼠标事件可以分为三类:

1. 客户区鼠标消息:鼠标在窗口的客户区移动时产生的消息,此消息是标准的鼠标消息,MFC中通过WM_MOUSEMOVE这个事件解决了这个问题。

2. 非客户区鼠标消息:鼠标在非客户区以外(标题栏、框架等)移动时产生的消息,此消息是标准的鼠标消息,MFC中通过WM_NCMOUSEMOVE这个事件解决了这个问题。

3. 窗口以外的鼠标消息:鼠标不在本窗口移动时产生的消息,此消息不是标准的鼠标消息,在MFC中也找不到这样的事件。那该如何捕获这样的鼠标消息呢?

窗口以外的鼠标消息必然是发生在其他窗口上的,此鼠标消息是发往其他窗口的消息队列中,由其他窗口的消息队列所维护。

不过,我们可以通过设置全局鼠标钩子来监视鼠标的位置,并触发鼠标消息。如果将鼠标钩子设置在窗口内部设置的话,那此鼠标钩子仅能够监视到上述鼠标事件的前两类事件,而不能够监视到本窗口以外的鼠标消息,并不是真正的全局鼠标钩子。如果将鼠标钩子设置在DLL中,那么鼠标在整个屏幕上所发生的事件都会被这个鼠标过程所监察到,即可以捕获其他窗口的鼠标消息并将此鼠标消息发往本窗口的所属线程的消息队列中。在本窗口中,必须将本窗口的线程ID传到DLL中,使DLL能够将其他鼠标事件发到指定线程的消息队列中。具体实现如下:

001.//------------------------------------------------------------------------------------
002.  
003.// Function: SetHook - Creates mouse hook (Exported), called by CAppBarMngr
004.  
005.// Arguments: _id - Calling thread ID, used to send message to it
006.  
007.// _width - Width of window
008.  
009.// _left - True if window is left side docked, false if not
010.  
011.// Returns: False if it is already hooked
012.  
013.// True if hook has been created 
014.  
015.//------------------------------------------------------------------------------------
016.  
017.BOOL SetHook(DWORD _id, int _width, BOOL _left)
018.{
019.     if (s_ThreadID)
020.     return FALSE; // Already hooked!
021.                <P>
022.                    <BR>
023.  
024.                </P>
025.s_Width = _width;
026.     s_Left = _left;
027.     g_Hook = ::SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseProc, g_Instance, 0);
028.     s_ThreadID = _id;
029.     return TRUE; // Hook has been created correctly
030.  
031.}
032.  
033.  //-------------------------------------------------------------------------------------
034.  
035.  // Function: MouseProc - Callback function for mouse hook
036.  
037.  // Arguments: nCode - action code, according to MS documentation, must return 
038.  
039.  // inmediatly if less than 0
040.  
041.  // wParam - not used
042.  
043.  // lParam - not used
044.  
045.  // Returns: result from next hook in chain
046.  
047.  //-------------------------------------------------------------------------------------
048.    static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
049.      {
050.       static LRESULT lResult; // Made static to accelerate processing
051.       static POINT pt; // idem
052.            if (nCode<0 && g_Hook) 
053.           {
054.            ::CallNextHookEx(g_Hook, nCode, wParam, lParam); // Call next hook in chain
055.            return 0;
056.           }
057.            if (s_ThreadID) 
058.           {
059.            // Obtain absolute screen coordinates
060.            ::GetCursorPos(&pt);
061.            static POINT ptOld;
062.            //只有当鼠标发生移动时候发生鼠标事件,没有想到鼠标不移动也会产生此鼠标过程,
063.          //真让我大吃一惊,必须得防止鼠标消息乱发。
064.          if(ptOld.x!=pt.x && ptOld.y!=pt.y)
065.           {
066.            ::PostThreadMessage(s_ThreadID, WM_USER+1000, 0, 0); 
067.            ptOld.x = pt.x;
068.            ptOld.y = pt.y;
069.            }
070.      }
071.  return ::CallNextHookEx(g_Hook, nCode, wParam, lParam); // Call next hook in chain
072.   }
073.  
074.  //-------------------------------------------------------------------------------------
075.  
076.  // Function: UnSetHook - Removes hook from chain
077.  
078.  // Arguments: none
079.  
080.  // Returns: False if not hook pending to delete (no thread ID defined)
081.  
082.  // True if hook has been removed. Also returns true if there is not hook
083.  
084.  // handler, this can occur if Init failed when called in second instance
085.  
086.  //-------------------------------------------------------------------------------------
087.  
088.  BOOL UnSetHook()
089.  
090.  {
091.    if (!s_ThreadID) {
092.    return FALSE; // There is no hook pending to close
093.  }
094.  if (g_Hook) { // Check if hook handler is valid
095.  ::UnhookWindowsHookEx(g_Hook); // Unhook is done here
096.  s_ThreadID = 0; // Remove thread id to avoid continue sending
097.  g_Hook = NULL; // Remove hook handler to avoid to use it again
098.  }
099.    return TRUE; // Hook has been removed
100.  
101.}

鼠标消息一旦发到本窗口线程的消息队列后,本窗口过程在鼠标消息未被翻译之前从消息队列中取出消息,并进行处理。故得重载PreTranslateMessage这个虚函数。逻辑判断过程如下:

001.BOOL CHookTestDlg::PreTranslateMessage(MSG* pMsg) 
002.  {
003.  
004.  // TODO: Add your specialized code here and/or call the base class
005.  static int i=0;
006.  int nSrcWidth = ::GetSystemMetrics(SM_CXSCREEN);
007.  int nSrcHeight = ::GetSystemMetrics(SM_CYSCREEN);
008.  switch(pMsg->message)
009.  {
010.  case WM_USER+1000:
011.  {
012.  POINT pt;
013.  CRect rcWindow;
014.  ::GetCursorPos(&pt);
015.  GetWindowRect(&rcWindow);
016.  
017.if(pt.x<1 && (pt.y>rcWindow.top && pt.y99)
018.     {
019.  
020.     }
021.  else if(rcWindow.left>1 && rcWindow.Width()>99)
022.     {
023.      
024.     }
025.  else
026.     {
027.  
028.     }
029.   }
030.  else if(pt.yrcWindow.bottom)
031.   {
032.     if(rcWindow.left<1 && rcWindow.Width()<1)
033.      {
034.  
035.      }
036.     else if(rcWindow.left<1 && rcWindow.Width()>99)
037.      {
038.        SliderWindow(LEFT, false);
039.      }
040.     else if(rcWindow.left>1 && rcWindow.Width()>99)
041.      {
042.       
043.      }
044.     else
045.      {
046.  
047.      }
048.  
049.  }
050.  else if(pt.x>0 && pt.x<100)
051.  {
052.    if(rcWindow.left<1 && rcWindow.Width()<1 && (pt.y>rcWindow.top && pt.y99)
053.      {
054.      //SliderWindow(LEFT, true);
055.      }
056.    else if(rcWindow.left>1 && rcWindow.Width()>99)
057.     {
058.  
059.     }
060.    else
061.     {
062.   
063.     }
064.  }
065.  else
066.  {
067.    if(rcWindow.left<1 && rcWindow.Width()<1)
068.     {
069.  
070.      }
071.    else if(rcWindow.left<1 && rcWindow.Width()>99 && (pt.y>rcWindow.top && pt.y1 && rcWindow.Width()>99)
072.     {
073.    
074.      }
075.    else
076.     {
077.  
078.      }
079.  
080.   }
081.  
082. }
083.  break;
084.  default:
085.  break;
086. }
087.  return CDialog::PreTranslateMessage(pMsg);
088.}
089.  
090.void CHookTestDlg::SliderWindow(int nPos, bool bShow)
091.{
092.  CRect rc;
093.  GetWindowRect(rc);
094.  int nSrcWidth = ::GetSystemMetrics(SM_CXSCREEN);
095.  switch(nPos)
096.  {
097.  case LEFT:
098.  if(bShow)
099.    {
100.    for(int i=0; i<=10; i++)
101.      {
102.        SetWindowPos(&CWnd::wndTopMost, 0, rc.top, i*10, rc.Height(), SWP_SHOWWINDOW);
103.        Sleep(20);
104.       }
105.     }
106.  else
107.    {
108.     for(int i=0; i<=10; i++)
109.      {
110.        SetWindowPos(&CWnd::wndTopMost, 0, rc.top, 100-10*i, rc.Height(), SWP_SHOWWINDOW);
111.        Sleep(20);
112.       }
113.   }
114.   break;
115.   case RIGHT:
116.   break;
117.   case TOP:
118.   break;
119.   case BOTTOM:
120.   break;
121.   default:
122.   break;
123.  
124.  
125.}

最后运行结果如下:

 

朋友们,以后若想捕获其他窗口的鼠标事件的时候可以采用这个方法,大家也可以明白MFC中的标准鼠标消息的底层是怎么实现的,大家是否有眼前一亮的感觉呢?最后提出几个问题:

1、 在安装完上述鼠标钩子后,MFC的标准鼠标消息WM_MOUSEMOVE、WM_LBUTTONDOWN等还有作用吗?

2、 通过MFC的ON_MESSAGE将WM_USER+1000这个与指定的处理过程相关联来处理鼠标消息(当然此时不需要重载PreTranslateMessage),这样做可以吗?如:ON_MESSAGE(WM_USER+1000, MouseProc)。

希望大家给我发邮件进行讨论,祝大家编程愉快!

QQ:181484408 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
WIN32无边框窗体的缩放、移动与WM
_TrackMouseEvent
一个关于鼠标SetCapture()的问题!
鼠标离开窗口或控件及悬停的消息(源码)
Windows消息拦截技术的应用
Windows API 教程(七) hook 钩子监听
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服