如何得知Mouse已离开某物件(Mouse Hook)

来源:cww

常见到某些软体,当Mouse进入其区域时,会启动某个行为,Mouse离开时,又有其他的
动作,例如Cool Bar,当Mouse移入时,Button会上升,离开时Button水变平面。

第一个想到的是在物件的MouseMove中设定进入的行为,这没有问题,但离开呢?有几
个想法:1.如果该物件在Form上,可以在Form的MouseMove上作离开的动作。2.於该物
件的MouseMove上Check是否Mouse的座标已在物件的边缘,若是则执行离开的动作。
但这两者,都会遇上一个问题,如果Mouse的移动很快,使得MouseMove的Event根本没
有在该物件或Form上面发生,那就不可行了;所以看来简单的问题又变复杂了,那只好
使用Mouse Hook来做。

Mouse Hook是拦截硬体所产生Mouse硬体的讯息,不管Mouse现在於何处,都会将Mouse的
讯息送往Hook Procedure,当然,一般情况下,是於该程式正处於Active的情况下
(Local Hook),讯息才会送往该Hook Procedure,如果别的程式所产生的Mouse讯息也要
进入该Hook Function时,那便得使用Remote Hook,而Remote Hook的方式,是要把Hook
 Procedure放在.Dll之中,而Local Hook只要把 Hook Procedure放在.Bas之中便可以了。

因挂上了Mouse Hook(Local),所以该程式执行时所有的Mouse 的讯息便会送往该Hook
Function,而且有包含Mouse所在的座标(相对於Screen),於是我们可以Check Mouse
的座标,进而得知Mouse是否仍在物件范围。

Please Reference : 如何得知Mouse已离开某物件(二)
'以下在.Bas
Option Explicit

Public Const WM_MOUSEMOVE = &H200
Public Const WH_MOUSE = 7

Type POINTAPI
	X As Long
	Y As Long
End Type
Type MOUSEHOOKSTRUCT
	pt As POINTAPI
	hwnd As Long
	wHitTestCode As Long
	dwExtraInfo As Long
End Type
Type RECT
	Left As Long
	Top As Long
	Right As Long
	Bottom As Long
End Type

Declare Function SetWindowsHookEx Lib "user32" Alias _
   "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
   ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Declare Function UnhookWindowsHookEx Lib "user32" _
   (ByVal hHook As Long) As Long
Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _
   ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
  (lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy As Long)
Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
Public theForm As Form
Public hHook As Long   ' handle of Hook Procedure
Public imgRect As RECT
Sub EnableHook(ctl As Control)
   If hHook = 0 Then
      imgRect.Top = ctl.Top
      imgRect.Left = ctl.Left
      imgRect.Right = imgRect.Left + ctl.Width
      imgRect.Bottom = imgRect.Top + ctl.Height
      hHook = SetWindowsHookEx(WH_MOUSE, AddressOf MouseHookProc, App.hInstance, App.ThreadID)
   End If
End Sub
Sub FreeHook()
Dim ret As Long
If hHook <> 0 Then
   ret = UnhookWindowsHookEx(hHook)
   hHook = 0
End If
End Sub
Function MouseHookProc(ByVal code As Long, ByVal wParam As Long, _
		ByVal lParam As Long) As Long
 Dim mStru As MOUSEHOOKSTRUCT, i As Long
 If wParam = WM_MOUSEMOVE Then
    CopyMemory mStru, lParam, LenB(mStru)
    'mStru.pt的座标是萤幕座标,所以要经转换成相对於Form的座标
    Call ScreenToClient(Screen.ActiveForm.hwnd, mStru.pt)

    '不在imgButton之内
    If Not (mStru.pt.Y >= imgRect.Top And mStru.pt.Y <= imgRect.Bottom And _
       mStru.pt.X >= imgRect.Left And mStru.pt.X <= imgRect.Right) Then
       MouseHookProc = 0
       Call CallNextHookEx(hHook, code, wParam, lParam)
       Call FreeHook
       Debug.Print "Out of The Range "
       Exit Function
    Else
       Debug.Print "In The Range"
    End If
 End If
 MouseHookProc = 0 '表示要处理这个讯息
 Call CallNextHookEx(hHook, code, wParam, lParam)
End Function

'以下在Form,需一个Command1
Private Sub Command1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Call EnableHook(Command1)
End Sub

Private Sub Form_Load()
Me.ScaleMode = 3
End Sub