From: MikeD on

"Matthias Immhoff" <m.immhoff(a)gmx.de> wrote in message
news:%23dSxp5wfIHA.1132(a)TK2MSFTNGP06.phx.gbl...
> You must subclass. I attached a sample, I think it's convenient.
> Cheers!

No binary attachments in here please. Any attachment is frowned upon here,
but if you must make an attachment(s), ONLY attach text files.

--
Mike
Microsoft MVP Visual Basic


From: Lorin on
Try
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=67440&lngWId=1

"JanAdam" wrote:

> How to read mouse wheel rotation in VB6?
> I am not a programmer. I need to control a step motor from a computer via a
> serial port. I am trying to write a code to move the motor one way or another
> by using a mouse wheel, when a mouse cursor is placed on a control button on
> a form. I know the protocol for the motor controller. I need to read the
> number of wheel steps and direction of the wheel rotation. I know how to make
> the form responding to clicking on the button, but do not know how to make it
> responding to the wheel rotation. WM_MOUSEWHEEL notification gives me all
> that I need but I do not know how to use it in my control button code in VB6.
> Any help will be greatly appreciated.
>
> --
> JanAdam
From: Karl E. Peterson on
JanAdam wrote:
> Karl, no need for sarcasm.

Sorry.

> I did Google before posting. Most relevant postings I found deal with using
> the wheel in IDE. Some are written in languages I do not know. Some are by
> far too advanced for me as I only have a rudimentary knowledge of VB.

That's gonna be an issue, then. I'm not aware of any really easy ways to get at
that message without directly subclassing windows in your app for it. It's not
difficult code, but it's definitely not double-click, point, drag, etc.

> tried to use the code in http://www.adit.co.uk/html/mousewheelsupport.htmlf
> but run into problems with type declarations. I only posted my question
> because I need some help.

Ya gotta admit, it was a pretty wide open question. This narrows it down quite a
bit. The more specific you are, the more so we can be too.
--
..NET: It's About Trust!
http://vfred.mvps.org


From: BeastFish on
"JanAdam" <JanAdam(a)discussions.microsoft.com> wrote in message
news:4F1CDAC9-BA1C-44AF-9C1A-DCE53E15368B(a)microsoft.com...
> How to read mouse wheel rotation in VB6?
> I am not a programmer. I need to control a step motor from a computer via
a
> serial port. I am trying to write a code to move the motor one way or
another
> by using a mouse wheel, when a mouse cursor is placed on a control button
on
> a form. I know the protocol for the motor controller. I need to read the
> number of wheel steps and direction of the wheel rotation. I know how to
make
> the form responding to clicking on the button, but do not know how to make
it
> responding to the wheel rotation. WM_MOUSEWHEEL notification gives me all
> that I need but I do not know how to use it in my control button code in
VB6.
> Any help will be greatly appreciated.



VB forms, controls, et al don't natively handle mouse scroll messages, so
you're going to have to do some subclassing (hook into the windows messages
and look for WM_MOUSEWHEEL yourself). Since command buttons have a handle
(hWnd), you can hook into the particular command button. But I would
probably hook the form itself and just check if the mouse is over the
desired button.

Here's a quick and dirty sample (just did some quick copy/paste-ing, so it
could likely be better). For a non-programmer, this is enough to make you
dangerous :-) So don't try to incorporate it into the main project until
you understand it... what's going on. Put this into a "test" project first.
Fair warning... since it involves subclassing, DO NOT use the IDE's "stop"
button (or the End statement in code), close the form so it's Unload event
executes or it will explode! Also, please watch for any wordwrapping in the
code below...

Start a new, fresh VB6 project. On the form, place a command button and
paste this code into the form's (General)(Declarations)...
=====================================================
Private Sub Form_Load()
Call HasWheelScroll

' "register" handle of control for scroll messages
HandleForScroll = Command1.hwnd

' Store handle of the hooked winder
HookedHandle = Form1.hwnd
' Start capturing messages for it
Call Hook
End Sub


