This is code that goes into, well, probably every project I ever
write. I was once kidded by the author of MsgHook
that I virtually made a living explaining to people how to use that tool. Yeah,
I wrote a lot of columns over the years, some listed below, that involved
subclassing. But since VB5 shipped, production code omitted the OCXs, and stuck
to pure inline message hooking.
Originally, I used a single BAS file for all subclassing, but found that
solution to be wanting. In particular, it meant I had to modify the code module
for each application. And, it prevented sinking the messages in more than a
single object type. With my latest approach, I think you'll agree, we've come to
be about as flexible as possible.
HookMe now includes an IHookSink interface that allows you to redirect window
messages back into any object you desire. Your forms can sink their messages
within themselves, or delegate message handling to companion or even global
classes. Here's the entire IHookSink interface:
Public Function WindowProc( _
hWnd As Long, msg As Long, wp As Long, lp As Long) As Long
' Stub to be used with MHookMe.bas
End Function
In your class, control, or form module, you simply Implement IHookSink, then
provide code for the WindowProc method. As a very simple example, to watch the
message stream for every textbox on a form, and "eat" any WM_PASTE
messages that happen to not meet some arbitrary criteria (say, evaluate to
positive values), you could use code like this:
Option Explicit
Private Const WM_PASTE As Long = &H302
Implements IHookSink
Private Sub Form_Load()
Dim i As Long
For i = Text1.LBound To Text1.UBound
Call HookWindow(Text1(i).hWnd, Me)
Next i
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim i As Long
For i = Text1.LBound To Text1.UBound
Call UnhookWindow(Text1(i).hWnd)
Next i
End Sub
Private Function IHookSink_WindowProc(hWnd As Long, msg As Long, wp As Long, lp As Long) As Long
Dim buffer As String
Dim nVal As Long
Dim nRet As Long
Select Case msg
Case WM_PASTE
' Validate clipboard contents.
If Clipboard.GetFormat(vbCFText) Then
buffer = Clipboard.GetText
On Error Resume Next
nVal = CLng(buffer)
On Error GoTo 0
' Only allow >0 values to be pasted.
If nVal > 0 Then
nRet = InvokeWindowProc(hWnd, msg, wp, lp)
Else
nRet = 0 ' eat it!
End If
End If
Case Else
nRet = InvokeWindowProc(hWnd, msg, wp, lp)
End Select
' Return results.
IHookSink_WindowProc = nRet
End Function
Note the second parameter to the HookWindow procedure. Here, we used Me, to
route window messages back into the current form. But that parameter could be any
object that implements the IHookSink interface.
But Is It Safe?
It is, if you save. Seriously, running message hooks within the IDE is just
downright inherently problematic if you enter Break Mode. Some people use
special purpose DLLs to protect them from this situation. I prefer to just be
careful. Often, I test the compiled state of an application before calling
HookWindow, especially after I have fully debugged the WindowProc code. I'd
advise the same precaution.