上过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 oid continue sending g_Hook = NULL; // Remove hook handler to oid to use it again }
return TRUE; // Hook has been removed
}
鼠标消息一旦发到本窗口线程的消息队列后,本窗口过程在鼠标消息未被翻译之前从消息队列中取出消息,并进行处理。故得重载PreTranslateMessage这个虚函数。逻辑判断过程如下:
BOOL CHookTestDlg::PreTranslateMessage(MSG* pMsg) {
// TODO: Add your specialized code here and/or call the base class static int i=0; int nSrcWidth = ::GetSystemMetrics(SM_CXSCREEN); int nSrcHeight = ::GetSystemMetrics(SM_CYSCREEN); switch(pMsg->message) { case WM_USER+1000: { POINT pt; CRect rcWindow; ::GetCursorPos(&pt); GetWindowRect(&rcWindow);
if(pt.x<1 && (pt.y>rcWindow.top && pt.y<rcWindow.bottom)) { if(rcWindow.left<1 && rcWindow.Width()<1) { SliderWindow(LEFT, true); } else if(rcWindow.left<1 && rcWindow.Width()>99) {
} else if(rcWindow.left>1 && rcWindow.Width()>99) {
} else {
} } else if(pt.y<rcWindow.top || pt.y>rcWindow.bottom) { if(rcWindow.left<1 && rcWindow.Width()<1) {
} else if(rcWindow.left<1 && rcWindow.Width()>99) { SliderWindow(LEFT, false); } else if(rcWindow.left>1 && rcWindow.Width()>99) {
} else {
}
} else if(pt.x>0 && pt.x<100) { if(rcWindow.left<1 && rcWindow.Width()<1 && (pt.y>rcWindow.top && pt.y<rcWindow.bottom)) {
} else if(rcWindow.left<1 && rcWindow.Width()>99) { //SliderWindow(LEFT, true); } else if(rcWindow.left>1 && rcWindow.Width()>99) {
} else {
} } else { if(rcWindow.left<1 && rcWindow.Width()<1) {
} else if(rcWindow.left<1 && rcWindow.Width()>99 && (pt.y>rcWindow.top && pt.y<rcWindow.bottom)) { SliderWindow(LEFT, false); } else if(rcWindow.left>1 && rcWindow.Width()>99) {
} else {
}
}
} break; default: break; } return CDialog::PreTranslateMessage(pMsg);}
void CHookTestDlg::SliderWindow(int nPos, bool bShow){ CRect rc; GetWindowRect(rc); int nSrcWidth = ::GetSystemMetrics(SM_CXSCREEN); switch(nPos) { case LEFT: if(bShow) { for(int i=0; i<=10; i++) { SetWindowPos(&CWnd::wndTopMost, 0, rc.top, i*10, rc.Height(), SWP_SHOWWINDOW); Sleep(20); } } else { for(int i=0; i<=10; i++) { SetWindowPos(&CWnd::wndTopMost, 0, rc.top, 100-10*i, rc.Height(), SWP_SHOWWINDOW); Sleep(20); } } break; case RIGHT: break; case TOP: break; case BOTTOM: break; default: break; }
}
朋友们,以后若想捕获其他窗口的鼠标事件的时候可以采用这个方法,大家也可以明白MFC中的标准鼠标消息的底层是怎么实现的,大家是否有眼前一亮的感觉呢?最后提出几个问题:
1、 在安装完上述鼠标钩子后,MFC的标准鼠标消息WM_MOUSEMOVE、WM_LBUTTONDOWN等还有作用吗?
2、 通过MFC的ON_MESSAGE将WM_USER+1000这个与指定的处理过程相关联来处理鼠标消息(当然此时不需要重载PreTranslateMessage),这样做可以吗?如:ON_MESSAGE(WM_USER+1000, MouseProc)。
希望大家给我发邮件进行讨论,祝大家编程愉快!
|