Private Sub Form_Unload(Cancel As Integer)
' Stop hookin' (* very important *)
Call Unhook
End Sub

=====================================================

Not too bad, eh? But wait, there's more.

Now open a new Bas module (subclassing needs to be done in a bas module),
and paste this code into the module's (General)(Declarations)...

=====================================================
Option Explicit

Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long

Public Const GWL_WNDPROC = -4

Public lpPrevWndProc As Long
Public HookedHandle As Long

' Mouse scrollwheel
Private Const WM_MOUSEWHEEL = &H20A
Private Const WM_VSCROLL = &H115
Private Const WHEEL_DELTA = 120

' Mouse Scrollwheel init
Private Declare Function GetSystemMetrics Lib "user32" _
(ByVal nIndex As Integer) As Integer
Private Declare Function SystemParametersInfo _
Lib "user32" Alias "SystemParametersInfoA" _
(ByVal uAction As Long, ByVal uParam As Long, _
ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
Private Const SM_MOUSEWHEELPRESENT = 75
Private Const SPI_GETWHEELSCROLLLINES = 104
Private WheelScrollLines As Long

' Iz mouse over "registered" control stuff
Public HandleForScroll As Long
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Declare Function GetCursorPos Lib "user32" _
(lpPoint As POINTAPI) As Long
Private Declare Function WindowFromPoint Lib "user32" _
(ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private wPt As POINTAPI

Public Sub Hook()
lpPrevWndProc = SetWindowLong(HookedHandle, _
GWL_WNDPROC, _
AddressOf WindowProc)
End Sub

Public Sub Unhook()
Call SetWindowLong(HookedHandle, _
GWL_WNDPROC, _
lpPrevWndProc)
End Sub

Public Sub HasWheelScroll()
' See if da mousie has a scroll wheel
If GetSystemMetrics(SM_MOUSEWHEELPRESENT) <> 0 Then
' Yup, it does
If SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, WheelScrollLines,
0) = 0 Then
' Didn't retrieve a value, so use a default value
WheelScrollLines = 3
End If
Else
' No mouse wheel present
WheelScrollLines = 0
End If
End Sub

Function WindowProc(ByVal hw As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long

' Handle mousewheel scroll messages
If uMsg = WM_MOUSEWHEEL Then
' Iz mousie on "registered" control/winder
Call GetCursorPos(wPt)
If WindowFromPoint(wPt.X, wPt.Y) = HandleForScroll Then
Dim ScrollDistance As Long
ScrollDistance = wParam \ &H10000 \ WHEEL_DELTA
If ScrollDistance > 0 Then
Debug.Print "Scroll - " & CStr(Abs(ScrollDistance) *
WheelScrollLines)
Else
Debug.Print "Scroll + " & CStr(Abs(ScrollDistance) *
WheelScrollLines)
End If
End If
End If

' Pass message on to the original window message handler
WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)
End Function
=====================================================


Now, save your project as a test project. Then run it, move the mouse over
the command button and scroll to your heart's content, look at the IDE's
intermediate window (debug winder).

Remember, this is enough to cause havoc and anarchy for someone that doesn't
quite understand what's going on. So don't throw this into the actual
project until you're comfortable with it and have some understanding of what
it's doing... do it as a test or sample project first.

Quick and dirty explaination of the subclassing... The Hook sub starts it
off by specifying which window to intercept messages for and where to
re-direct those messages (WindowProc, as specified with AddressOf). The
WindowProc function is where all the messages for that window are now
flowing through, where you can check for a particular message and act
accordingly (in this case, the WM_MOUSEWHEEL message). The last line in
WindowProc sends all the messages back to the normal message handler. The
Unhook sub stops the subclassing, resets it back to "normal" (which is
important unless you like GPFs et al).

Have fun.


From: Matthias Immhoff on
Okay, no binary attachments...
Then do the following.
Create a sample project that has 1 form.
Add an empty usercontrol.
Put the code below in the usercontrol, overwriting its current context
completely.
Put the usercontrol on your form.
Make a label and add the following to the form:

Private Sub ucSubclass1_MouseWheel(ByVal Distance As Integer)

Static lCount&
lCount = lCount + 1

Me.Label1.Caption = "The mouse wheel was triggered for the " &
lCount & ". time." & vbNewLine & "Wheel scrolled up: " & IIf(Distance =
1, True, False)

End Sub
'///////////////////////////////////////////////////////

Usercontrol code:
'//////////////////////////////////////////////////////////

Option Explicit

Public Event MouseEnter()
Public Event MouseLeave()
Public Event MouseWheel(ByVal Distance As Integer)
Public Event Status(ByVal sStatus As String)

Private Const WM_MOUSEWHEEL As Long = &H20A
Private Const WHEEL_DELTA As Long = 120 ' Default value for rolling one
notch
Private Const WM_MOUSEMOVE As Long = &H200
Private Const WM_MOUSELEAVE As Long = &H2A3
Private Const WM_MOVING As Long = &H216
Private Const WM_SIZING As Long = &H214
Private Const WM_EXITSIZEMOVE As Long = &H232

Private Enum TRACKMOUSEEVENT_FLAGS
TME_HOVER = &H1&
TME_LEAVE = &H2&
TME_QUERY = &H40000000
TME_CANCEL = &H80000000
End Enum

Private Type TRACKMOUSEEVENT_STRUCT
cbSize As Long
dwFlags As TRACKMOUSEEVENT_FLAGS
hwndTrack As Long
dwHoverTime As Long
End Type

Private bTrack As Boolean
Private bTrackUser32 As Boolean
Private bInCtrl As Boolean

Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As
Long) As Long
Private Declare Function LoadLibraryA Lib "kernel32" (ByVal
lpLibFileName As String) As Long
Private Declare Function TrackMouseEvent Lib "user32" (lpEventTrack As
TRACKMOUSEEVENT_STRUCT) As Long
Private Declare Function TrackMouseEventComCtl Lib "Comctl32" Alias
"_TrackMouseEvent" (lpEventTrack As TRACKMOUSEEVENT_STRUCT) As Long

