-------------------------------------------------------------
Safe and Efficient Subclassing 
with the MsgHook DLL
-------------------------------------------------------------

MsgHook is an ActiveX DLL that enhances VB with the capability to perform subclassing in a safe and extremely efficient way. These are the main features of the DLL:

*) safe subclassing: can be used within the IDE and in break mode without any risk of system crashes.
*) it provides the BeforeMessage and AfterMessage events for easy event-driven programming model
*) it additionally can notify incoming messages through the IMsgHookEvents secondary interface, for better performance and easier debugging (in some cases, events are inhibited in the IDE)
*) highest flexibility: you can decide to call the original window procedure yourself from within the BeforeMessage event/method and/or cancel the default processing for the message. You can also browse and modify the value that will be returned to the operating system.
*) the DLL's type library includes the definition of over 300 symbolic constants that define the most common Windows messages, so you don't have to use the API Viewer to include them in your applications.

USING MSGHOOK.DLL
----------------------------------

Using the DLL is very simple. After adding a reference to it in the References dialog, you can create one or more instances of the MsgHook class, where each individual instance can subclass a distinct window (that is, a form or control). Note that you can subclass only the forms and controls that belong to the current process, which means that they must be defined in the VB program or in a DLL used by the VB program.

The simplest way to subclass a window is to declare a MsgHook object using the WithEvents keyword, and write appropriate code inside the BeforeMessage and AfterMessage events. As their names suggest, the BeforeMessage event fires before the message is sent to the original window procedure, whereas the AfterMessage event fires after the message has been processed by the original window procedure. In most cases you will use the AfterMessage event, because the BeforeMessage event is mostly necessary when you want to prevent the execution of the original window procedure (in this case you must set the Cancel parameter to True). 

' subclass the Text1 control
Dim WithEvents HookText1 As MsgHook

Private Form_Load()
    Set HookText1 = New MsgHook
    ' pass the hWnd of the control to be subclassed
    HookText1.StartSubclass Text1.hWnd
End Sub

Private Sub HookText1_BeforeMessage(uMsg As Long, wParam As Long, lParam As Long, retValue As Long, Cancel As Boolean)
    If uMsg = WM_CONTEXTMENU Then
        ' Show a custom popup menu.
        PopupMenu mnuPopup
        ' Cancel the default processing
        ' (i.e. don't display the default context menu).
        Cancel = True
    End If
End Sub

If you set Cancel to True in BeforeMessage you can (and often must) assign a value to RetValue argument: this is the value that will be returned to the operating system. If you don't cancel the execution of the original window procedure, you can query and even modify the values passed in wParam and lParam arguments. 

If you write code for the AfterMessage event, you can use the RetValue argument to query the value that is about to be returned to the system, and modify it if you wish. In some cases you can use the value in lParam to access data that was passed to the original window procedure or that the original window procedure is returning to the operating system. Note that the AfterMessage event doesn't offer a Cancel argument, because the orginal window procedure has been already processed and you can't cancel its effects. Here's an example of the AfterMessage event with the code that detects when the form is moved and when the application is activated or deactivated

' we are subclassing the form
Private Sub FormHook_AfterMessage(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, retValue As Long)
    Select Case uMsg
        Case WM_MOVE
            ' The form has moved.
        Case WM_ACTIVATEAPP
             ' id wParam <> 0 then the app is being activated
            If wParam Then
                ' The application is being activated
            Else
                ' The application is being deactivated
            End If
    End Select
End Sub

In same cases, the BeforeMessage and AfterMessage events don't fire. For example, this may happen when testing an application inside the VB IDE and you have a message box active on the screen. To work around this shortcoming, you can also decide to have the MsgHook DLL communicate with your program using the IMsgHookEvents secondary interface. In this case the parent Form must implement this interface:

Implements IMsgHookEvents

' note that no WithEvents is used
Dim TextBoxHook As MsgHook

Private Sub Form_Load()
    ' textbox subclassing uses IMsgHookEvents secondary interface
    Set TextBoxHook = New MsgHook
    TextBoxHook.StartSubclass txtEditor.hWnd, Me
End Sub

Private Sub IMsgHookEvents_AfterMessage(ByVal obj As MsgHookSvr.MsgHook, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ReturnValue As Long)
    ' not used
End Sub

Private Sub IMsgHookEvents_BeforeMessage(ByVal obj As MsgHookSvr.MsgHook, uMsg As Long, wParam As Long, lParam As Long, ReturnValue As Long, Cancel As Boolean)
    ' in this particular case, it isn't necessary to test for
    ' hWnd, because we are subclassing only the txtEditor
    If obj.hWnd = txtEditor.hWnd Then
        If uMsg = WM_CONTEXTMENU Then
            ' Show a custom popup menu.
            PopupMenu mnuPopup
            ' Cancel the default processing
            ' (i.e. don't display the default context menu).
            Cancel = True
        End If
    End If
End Sub

Note that if you subclass multiple controls (or the form and one or more controls), all the instances of the MsgHook class will call the methods of the IMsgHookEvents interface. For this reason, the MsgHook object calling through the interface is also passed to both the BeforeMessage and AfterMessage methods, and you can determine which control the message was sent to by examining the obj.hWnd property, as showed in the previous code snippet.

Apart from this detail, the arguments of the methods of the IMsgHookEvents interfaces are the same received by the BeforeMessage and AfterMessage events seen previously.

PROPERTIES AND METHODS
---------------------------------------------------------

Sub StartSubclass(ObjectOrHandle As Variant, Optional Owner As IMsgHookEvents)

The StartSubclass method can be passed either a window handle (a Long value) or a reference to a VB object that exposes the hWnd property, that is a form or a control. This flexibility lets you subclass a form or a control defined in the current program, or a window that isn't defined in the current program but belongs to the current process (for example, a form or a control in a form embedded in a DLL). 
If you pass a second argoment, it must be a form, a UserControl module, or a class module that implements the IMsgHookEvents secondary interface. In this argument is specified, the MsgHook object will notify incoming messages through this secondary interface instead of the BeforeMessage and AfterMessage events. Note that the "Owner" module doesn't have to be the form that hosts the control being subclassed: for example, you might have a class module monitor all the messsages sent to a form or a control on a form or on a UserControl.

Sub StopSubclass

Terminates all subclassing operations.

Property Get hWnd() As Long

Returns the hWnd of the subclassed window (read-only).

Property Get ObjRef() As Object

Returns a reference to the form or control being subclassed. This is the same object passed as the first argument to the StartSubclass method, or is Nothing if you passed a hWnd value instead of an object reference.

Property Enabled() As Boolean

It lets you enable or disable subclassing. Set this property to False instead of using the StopSubclass method if you plan to re-activate subclassing.

Function CallWndProc(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Call the original window procedure of the window being subclassed. Typically you use this method from within the BeforeMessage event or method, if you want to manually invoke the original window procedure When you do so, remember to set Cancel to True. As a safety trick, the MsgHook class will ignore this method if the original window procedure has been already executed (e.g. when you call this method from within the AfterMessage event or method).

USING API MESSAGE CONSTANTS
-------------------------------------------------------

The type library embedded in MsgHook.Dll includes the definition of over 300 windows messages, including all the most frequently used messages such as WM_MOVE or WM_APPACTIVATE, all the keyboard and mouse messages, and all the messages related to TextBox, ListBox, ComboBox, and ScrollBar controls.

This will save you a lot of time and trips to the API Viewer during the development phase.





