上过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
联系客服