'==================================================================================================
'Subclasser declarations

Private Enum eMsgWhen
MSG_AFTER = 1
'Message calls back after the original (previous) WndProc
MSG_BEFORE = 2
'Message calls back before the original (previous) WndProc
MSG_BEFORE_AND_AFTER = MSG_AFTER Or MSG_BEFORE
'Message calls back before and after the original
(previous) WndProc
End Enum

Private Const ALL_MESSAGES As Long = -1
'All messages added or deleted
Private Const GMEM_FIXED As Long = 0
'Fixed memory GlobalAlloc flag
Private Const GWL_WNDPROC As Long = -4
'Get/SetWindow offset to the WndProc procedure address
Private Const PATCH_04 As Long = 88
'Table B (before) address patch offset
Private Const PATCH_05 As Long = 93
'Table B (before) entry count patch offset
Private Const PATCH_08 As Long = 132
'Table A (after) address patch offset
Private Const PATCH_09 As Long = 137
'Table A (after) entry count patch offset

Private Type tSubData
'Subclass data type
hWnd As Long
'Handle of the window being subclassed
nAddrSub As Long
'The address of our new WndProc (allocated memory).
nAddrOrig As Long
'The address of the pre-existing WndProc
nMsgCntA As Long
'Msg after table entry count
nMsgCntB As Long
'Msg before table entry count
aMsgTblA() As Long
'Msg after table array
aMsgTblB() As Long
'Msg Before table array
End Type

Private sc_aSubData() As tSubData
'Subclass data array

