仿QQ悬挂窗口的实现2010-07-20 vckbase 郑瑜上过QQ的朋友们都知道,当QQ窗口位于桌面的左边界、右边界或顶部的时候,QQ会自动隐藏起来;而一旦鼠标再次接触到上述边界的时候,QQ窗口又会自动展开。QQ的这种特效在一定程度上大大的节约了桌面资源,给使用者带来的方便。QQ悬挂窗口主要特点就是结合窗口以及鼠标的位置,并通过鼠标事件来调整窗口的显示方式。其中,窗口以及鼠标的位置可以通过GetWindowRect和GetCursorPos这两个函数来获取,故如何获取鼠标事件成为QQ悬挂窗口实现的关键。对于一个窗口来说,按鼠标事件的触发位置,鼠标事件可以分为三类:1. 客户区鼠标消息:鼠标在窗口的客户区移动时产生的消息,此消息是标准的鼠标消息,MFC中通过WM_MOUSEMOVE这个事件解决了这个问题。2. 非客户区鼠标消息:鼠标在非客户区以外(标题栏、框架等)移动时产生的消息,此消息是标准的鼠标消息,MFC中通过WM_NCMOUSEMOVE这个事件解决了这个问题。3. 窗口以外的鼠标消息:鼠标不在本窗口移动时产生的消息,此消息不是标准的鼠标消息,在MFC中也找不到这样的事件。那该如何捕获这样的鼠标消息呢?窗口以外的鼠标消息必然是发生在其他窗口上的,此鼠标消息是发往其他窗口的消息队列中,由其他窗口的消息队列所维护。不过,我们可以通过设置全局鼠标钩子来监视鼠标的位置,并触发鼠标消息。如果将鼠标钩子设置在窗口内部设置的话,那此鼠标钩子仅能够监视到上述鼠标事件的前两类事件,而不能够监视到本窗口以外的鼠标消息,并不是真正的全局鼠标钩子。如果将鼠标钩子设置在DLL中,那么鼠标在整个屏幕上所发生的事件都会被这个鼠标过程所监察到,即可以捕获其他窗口的鼠标消息并将此鼠标消息发往本窗口的所属线程的消息队列中。在本窗口中,必须将本窗口的线程ID传到DLL中,使DLL能够将其他鼠标事件发到指定线程的消息队列中。具体实现如下://------------------------------------------------------------------------------------ // Function: SetHook - Creates mouse hook (Exported), called by CAppBarMngr // Arguments: _id - Calling thread ID, used to send message to it // _width - Width of window // _left - True if window is left side docked, false if not // Returns: False if it is already hooked // True if hook has been created //------------------------------------------------------------------------------------ BOOL SetHook(DWORD _id, int _width, BOOL _left) { if (s_ThreadID) return FALSE; // Already hooked! s_Width = _width; s_Left = _left; g_Hook = ::SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseProc, g_Instance, 0); s_ThreadID = _id; return TRUE; // Hook has been created correctly } //------------------------------------------------------------------------------------- // Function: MouseProc - Callback function for mouse hook // Arguments: nCode - action code, according to MS documentation, must return // inmediatly if less than 0 // wParam - not used // lParam - not used // Returns: result from next hook in chain //------------------------------------------------------------------------------------- static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { static LRESULT lResult; // Made static to accelerate processing static POINT pt; // idem if (nCode<0 && g_Hook) { ::CallNextHookEx(g_Hook, nCode, wParam, lParam); // Call next hook in chain return 0; } if (s_ThreadID) { // Obtain absolute screen coordinates ::GetCursorPos(&pt); static POINT ptOld; //只有当鼠标发生移动时候发生鼠标事件,没有想到鼠标不移动也会产生此鼠标过程, //真让我大吃一惊,必须得防止鼠标消息乱发。 if(ptOld.x!=pt.x && ptOld.y!=pt.y) { ::PostThreadMessage(s_ThreadID, WM_USER+1000, 0, 0); ptOld.x = pt.x; ptOld.y = pt.y; } } return ::CallNextHookEx(g_Hook, nCode, wParam, lParam); // Call next hook in chain } //------------------------------------------------------------------------------------- // Function: UnSetHook - Removes hook from chain // Arguments: none // Returns: False if not hook pending to delete (no thread ID defined) // True if hook has been removed. Also returns true if there is not hook // handler, this can occur if Init failed when called in second instance //------------------------------------------------------------------------------------- BOOL UnSetHook() { if (!s_ThreadID) { return FALSE; // There is no hook pending to close } if (g_Hook) { // Check if hook handler is valid ::UnhookWindowsHookEx(g_Hook); // Unhook is done here s_ThreadID = 0; // Remove thread id to avoid continue sending g_Hook = NULL; // Remove hook handler to avoid to use it again } return TRUE; // Hook has been removed }