Private Declare Sub RtlMoveMemory Lib "kernel32" (Destination As Any,
Source As Any, ByVal Length As Long)
Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal
lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As
Long, ByVal lpProcName As String) As Long
Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As
Long, ByVal dwBytes As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long)
As Long
Private Declare Function SetWindowLongA Lib "user32" (ByVal hWnd As
Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
'==================================================================================================

'UserControl events

'Read the properties from the property bag - also, a good place to start
the subclassing (if we're running)
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
With PropBag
'Read your properties here
'm_PropName = .ReadProperty("PropName", DEF_PROP_VALUE)
End With

If Ambient.UserMode Then
'If we're not in design mode
bTrack = True
bTrackUser32 = IsFunctionExported("TrackMouseEvent", "User32")

If Not bTrackUser32 Then
If Not IsFunctionExported("_TrackMouseEvent", "Comctl32") Then
bTrack = False
End If
End If

If bTrack Then
'OS supports mouse leave so subclass for it
With UserControl
'Start subclassing the UserControl
Call Subclass_Start(.hWnd)
Call Subclass_AddMsg(.hWnd, WM_MOUSEMOVE, MSG_AFTER)
Call Subclass_AddMsg(.hWnd, WM_MOUSELEAVE, MSG_AFTER)

'Start subclassing the Parent form
With .Parent
Call Subclass_Start(.hWnd)
Call Subclass_AddMsg(.hWnd, WM_MOVING, MSG_AFTER)
Call Subclass_AddMsg(.hWnd, WM_SIZING, MSG_AFTER)
Call Subclass_AddMsg(.hWnd, WM_EXITSIZEMOVE, MSG_AFTER)
Call Subclass_AddMsg(.hWnd, WM_MOUSEWHEEL, MSG_AFTER)
End With
End With
End If
End If
End Sub

Private Sub UserControl_Resize()

Static inProc As Boolean
If inProc Then Exit Sub

UserControl.Width = 32 * Screen.TwipsPerPixelX
UserControl.Height = 32 * Screen.TwipsPerPixelY

inProc = False

End Sub

'The control is terminating - a good place to stop the subclasser
Private Sub UserControl_Terminate()
On Error GoTo Catch
'Stop all subclassing
Call Subclass_StopAll
Catch:
End Sub

'======================================================================================================
'UserControl private routines

'Determine if the passed function is supported
Private Function IsFunctionExported(ByVal sFunction As String, ByVal
sModule As String) As Boolean
Dim hMod As Long
Dim bLibLoaded As Boolean

hMod = GetModuleHandleA(sModule)

If hMod = 0 Then
hMod = LoadLibraryA(sModule)
If hMod Then
bLibLoaded = True
End If
End If

If hMod Then
If GetProcAddress(hMod, sFunction) Then
IsFunctionExported = True
End If
End If

If bLibLoaded Then
Call FreeLibrary(hMod)
End If
End Function

'Track the mouse leaving the indicated window
Private Sub TrackMouseLeave(ByVal lng_hWnd As Long)
Dim tme As TRACKMOUSEEVENT_STRUCT

If bTrack Then
With tme
.cbSize = Len(tme)
.dwFlags = TME_LEAVE
.hwndTrack = lng_hWnd
End With

If bTrackUser32 Then
Call TrackMouseEvent(tme)
Else
Call TrackMouseEventComCtl(tme)
End If
End If
End Sub

'======================================================================================================
'Subclass handler - MUST be the first Public routine in this file. That
includes public properties also

Public Sub zSubclass_Proc(ByVal bBefore As Boolean, ByRef bHandled As
Boolean, ByRef lReturn As Long, ByRef lng_hWnd As Long, ByRef uMsg As
Long, ByRef wParam As Long, ByRef lParam As Long)
'Parameters:
'bBefore - Indicates whether the the message is being processed
before or after the default handler - only really needed if a message is
set to callback both before & after.
'bHandled - Set this variable to True in a 'before' callback to
prevent the message being subsequently processed by the default
handler... and if set, an 'after' callback
'lReturn - Set this variable as per your intentions and
requirements, see the MSDN documentation for each individual message value.
'hWnd - The window handle
'uMsg - The message number
'wParam - Message related data
'lParam - Message related data
'Notes:
'If you really know what you're doing, it's possible to change the
values of the
'hWnd, uMsg, wParam and lParam parameters in a 'before' callback so
that different
'values get passed to the default handler.. and optionaly, the
'after' callback
Static bMoving As Boolean

Select Case uMsg
Case WM_MOUSEMOVE
If Not bInCtrl Then
bInCtrl = True
Call TrackMouseLeave(lng_hWnd)
RaiseEvent MouseEnter
End If

Case WM_MOUSELEAVE
bInCtrl = False
RaiseEvent MouseLeave

Case WM_MOVING
bMoving = True
RaiseEvent Status("Form is moving...")

Case WM_SIZING
bMoving = False
RaiseEvent Status("Form is sizing...")

Case WM_EXITSIZEMOVE
RaiseEvent Status("Finished " & IIf(bMoving, "moving.", "sizing."))

Case WM_MOUSEWHEEL
Dim Distance As Long
Dim yPos As Long
Dim xPos As Long

Distance = HighWord(wParam) \ WHEEL_DELTA

RaiseEvent MouseWheel(Distance)


End Select
End Sub

'======================================================================================================
'Subclass code - The programmer may call any of the following
Subclass_??? routines

'Add a message to the table of those that will invoke a callback. You
should Subclass_Start first and then add the messages
Private Sub Subclass_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long,
Optional ByVal When As eMsgWhen = MSG_AFTER)
'Parameters:
'lng_hWnd - The handle of the window for which the uMsg is to be
added to the callback table
'uMsg - The message number that will invoke a callback. NB Can
also be ALL_MESSAGES, ie all messages will callback
'When - Whether the msg is to callback before, after or both
with respect to the the default (previous) handler
With sc_aSubData(zIdx(lng_hWnd))
If When And eMsgWhen.MSG_BEFORE Then
Call zAddMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE,
..nAddrSub)
End If
If When And eMsgWhen.MSG_AFTER Then
Call zAddMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER,
..nAddrSub)
End If
End With
End Sub

'Delete a message from the table of those that will invoke a callback.
Private Sub Subclass_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long,
Optional ByVal When As eMsgWhen = MSG_AFTER)
'Parameters:
'lng_hWnd - The handle of the window for which the uMsg is to be
removed from the callback table
'uMsg - The message number that will be removed from the
callback table. NB Can also be ALL_MESSAGES, ie all messages will callback
'When - Whether the msg is to be removed from the before, after
or both callback tables
With sc_aSubData(zIdx(lng_hWnd))
If When And eMsgWhen.MSG_BEFORE Then
Call zDelMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE,
..nAddrSub)
End If
If When And eMsgWhen.MSG_AFTER Then
Call zDelMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER,
..nAddrSub)
End If
End With
End Sub

'Return whether we're running in the IDE.
Private Function Subclass_InIDE() As Boolean
Debug.Assert zSetTrue(Subclass_InIDE)
End Function

'Start subclassing the passed window handle
Private Function Subclass_Start(ByVal lng_hWnd As Long) As Long
'Parameters:
'lng_hWnd - The handle of the window to be subclassed
'Returns;
'The sc_aSubData() index
Const CODE_LEN As Long = 200
'Length of the machine code in bytes
Const FUNC_CWP As String = "CallWindowProcA"
'We use CallWindowProc to call the original WndProc
Const FUNC_EBM As String = "EbMode"
'VBA's EbMode function allows the machine code thunk
to know if the IDE has stopped or is on a breakpoint
Const FUNC_SWL As String = "SetWindowLongA"
'SetWindowLongA allows the cSubclasser machine code
thunk to unsubclass the subclasser itself if it detects via the EbMode
function that the IDE has stopped
Const MOD_USER As String = "user32"
'Location of the SetWindowLongA & CallWindowProc functions
Const MOD_VBA5 As String = "vba5"
'Location of the EbMode function if running VB5
Const MOD_VBA6 As String = "vba6"
'Location of the EbMode function if running VB6
Const PATCH_01 As Long = 18
'Code buffer offset to the location of the relative
address to EbMode
Const PATCH_02 As Long = 68
'Address of the previous WndProc
Const PATCH_03 As Long = 78
'Relative address of SetWindowsLong
Const PATCH_06 As Long = 116
'Address of the previous WndProc
Const PATCH_07 As Long = 121
'Relative address of CallWindowProc
Const PATCH_0A As Long = 186
'Address of the owner object
Static aBuf(1 To CODE_LEN) As Byte
'Static code buffer byte array
Static pCWP As Long
'Address of the CallWindowsProc
Static pEbMode As Long
'Address of the EbMode IDE break/stop/running function
Static pSWL As Long
'Address of the SetWindowsLong function
Dim i As Long
'Loop index
Dim j As Long
'Loop index
Dim nSubIdx As Long
'Subclass data index
Dim sHex As String
'Hex code string

'If it's the first time through here..
If aBuf(1) = 0 Then

'The hex pair machine code representation.
sHex =
"5589E583C4F85731C08945FC8945F8EB0EE80000000083F802742185C07424E830000000837DF800750AE838000000E84D00"
& _

"00005F8B45FCC9C21000E826000000EBF168000000006AFCFF7508E800000000EBE031D24ABF00000000B900000000E82D00"
& _

"0000C3FF7514FF7510FF750CFF75086800000000E8000000008945FCC331D2BF00000000B900000000E801000000C3E33209"
& _

"C978078B450CF2AF75278D4514508D4510508D450C508D4508508D45FC508D45F85052B800000000508B00FF90A4070000C3"

'Convert the string from hex pairs to bytes and store in the static
machine code buffer
i = 1
Do While j < CODE_LEN
j = j + 1
aBuf(j) = Val("&H" & Mid$(sHex, i, 2))
'Convert a pair of hex characters to an eight-bit
value and store in the static code buffer array
i = i + 2
Loop
'Next pair of hex characters

'Get API function addresses
If Subclass_InIDE Then
'If we're running in the VB IDE
aBuf(16) = &H90
'Patch the code buffer to enable the IDE state code
aBuf(17) = &H90
'Patch the code buffer to enable the IDE state code
pEbMode = zAddrFunc(MOD_VBA6, FUNC_EBM)
'Get the address of EbMode in vba6.dll
If pEbMode = 0 Then
'Found?
pEbMode = zAddrFunc(MOD_VBA5, FUNC_EBM)
'VB5 perhaps
End If
End If

pCWP = zAddrFunc(MOD_USER, FUNC_CWP)
'Get the address of the CallWindowsProc function
pSWL = zAddrFunc(MOD_USER, FUNC_SWL)
'Get the address of the SetWindowLongA function
ReDim sc_aSubData(0 To 0) As tSubData
'Create the first sc_aSubData element
Else
nSubIdx = zIdx(lng_hWnd, True)
If nSubIdx = -1 Then
'If an sc_aSubData element isn't being re-cycled
nSubIdx = UBound(sc_aSubData()) + 1
'Calculate the next element
ReDim Preserve sc_aSubData(0 To nSubIdx) As tSubData
'Create a new sc_aSubData element
End If

Subclass_Start = nSubIdx
End If

With sc_aSubData(nSubIdx)
.hWnd = lng_hWnd
'Store the hWnd
.nAddrSub = GlobalAlloc(GMEM_FIXED, CODE_LEN)
'Allocate memory for the machine code WndProc
.nAddrOrig = SetWindowLongA(.hWnd, GWL_WNDPROC, .nAddrSub)
'Set our WndProc in place
Call RtlMoveMemory(ByVal .nAddrSub, aBuf(1), CODE_LEN)
'Copy the machine code from the static byte array to
the code array in sc_aSubData
Call zPatchRel(.nAddrSub, PATCH_01, pEbMode)
'Patch the relative address to the VBA EbMode api
function, whether we need to not.. hardly worth testing
Call zPatchVal(.nAddrSub, PATCH_02, .nAddrOrig)
'Original WndProc address for CallWindowProc, call the
original WndProc
Call zPatchRel(.nAddrSub, PATCH_03, pSWL)
'Patch the relative address of the SetWindowLongA api
function
Call zPatchVal(.nAddrSub, PATCH_06, .nAddrOrig)
'Original WndProc address for SetWindowLongA,
unsubclass on IDE stop
Call zPatchRel(.nAddrSub, PATCH_07, pCWP)
'Patch the relative address of the CallWindowProc api
function
Call zPatchVal(.nAddrSub, PATCH_0A, ObjPtr(Me))
'Patch the address of this object instance into the
static machine code buffer
End With
End Function

'Stop all subclassing
Private Sub Subclass_StopAll()
Dim i As Long

i = UBound(sc_aSubData())
'Get the upper bound of the subclass data array
Do While i >= 0
'Iterate through each element
With sc_aSubData(i)
If .hWnd <> 0 Then
'If not previously Subclass_Stop'd
Call Subclass_Stop(.hWnd)
'Subclass_Stop
End If
End With

i = i - 1
'Next element
Loop
End Sub

'Stop subclassing the passed window handle
Private Sub Subclass_Stop(ByVal lng_hWnd As Long)
'Parameters:
'lng_hWnd - The handle of the window to stop being subclassed
With sc_aSubData(zIdx(lng_hWnd))
Call SetWindowLongA(.hWnd, GWL_WNDPROC, .nAddrOrig)
'Restore the original WndProc
Call zPatchVal(.nAddrSub, PATCH_05, 0)
'Patch the Table B entry count to ensure no further
'before' callbacks
Call zPatchVal(.nAddrSub, PATCH_09, 0)
'Patch the Table A entry count to ensure no further
'after' callbacks
Call GlobalFree(.nAddrSub)
'Release the machine code memory
.hWnd = 0
'Mark the sc_aSubData element as available for re-use
.nMsgCntB = 0
'Clear the before table
.nMsgCntA = 0
'Clear the after table
Erase .aMsgTblB
'Erase the before table
Erase .aMsgTblA
'Erase the after table
End With
End Sub

'======================================================================================================
'These z??? routines are exclusively called by the Subclass_??? routines.

'Worker sub for Subclass_AddMsg
Private Sub zAddMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef
nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
Dim nEntry As Long
'Message table entry index
Dim nOff1 As Long
'Machine code buffer offset 1
Dim nOff2 As Long
'Machine code buffer offset 2

If uMsg = ALL_MESSAGES Then
'If all messages
nMsgCnt = ALL_MESSAGES
'Indicates that all messages will callback
Else
'Else a specific message number
Do While nEntry < nMsgCnt
'For each existing entry. NB will skip if nMsgCnt = 0
nEntry = nEntry + 1

If aMsgTbl(nEntry) = 0 Then
'This msg table slot is a deleted entry
aMsgTbl(nEntry) = uMsg
'Re-use this entry
Exit Sub
'Bail
ElseIf aMsgTbl(nEntry) = uMsg Then
'The msg is already in the table!
Exit Sub
'Bail
End If
Loop
'Next entry

nMsgCnt = nMsgCnt + 1
'New slot required, bump the table entry count
ReDim Preserve aMsgTbl(1 To nMsgCnt) As Long
'Bump the size of the table.
aMsgTbl(nMsgCnt) = uMsg
'Store the message number in the table
End If

If When = eMsgWhen.MSG_BEFORE Then
'If before
nOff1 = PATCH_04
'Offset to the Before table
nOff2 = PATCH_05
'Offset to the Before table entry count
Else
'Else after
nOff1 = PATCH_08
'Offset to the After table
nOff2 = PATCH_09
'Offset to the After table entry count
End If

If uMsg <> ALL_MESSAGES Then
Call zPatchVal(nAddr, nOff1, VarPtr(aMsgTbl(1)))
'Address of the msg table, has to be re-patched
because Redim Preserve will move it in memory.
End If
Call zPatchVal(nAddr, nOff2, nMsgCnt)
'Patch the appropriate table entry count
End Sub

'Return the memory address of the passed function in the passed dll
Private Function zAddrFunc(ByVal sDLL As String, ByVal sProc As String)
As Long
zAddrFunc = GetProcAddress(GetModuleHandleA(sDLL), sProc)
Debug.Assert zAddrFunc
'You may wish to comment out this line if you're using
vb5 else the EbMode GetProcAddress will stop here everytime because we
look for vba6.dll first
End Function

'Worker sub for Subclass_DelMsg
Private Sub zDelMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef
nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
Dim nEntry As Long

If uMsg = ALL_MESSAGES Then
'If deleting all messages
nMsgCnt = 0
'Message count is now zero
If When = eMsgWhen.MSG_BEFORE Then
'If before
nEntry = PATCH_05
'Patch the before table message count location
Else
'Else after
nEntry = PATCH_09
'Patch the after table message count location
End If
Call zPatchVal(nAddr, nEntry, 0)
'Patch the table message count to zero
Else
'Else deleteting a specific message
Do While nEntry < nMsgCnt
'For each table entry
nEntry = nEntry + 1
If aMsgTbl(nEntry) = uMsg Then
'If this entry is the message we wish to delete
aMsgTbl(nEntry) = 0
'Mark the table slot as available
Exit Do
'Bail
End If
Loop
'Next entry
End If
End Sub

'Get the sc_aSubData() array index of the passed hWnd
Private Function zIdx(ByVal lng_hWnd As Long, Optional ByVal bAdd As
Boolean = False) As Long
'Get the upper bound of sc_aSubData() - If you get an error here, you're
probably Subclass_AddMsg-ing before Subclass_Start
zIdx = UBound(sc_aSubData)
Do While zIdx >= 0
'Iterate through the existing sc_aSubData() elements
With sc_aSubData(zIdx)
If .hWnd = lng_hWnd Then
'If the hWnd of this element is the one we're looking for
If Not bAdd Then
'If we're searching not adding
Exit Function
'Found
End If
ElseIf .hWnd = 0 Then
'If this an element marked for reuse.
If bAdd Then
'If we're adding
Exit Function
'Re-use it
End If
End If
End With
zIdx = zIdx - 1
'Decrement the index
Loop

If Not bAdd Then
Debug.Assert False
'hWnd not found, programmer error
End If

'If we exit here, we're returning -1, no freed elements were found
End Function

'Patch the machine code buffer at the indicated offset with the relative
address to the target address.
Private Sub zPatchRel(ByVal nAddr As Long, ByVal nOffset As Long, ByVal
nTargetAddr As Long)
Call RtlMoveMemory(ByVal nAddr + nOffset, nTargetAddr - nAddr -
nOffset - 4, 4)
End Sub

'Patch the machine code buffer at the indicated offset with the passed value
Private Sub zPatchVal(ByVal nAddr As Long, ByVal nOffset As Long, ByVal
nValue As Long)
Call RtlMoveMemory(ByVal nAddr + nOffset, nValue, 4)
End Sub

'Worker function for Subclass_InIDE
Private Function zSetTrue(ByRef bValue As Boolean) As Boolean
zSetTrue = True
bValue = True
End Function

Private Function LowWord(ByVal inDWord As Long) As Integer
LowWord = inDWord And &H7FFF&
If (inDWord And &H8000&) Then LowWord = LowWord Or &H8000
End Function

Private Function HighWord(ByVal inDWord As Long) As Integer
HighWord = LowWord(((inDWord And &HFFFF0000) \ &H10000) And &HFFFF&)
End Function

Private Function SWordToUWord(ByVal inWord As Integer) As Long
SWordToUWord = CLng(inWord) And &HFFFF&
End Function