home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 3_2004-2005.ISO / Data / Zips / cMagneticW18233111302004.psc / cMagneticWnd.cls < prev    next >
Text File  |  2004-11-30  |  47KB  |  997 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "cMagneticWnd"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. '========================================================================================
  15. ' Class:         cMagneticWnd.cls
  16. ' Author:        Carles P.V. - ⌐2004 (*)
  17. ' Dependencies:
  18. ' Last revision: 2004.11.30
  19. ' Version:       1.0.8
  20. '----------------------------------------------------------------------------------------
  21. '
  22. ' (*) 1. Code based on original post by Benjamin Wilger:
  23. '
  24. '        Magnetic Forms
  25. '        http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=27489&lngWId=1
  26. '
  27. '     2. Self-Subclassing UserControl/Class template (IDE safe) by Paul Caton:
  28. '
  29. '        Self-subclassing Controls/Forms - NO dependencies (v1.1.0010 2004.10.07)
  30. '        http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=54117&lngWId=1
  31. '----------------------------------------------------------------------------------------
  32. '
  33. ' History:
  34. '
  35. '     1.0.0: First release.
  36. '
  37. '     1.0.1: Use of DeferWindowPos() instead of MoveWindow.
  38. '            Better in case of moving/sizing multiple windows simultaneously.
  39. '            Thanks to jeremyxtz for suggestion.
  40. '
  41. '     1.0.2: Hereditary glueing.
  42. '
  43. '     1.0.3: - Removed 'RemoveWindow()' method.
  44. '              Now, class process WM_DESTROY message and automatically removes window.
  45. '            - Glueing checked in AddWindow().
  46. '
  47. '     1.0.4: Fixed: incorrect checking of 'hereditary glueing'.
  48. '            I hope it's working fine now! Sorry.
  49. '
  50. '     1.0.5: Final update, I hope.
  51. '            Added: hereditary magnetism (magnetism is also working for child windows).
  52. '            I think that WinAmp's *behaviour* is now fully emulated :-)
  53. '
  54. '     1.0.6: Never say final update:
  55. '            Added CheckGlueing() method. Call in case repositioning manually a window
  56. '            and want to enable/check (glue) again, if any. This was only checked for
  57. '            first time when new window added to collection.
  58. '            Thanks to Gandolf_The_GUI for info.
  59. '
  60. '     1.0.7: Returning to manual destroying window (W9x problems)
  61. '
  62. '     1.0.8: - Added processing of WM_SYSCOMMAND and WM_COMMAND:
  63. '              1. When window *state* is changed from 'system menu' or caption buttons.
  64. '              2. When window *state* is changed *externaly*.
  65. '              Thanks to LaVolpe for suggesting solution.
  66. '            - Added checking for maximized windows: At time to extract rectangles,
  67. '              maximized windows will take work area rectangle. This avoids edge
  68. '              offset that causes real window rectangle to go out of screen (work) area.
  69. '----------------------------------------------------------------------------------------
  70.  
  71. Option Explicit
  72.  
  73. '========================================================================================
  74. ' Subclasser declarations
  75. '========================================================================================
  76.  
  77. Private Enum eMsgWhen
  78.     [MSG_AFTER] = 1                                  'Message calls back after the original (previous) WndProc
  79.     [MSG_BEFORE] = 2                                 'Message calls back before the original (previous) WndProc
  80.     [MSG_BEFORE_AND_AFTER] = MSG_AFTER Or MSG_BEFORE 'Message calls back before and after the original (previous) WndProc
  81. End Enum
  82.  
  83. Private Const ALL_MESSAGES     As Long = -1          'All messages added or deleted
  84. Private Const CODE_LEN         As Long = 197         'Length of the machine code in bytes
  85. Private Const GWL_WNDPROC      As Long = -4          'Get/SetWindow offset to the WndProc procedure address
  86. Private Const PATCH_04         As Long = 88          'Table B (before) address patch offset
  87. Private Const PATCH_05         As Long = 93          'Table B (before) entry count patch offset
  88. Private Const PATCH_08         As Long = 132         'Table A (after) address patch offset
  89. Private Const PATCH_09         As Long = 137         'Table A (after) entry count patch offset
  90.  
  91. Private Type tSubData                                'Subclass data type
  92.     hWnd                       As Long               'Handle of the window being subclassed
  93.     nAddrSub                   As Long               'The address of our new WndProc (allocated memory).
  94.     nAddrOrig                  As Long               'The address of the pre-existing WndProc
  95.     nMsgCntA                   As Long               'Msg after table entry count
  96.     nMsgCntB                   As Long               'Msg before table entry count
  97.     aMsgTblA()                 As Long               'Msg after table array
  98.     aMsgTblB()                 As Long               'Msg Before table array
  99. End Type
  100.  
  101. Private sc_aSubData()          As tSubData           'Subclass data array
  102. Private sc_aBuf(1 To CODE_LEN) As Byte               'Code buffer byte array
  103. Private sc_pCWP                As Long               'Address of the CallWindowsProc
  104. Private sc_pEbMode             As Long               'Address of the EbMode IDE break/stop/running function
  105. Private sc_pSWL                As Long               'Address of the SetWindowsLong function
  106.   
  107. Private Declare Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal Length As Long)
  108. Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
  109. Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
  110. Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
  111. Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
  112. Private Declare Function SetWindowLongA Lib "user32" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  113. Private Declare Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long
  114.  
  115. '========================================================================================
  116. ' cMagneticWnd
  117. '========================================================================================
  118.  
  119. '-- API
  120.  
  121. Private Type POINTAPI
  122.     x1 As Long
  123.     y1 As Long
  124. End Type
  125.  
  126. Private Type RECT2
  127.     x1 As Long
  128.     y1 As Long
  129.     x2 As Long
  130.     y2 As Long
  131. End Type
  132.  
  133. Private Const SPI_GETWORKAREA  As Long = 48
  134.  
  135. Private Const WM_SIZING        As Long = &H214
  136. Private Const WM_MOVING        As Long = &H216
  137. Private Const WM_ENTERSIZEMOVE As Long = &H231
  138. Private Const WM_EXITSIZEMOVE  As Long = &H232
  139. Private Const WM_SYSCOMMAND    As Long = &H112
  140. Private Const WM_COMMAND       As Long = &H111
  141.  
  142. Private Const WMSZ_LEFT        As Long = 1
  143. Private Const WMSZ_RIGHT       As Long = 2
  144. Private Const WMSZ_TOP         As Long = 3
  145. Private Const WMSZ_TOPLEFT     As Long = 4
  146. Private Const WMSZ_TOPRIGHT    As Long = 5
  147. Private Const WMSZ_BOTTOM      As Long = 6
  148. Private Const WMSZ_BOTTOMLEFT  As Long = 7
  149. Private Const WMSZ_BOTTOMRIGHT As Long = 8
  150.  
  151. Private Const SC_MINIMIZE      As Long = &HF020&
  152. Private Const SC_RESTORE       As Long = &HF120&
  153.  
  154. Private Const SWP_NOSIZE       As Long = &H1
  155. Private Const SWP_NOZORDER     As Long = &H4
  156. Private Const SWP_NOACTIVATE   As Long = &H10
  157.  
  158. Private Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, lpvParam As Any, ByVal fuWinIni As Long) As Long
  159. Private Declare Function IsWindow Lib "user32" (ByVal hWnd As Long) As Long
  160. Private Declare Function IsZoomed Lib "user32" (ByVal hWnd As Long) As Long
  161. Private Declare Function BeginDeferWindowPos Lib "user32" (ByVal nNumWindows As Long) As Long
  162. Private Declare Function DeferWindowPos Lib "user32" (ByVal hWinPosInfo As Long, ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
  163. Private Declare Function EndDeferWindowPos Lib "user32" (ByVal hWinPosInfo As Long) As Long
  164. Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
  165. Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT2) As Long
  166. Private Declare Function OffsetRect Lib "user32" (lpRect As RECT2, ByVal x As Long, ByVal y As Long) As Long
  167. Private Declare Function UnionRect Lib "user32" (lpDestRect As RECT2, lpSrc1Rect As RECT2, lpSrc2Rect As RECT2) As Long
  168.  
  169. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)
  170.  
  171. '-- Private types:
  172.  
  173. Private Type WND_INFO
  174.     hWnd       As Long
  175.     hWndParent As Long
  176.     Glue       As Boolean
  177. End Type
  178.  
  179. '-- Private constants:
  180.  
  181. Private Const LB_RECT As Long = 16
  182.  
  183. '-- Private variables:
  184.  
  185. Private m_uWndInfo()  As WND_INFO
  186. Private m_lWndCount   As Long
  187. Private m_rcWnd()     As RECT2
  188. Private m_ptAnchor    As POINTAPI
  189. Private m_ptOffset    As POINTAPI
  190. Private m_ptCurr      As POINTAPI
  191. Private m_ptLast      As POINTAPI
  192.  
  193. '-- Property variables:
  194.  
  195. Private m_lSnapWidth As Long
  196.  
  197. '//
  198.  
  199. Private Sub Class_Initialize()
  200.     
  201.     '-- Default snap width
  202.     m_lSnapWidth = 10
  203.     
  204.     '-- Initialize array (handled windows info)
  205.     ReDim m_uWndInfo(0) As WND_INFO
  206.     m_lWndCount = 0
  207. End Sub
  208.  
  209. Private Sub Class_Terminate()
  210.     
  211.     '-- Stop subclassing
  212.     If (m_lWndCount) Then
  213.         Call Subclass_StopAll
  214.     End If
  215. End Sub
  216.  
  217.  
  218.  
  219. '========================================================================================
  220. ' Subclass handler: MUST be the first Public routine in this file.
  221. '                   That includes public properties also.
  222. '========================================================================================
  223.  
  224. 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)
  225. Attribute zSubclass_Proc.VB_MemberFlags = "40"
  226. '
  227. 'Parameters:
  228. '   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.
  229. '   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
  230. '   lReturn  - Set this variable as per your intentions and requirements, see the MSDN documentation for each individual message value.
  231. '   lng_hWnd - The window handle
  232. '   uMsg     - The message number
  233. '   wParam   - Message related data
  234. '   lParam   - Message related data
  235. '
  236. 'Notes:
  237. '   If you really know what you're doing, it's possible to change the values of the
  238. '   hWnd, uMsg, wParam and lParam parameters in a 'before' callback so that different
  239. '   values get passed to the default handler.. and optionaly, the 'after' callback
  240.   
  241.   Dim rcWnd As RECT2
  242.   Dim lc    As Long
  243.   
  244.     Select Case uMsg
  245.         
  246.         '-- Size/Move starting
  247.         Case WM_ENTERSIZEMOVE
  248.             
  249.             '-- Get Desktop area (as first rectangle)
  250.             Call SystemParametersInfo(SPI_GETWORKAREA, 0, m_rcWnd(0), 0)
  251.             
  252.             '-- Get rectangles of all handled windows
  253.             For lc = 1 To m_lWndCount
  254.                 
  255.                 '-- Window maximized ?
  256.                 If (IsZoomed(m_uWndInfo(lc).hWnd)) Then
  257.                     '-- Take work are rectangle
  258.                     Call CopyMemory(m_rcWnd(lc), m_rcWnd(0), LB_RECT)
  259.                   Else
  260.                     '-- Get window rectangle
  261.                     Call GetWindowRect(m_uWndInfo(lc).hWnd, m_rcWnd(lc))
  262.                 End If
  263.                 
  264.                 '-- Is it our current window ?
  265.                 If (m_uWndInfo(lc).hWnd = lng_hWnd) Then
  266.                     '-- Get anchor-offset
  267.                     Call GetCursorPos(m_ptAnchor)
  268.                     Call GetCursorPos(m_ptLast)
  269.                     m_ptOffset.x1 = m_rcWnd(lc).x1 - m_ptLast.x1
  270.                     m_ptOffset.y1 = m_rcWnd(lc).y1 - m_ptLast.y1
  271.                 End If
  272.             Next lc
  273.         
  274.         '-- Sizing
  275.         Case WM_SIZING
  276.             
  277.             Call CopyMemory(rcWnd, ByVal lParam, LB_RECT)
  278.             Call pvSizeRect(lng_hWnd, rcWnd, wParam)
  279.             Call CopyMemory(ByVal lParam, rcWnd, LB_RECT)
  280.             
  281.             bHandled = True
  282.             lReturn = 1
  283.         
  284.         '-- Moving
  285.         Case WM_MOVING
  286.             
  287.             Call CopyMemory(rcWnd, ByVal lParam, LB_RECT)
  288.             Call pvMoveRect(lng_hWnd, rcWnd)
  289.             Call CopyMemory(ByVal lParam, rcWnd, LB_RECT)
  290.             
  291.             bHandled = True
  292.             lReturn = 1
  293.         
  294.         '-- Size/Move finishing
  295.         Case WM_EXITSIZEMOVE
  296.             
  297.             Call pvCheckGlueing
  298.             
  299.         '-- Special case: *menu* call
  300.         Case WM_SYSCOMMAND
  301.             
  302.             If (wParam = SC_MINIMIZE Or wParam = SC_RESTORE) Then
  303.                 Call pvCheckGlueing
  304.             End If
  305.         
  306.         '-- Special case: *control* call
  307.         Case WM_COMMAND
  308.             
  309.             Call pvCheckGlueing
  310.     End Select
  311. End Sub
  312.  
  313.  
  314.  
  315. '========================================================================================
  316. ' Methods
  317. '========================================================================================
  318.  
  319. Public Function AddWindow(ByVal hWnd As Long, Optional ByVal hWndParent As Long = 0) As Boolean
  320.  
  321.   Dim lc As Long
  322.     
  323.     '-- Already in collection ?
  324.     For lc = 1 To m_lWndCount
  325.         If (hWnd = m_uWndInfo(lc).hWnd) Then Exit Function
  326.     Next lc
  327.     
  328.     '-- Validate windows
  329.     If (IsWindow(hWnd) And (IsWindow(hWndParent) Or hWndParent = 0)) Then
  330.         
  331.         '-- Increase count
  332.         m_lWndCount = m_lWndCount + 1
  333.         '-- Resize arrays
  334.         ReDim Preserve m_uWndInfo(0 To m_lWndCount)
  335.         ReDim Preserve m_rcWnd(0 To m_lWndCount)
  336.         
  337.         '-- Add info
  338.         With m_uWndInfo(m_lWndCount)
  339.             .hWnd = hWnd
  340.             .hWndParent = hWndParent
  341.         End With
  342.         
  343.         '-- Check glueing for first time
  344.         Call pvCheckGlueing
  345.         
  346.         '-- Start subclassing
  347.         Call Subclass_Start(hWnd)
  348.         Call Subclass_AddMsg(hWnd, WM_ENTERSIZEMOVE)
  349.         Call Subclass_AddMsg(hWnd, WM_SIZING, [MSG_BEFORE])
  350.         Call Subclass_AddMsg(hWnd, WM_MOVING, [MSG_BEFORE])
  351.         Call Subclass_AddMsg(hWnd, WM_EXITSIZEMOVE)
  352.         Call Subclass_AddMsg(hWnd, WM_SYSCOMMAND)
  353.         Call Subclass_AddMsg(hWnd, WM_COMMAND)
  354.         
  355.         '-- Success
  356.         AddWindow = True
  357.     End If
  358. End Function
  359.  
  360. Public Function RemoveWindow(ByVal hWnd As Long) As Boolean
  361.  
  362.   Dim lc1 As Long
  363.   Dim lc2 As Long
  364.  
  365.     For lc1 = 1 To m_lWndCount
  366.         
  367.         If (hWnd = m_uWndInfo(lc1).hWnd) Then
  368.             
  369.             '-- Move down
  370.             For lc2 = lc1 To m_lWndCount - 1
  371.                 m_uWndInfo(lc2) = m_uWndInfo(lc2 + 1)
  372.             Next lc2
  373.             
  374.             '-- Resize arrays
  375.             m_lWndCount = m_lWndCount - 1
  376.             ReDim Preserve m_uWndInfo(m_lWndCount)
  377.             ReDim Preserve m_rcWnd(m_lWndCount)
  378.             
  379.             '-- Remove parent relationships
  380.             For lc2 = 1 To m_lWndCount
  381.                 If (m_uWndInfo(lc2).hWndParent = hWnd) Then
  382.                     m_uWndInfo(lc2).hWndParent = 0
  383.                 End If
  384.             Next lc2
  385.             
  386.             '-- Stop subclassing / verify connections
  387.             Call Subclass_Stop(hWnd)
  388.             Call pvCheckGlueing
  389.             
  390.             '-- Success
  391.             RemoveWindow = True
  392.             Exit For
  393.         End If
  394.     Next lc1
  395. End Function
  396.  
  397. Public Sub CheckGlueing()
  398.         
  399.     '-- Check ALL windows for possible new *connections*.
  400.     Call pvCheckGlueing
  401. End Sub
  402.  
  403.  
  404.  
  405. '========================================================================================
  406. ' Properties
  407. '========================================================================================
  408.  
  409. Public Property Get SnapWidth() As Long
  410.     SnapWidth = m_lSnapWidth
  411. End Property
  412.  
  413. Public Property Let SnapWidth(ByVal New_SnapWidth As Long)
  414.     m_lSnapWidth = New_SnapWidth
  415. End Property
  416.  
  417.  
  418.  
  419. '========================================================================================
  420. ' Private
  421. '========================================================================================
  422.  
  423. Private Sub pvSizeRect(ByVal hWnd As Long, rcWnd As RECT2, ByVal lfEdge As Long)
  424.     
  425.   Dim rcTmp As RECT2
  426.   Dim lc    As Long
  427.     
  428.     '-- Get a copy
  429.     Call CopyMemory(rcTmp, rcWnd, LB_RECT)
  430.     
  431.     '-- Check all windows
  432.     For lc = 0 To m_lWndCount
  433.         
  434.         With m_rcWnd(lc)
  435.             
  436.             '-- Avoid current window
  437.             If (m_uWndInfo(lc).hWnd <> hWnd) Then
  438.                 
  439.                 '-- X magnetism
  440.                 If (rcWnd.y1 < .y2 + m_lSnapWidth And rcWnd.y2 > .y1 - m_lSnapWidth) Then
  441.                     
  442.                     Select Case lfEdge
  443.                         
  444.                       Case WMSZ_LEFT, WMSZ_TOPLEFT, WMSZ_BOTTOMLEFT
  445.                     
  446.                         Select Case True
  447.                           Case Abs(rcTmp.x1 - .x1) < m_lSnapWidth: rcWnd.x1 = .x1
  448.                           Case Abs(rcTmp.x1 - .x2) < m_lSnapWidth: rcWnd.x1 = .x2
  449.                         End Select
  450.                 
  451.                       Case WMSZ_RIGHT, WMSZ_TOPRIGHT, WMSZ_BOTTOMRIGHT
  452.                         
  453.                         Select Case True
  454.                           Case Abs(rcTmp.x2 - .x1) < m_lSnapWidth: rcWnd.x2 = .x1
  455.                           Case Abs(rcTmp.x2 - .x2) < m_lSnapWidth: rcWnd.x2 = .x2
  456.                         End Select
  457.                     End Select
  458.                 End If
  459.                 
  460.                 '-- Y magnetism
  461.                 If (rcWnd.x1 < .x2 + m_lSnapWidth And rcWnd.x2 > .x1 - m_lSnapWidth) Then
  462.                     
  463.                     Select Case lfEdge
  464.                         
  465.                       Case WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT
  466.                         
  467.                         Select Case True
  468.                           Case Abs(rcTmp.y1 - .y1) < m_lSnapWidth: rcWnd.y1 = .y1
  469.                           Case Abs(rcTmp.y1 - .y2) < m_lSnapWidth: rcWnd.y1 = .y2
  470.                         End Select
  471.                     
  472.                       Case WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT
  473.                         
  474.                         Select Case True
  475.                           Case Abs(rcTmp.y2 - .y1) < m_lSnapWidth: rcWnd.y2 = .y1
  476.                           Case Abs(rcTmp.y2 - .y2) < m_lSnapWidth: rcWnd.y2 = .y2
  477.                         End Select
  478.                     End Select
  479.                 End If
  480.             End If
  481.         End With
  482.     Next lc
  483. End Sub
  484.  
  485. Private Sub pvMoveRect(ByVal hWnd As Long, rcWnd As RECT2)
  486.     
  487.   Dim lc1   As Long
  488.   Dim lc2   As Long
  489.   Dim lWId  As Long
  490.   Dim rcTmp As RECT2
  491.   Dim lOffx As Long
  492.   Dim lOffy As Long
  493.   Dim hDWP  As Long
  494.     
  495.     '== Get current cursor position
  496.     
  497.     Call GetCursorPos(m_ptCurr)
  498.     
  499.     '== Check magnetism for current window
  500.     
  501.     '-- 'Move' current window
  502.     Call OffsetRect(rcWnd, (m_ptCurr.x1 - rcWnd.x1) + m_ptOffset.x1, 0)
  503.     Call OffsetRect(rcWnd, 0, (m_ptCurr.y1 - rcWnd.y1) + m_ptOffset.y1)
  504.     
  505.     '-- Check all windows
  506.     For lc1 = 0 To m_lWndCount
  507.         
  508.         '-- Avoid current window
  509.         If (m_uWndInfo(lc1).hWnd <> hWnd) Then
  510.                 
  511.             '-- Avoid child windows
  512.             If (m_uWndInfo(lc1).Glue = False Or m_uWndInfo(lc1).hWndParent <> hWnd) Then
  513.                     
  514.                 With m_rcWnd(lc1)
  515.                 
  516.                     '-- X magnetism
  517.                     If (rcWnd.y1 < .y2 + m_lSnapWidth And rcWnd.y2 > .y1 - m_lSnapWidth) Then
  518.                     
  519.                         Select Case True
  520.                           Case Abs(rcWnd.x1 - .x1) < m_lSnapWidth: lOffx = .x1 - rcWnd.x1
  521.                           Case Abs(rcWnd.x1 - .x2) < m_lSnapWidth: lOffx = .x2 - rcWnd.x1
  522.                           Case Abs(rcWnd.x2 - .x1) < m_lSnapWidth: lOffx = .x1 - rcWnd.x2
  523.                           Case Abs(rcWnd.x2 - .x2) < m_lSnapWidth: lOffx = .x2 - rcWnd.x2
  524.                         End Select
  525.                     End If
  526.                     
  527.                     '-- Y magnetism
  528.                     If (rcWnd.x1 < .x2 + m_lSnapWidth And rcWnd.x2 > .x1 - m_lSnapWidth) Then
  529.                     
  530.                         Select Case True
  531.                           Case Abs(rcWnd.y1 - .y1) < m_lSnapWidth: lOffy = .y1 - rcWnd.y1
  532.                           Case Abs(rcWnd.y1 - .y2) < m_lSnapWidth: lOffy = .y2 - rcWnd.y1
  533.                           Case Abs(rcWnd.y2 - .y1) < m_lSnapWidth: lOffy = .y1 - rcWnd.y2
  534.                           Case Abs(rcWnd.y2 - .y2) < m_lSnapWidth: lOffy = .y2 - rcWnd.y2
  535.                         End Select
  536.                     End If
  537.                 End With
  538.             End If
  539.         End If
  540.     Next lc1
  541.     
  542.     '== Check magnetism for child windows
  543.     
  544.     For lc1 = 1 To m_lWndCount
  545.         
  546.         '-- Child and connected window ?
  547.         If (m_uWndInfo(lc1).Glue And m_uWndInfo(lc1).hWndParent = hWnd) Then
  548.             
  549.             '-- 'Move' child window
  550.             Call CopyMemory(rcTmp, m_rcWnd(lc1), LB_RECT)
  551.             Call OffsetRect(rcTmp, m_ptCurr.x1 - m_ptAnchor.x1, 0)
  552.             Call OffsetRect(rcTmp, 0, m_ptCurr.y1 - m_ptAnchor.y1)
  553.             
  554.             For lc2 = 0 To m_lWndCount
  555.                                         
  556.                 If (lc1 <> lc2) Then
  557.                     
  558.                     '-- Avoid child windows
  559.                     If (m_uWndInfo(lc2).Glue = False And m_uWndInfo(lc2).hWnd <> hWnd) Then
  560.                     
  561.                         With m_rcWnd(lc2)
  562.                     
  563.                             '-- X magnetism
  564.                             If (rcTmp.y1 < .y2 + m_lSnapWidth And rcTmp.y2 > .y1 - m_lSnapWidth) Then
  565.                                 
  566.                                 Select Case True
  567.                                   Case Abs(rcTmp.x1 - .x1) < m_lSnapWidth: lOffx = .x1 - rcTmp.x1
  568.                                   Case Abs(rcTmp.x1 - .x2) < m_lSnapWidth: lOffx = .x2 - rcTmp.x1
  569.                                   Case Abs(rcTmp.x2 - .x1) < m_lSnapWidth: lOffx = .x1 - rcTmp.x2
  570.                                   Case Abs(rcTmp.x2 - .x2) < m_lSnapWidth: lOffx = .x2 - rcTmp.x2
  571.                                 End Select
  572.                             End If
  573.                             
  574.                             '-- Y magnetism
  575.                             If (rcTmp.x1 < .x2 + m_lSnapWidth And rcTmp.x2 > .x1 - m_lSnapWidth) Then
  576.                             
  577.                                 Select Case True
  578.                                   Case Abs(rcTmp.y1 - .y1) < m_lSnapWidth: lOffy = .y1 - rcTmp.y1
  579.                                   Case Abs(rcTmp.y1 - .y2) < m_lSnapWidth: lOffy = .y2 - rcTmp.y1
  580.                                   Case Abs(rcTmp.y2 - .y1) < m_lSnapWidth: lOffy = .y1 - rcTmp.y2
  581.                                   Case Abs(rcTmp.y2 - .y2) < m_lSnapWidth: lOffy = .y2 - rcTmp.y2
  582.                                 End Select
  583.                             End If
  584.                         End With
  585.                     End If
  586.                 End If
  587.             Next lc2
  588.         End If
  589.     Next lc1
  590.     
  591.     '== Apply offsets
  592.     
  593.     Call OffsetRect(rcWnd, lOffx, lOffy)
  594.     
  595.     '== Glueing (move child windows, if any)
  596.     
  597.     hDWP = BeginDeferWindowPos(1)
  598.     
  599.     For lc1 = 1 To m_lWndCount
  600.         With m_uWndInfo(lc1)
  601.             '-- Is parent our current window ?
  602.             If (.hWndParent = hWnd And .Glue) Then
  603.                 '-- Move 'child' window
  604.                 lWId = pvWndGetInfoIndex(hWnd)
  605.                 With m_rcWnd(lc1)
  606.                     Call DeferWindowPos(hDWP, m_uWndInfo(lc1).hWnd, 0, .x1 - (m_rcWnd(lWId).x1 - rcWnd.x1), .y1 - (m_rcWnd(lWId).y1 - rcWnd.y1), 0, 0, SWP_NOACTIVATE Or SWP_NOSIZE Or SWP_NOZORDER)
  607.                 End With
  608.             End If
  609.         End With
  610.     Next lc1
  611.     
  612.     Call EndDeferWindowPos(hDWP)
  613.     
  614.     '== Store last cursor position
  615.     
  616.     m_ptLast = m_ptCurr
  617. End Sub
  618.  
  619. Private Sub pvCheckGlueing()
  620.     
  621.   Dim lcMain As Long
  622.   Dim lc1    As Long
  623.   Dim lc2    As Long
  624.   Dim lWId   As Long
  625.     
  626.     '-- Get all windows rectangles / Reset glueing
  627.     For lc1 = 1 To m_lWndCount
  628.         
  629.         Call GetWindowRect(m_uWndInfo(lc1).hWnd, m_rcWnd(lc1))
  630.         m_uWndInfo(lc1).Glue = False
  631.     Next lc1
  632.     
  633.     '-- Check direct connection
  634.     For lc1 = 1 To m_lWndCount
  635.         
  636.         If (m_uWndInfo(lc1).hWndParent) Then
  637.         
  638.             '-- Get parent window info index
  639.             lWId = pvWndParentGetInfoIndex(m_uWndInfo(lc1).hWndParent)
  640.             '-- Connected ?
  641.             m_uWndInfo(lc1).Glue = pvWndsConnected(m_rcWnd(lWId), m_rcWnd(lc1))
  642.         End If
  643.     Next lc1
  644.     
  645.     '-- Check indirect connection
  646.     For lcMain = 1 To m_lWndCount
  647.         
  648.         For lc1 = 1 To m_lWndCount
  649.             
  650.             If (m_uWndInfo(lc1).Glue) Then
  651.                 
  652.                 For lc2 = 1 To m_lWndCount
  653.                 
  654.                     If (lc1 <> lc2) Then
  655.                     
  656.                         If (m_uWndInfo(lc1).hWndParent = m_uWndInfo(lc2).hWndParent) Then
  657.                             '-- Connected ?
  658.                             If (m_uWndInfo(lc2).Glue = False) Then
  659.                                 m_uWndInfo(lc2).Glue = pvWndsConnected(m_rcWnd(lc1), m_rcWnd(lc2))
  660.                             End If
  661.                         End If
  662.                     End If
  663.                 Next lc2
  664.             End If
  665.         Next lc1
  666.     Next lcMain
  667. End Sub
  668.  
  669. Private Function pvWndsConnected(rcWnd1 As RECT2, rcWnd2 As RECT2) As Boolean
  670.     
  671.   Dim rcUnion As RECT2
  672.   
  673.     '-- Calc. union rectangle of windows
  674.     Call UnionRect(rcUnion, rcWnd1, rcWnd2)
  675.     
  676.     '-- Bounding glue-rectangle
  677.     If ((rcUnion.x2 - rcUnion.x1) <= (rcWnd1.x2 - rcWnd1.x1) + (rcWnd2.x2 - rcWnd2.x1) And _
  678.         (rcUnion.y2 - rcUnion.y1) <= (rcWnd1.y2 - rcWnd1.y1) + (rcWnd2.y2 - rcWnd2.y1) _
  679.          ) Then
  680.         
  681.         '-- Edge coincidences ?
  682.         If (rcWnd1.x1 = rcWnd2.x1 Or rcWnd1.x1 = rcWnd2.x2 Or _
  683.             rcWnd1.x2 = rcWnd2.x1 Or rcWnd1.x2 = rcWnd2.x2 Or _
  684.             rcWnd1.y1 = rcWnd2.y1 Or rcWnd1.y1 = rcWnd2.y2 Or _
  685.             rcWnd1.y2 = rcWnd2.y1 Or rcWnd1.y2 = rcWnd2.y2 _
  686.             ) Then
  687.             
  688.             pvWndsConnected = True
  689.         End If
  690.     End If
  691. End Function
  692.  
  693. Private Function pvWndGetInfoIndex(ByVal hWnd As Long) As Long
  694.     
  695.   Dim lc As Long
  696.     
  697.     For lc = 1 To m_lWndCount
  698.         If (m_uWndInfo(lc).hWnd = hWnd) Then
  699.             pvWndGetInfoIndex = lc
  700.             Exit For
  701.         End If
  702.     Next lc
  703. End Function
  704.  
  705. Private Function pvWndParentGetInfoIndex(ByVal hWndParent As Long) As Long
  706.     
  707.   Dim lc As Long
  708.     
  709.     For lc = 1 To m_lWndCount
  710.         If (m_uWndInfo(lc).hWnd = hWndParent) Then
  711.             pvWndParentGetInfoIndex = lc
  712.             Exit For
  713.         End If
  714.     Next lc
  715. End Function
  716.  
  717.  
  718.  
  719. '========================================================================================
  720. ' Subclass code - The programmer may call any of the following Subclass_??? routines
  721. '========================================================================================
  722.  
  723. Private Sub Subclass_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  724. 'Add a message to the table of those that will invoke a callback. You should Subclass_Start first and then add the messages
  725. 'Parameters:
  726. '   lng_hWnd - The handle of the window for which the uMsg is to be added to the callback table
  727. '   uMsg     - The message number that will invoke a callback. NB Can also be ALL_MESSAGES, ie all messages will callback
  728. '   When     - Whether the msg is to callback before, after or both with respect to the the default (previous) handler
  729.   
  730.     With sc_aSubData(zIdx(lng_hWnd))
  731.         If (When And eMsgWhen.MSG_BEFORE) Then
  732.             Call zAddMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  733.         End If
  734.         If (When And eMsgWhen.MSG_AFTER) Then
  735.             Call zAddMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  736.         End If
  737.     End With
  738. End Sub
  739.  
  740. Private Sub Subclass_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  741. 'Delete a message from the table of those that will invoke a callback.
  742. 'Parameters:
  743. '   lng_hWnd - The handle of the window for which the uMsg is to be removed from the callback table
  744. '   uMsg     - The message number that will be removed from the callback table. NB Can also be ALL_MESSAGES, ie all messages will callback
  745. '   When     - Whether the msg is to be removed from the before, after or both callback tables
  746.   
  747.     With sc_aSubData(zIdx(lng_hWnd))
  748.         If (When And eMsgWhen.MSG_BEFORE) Then
  749.             Call zDelMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  750.         End If
  751.         If (When And eMsgWhen.MSG_AFTER) Then
  752.             Call zDelMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  753.         End If
  754.     End With
  755. End Sub
  756.  
  757. Private Function Subclass_InIDE() As Boolean
  758. 'Return whether we're running in the IDE.
  759.     Debug.Assert zSetTrue(Subclass_InIDE)
  760. End Function
  761.  
  762. Private Function Subclass_Start(ByVal lng_hWnd As Long) As Long
  763. 'Start subclassing the passed window handle
  764. 'Parameters:
  765. '   lng_hWnd - The handle of the window to be subclassed
  766. 'Returns;
  767. '   The sc_aSubData() index
  768.  
  769.   Dim i                        As Long                       'Loop index
  770.   Dim J                        As Long                       'Loop index
  771.   Dim nSubIdx                  As Long                       'Subclass data index
  772.   Dim sSubCode                 As String                     'Subclass code string
  773.   
  774.   Const GMEM_FIXED             As Long = 0                   'Fixed memory GlobalAlloc flag
  775.   Const PAGE_EXECUTE_READWRITE As Long = &H40&               'Allow memory to execute without violating XP SP2 Data Execution Prevention
  776.   Const PATCH_01               As Long = 18                  'Code buffer offset to the location of the relative address to EbMode
  777.   Const PATCH_02               As Long = 68                  'Address of the previous WndProc
  778.   Const PATCH_03               As Long = 78                  'Relative address of SetWindowsLong
  779.   Const PATCH_06               As Long = 116                 'Address of the previous WndProc
  780.   Const PATCH_07               As Long = 121                 'Relative address of CallWindowProc
  781.   Const PATCH_0A               As Long = 186                 'Address of the owner object
  782.   Const FUNC_CWP               As String = "CallWindowProcA" 'We use CallWindowProc to call the original WndProc
  783.   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
  784.   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
  785.   Const MOD_USER               As String = "user32"          'Location of the SetWindowLongA & CallWindowProc functions
  786.   Const MOD_VBA5               As String = "vba5"            'Location of the EbMode function if running VB5
  787.   Const MOD_VBA6               As String = "vba6"            'Location of the EbMode function if running VB6
  788.  
  789.     'If it's the first time through here..
  790.     If (sc_aBuf(1) = 0) Then
  791.  
  792.         'Build the hex pair subclass string
  793.         sSubCode = "5589E583C4F85731C08945FC8945F8EB0EE80000000083F802742185C07424E830000000837DF800750AE838000000E84D0000005F8B45FCC9C21000E826000000EBF168000000006AFCFF7508E800000000EBE031D24ABF00000000B900000000E82D000000C3FF7514FF7510FF750CFF75086800000000E8000000008945FCC331D2BF00000000B900000000E801000000C3E32F09C978078B450CF2AF75248D4514508D4510508D450C508D4508508D45FC508D45F85052B800000000508B00FF501CC3"
  794.     
  795.         'Convert the string from hex pairs to bytes and store in the machine code buffer
  796.         i = 1
  797.         Do While J < CODE_LEN
  798.             J = J + 1
  799.             sc_aBuf(J) = CByte("&H" & Mid$(sSubCode, i, 2))                       'Convert a pair of hex characters to an eight-bit value and store in the static code buffer array
  800.             i = i + 2
  801.         Loop                                                                      'Next pair of hex characters
  802.     
  803.         'Get API function addresses
  804.         If (Subclass_InIDE) Then                                                  'If we're running in the VB IDE
  805.             sc_aBuf(16) = &H90                                                    'Patch the code buffer to enable the IDE state code
  806.             sc_aBuf(17) = &H90                                                    'Patch the code buffer to enable the IDE state code
  807.             sc_pEbMode = zAddrFunc(MOD_VBA6, FUNC_EBM)                            'Get the address of EbMode in vba6.dll
  808.             If (sc_pEbMode = 0) Then                                              'Found?
  809.                 sc_pEbMode = zAddrFunc(MOD_VBA5, FUNC_EBM)                        'VB5 perhaps
  810.             End If
  811.         End If
  812.     
  813.         Call zPatchVal(VarPtr(sc_aBuf(1)), PATCH_0A, ObjPtr(Me))                  'Patch the address of this object instance into the static machine code buffer
  814.     
  815.         sc_pCWP = zAddrFunc(MOD_USER, FUNC_CWP)                                   'Get the address of the CallWindowsProc function
  816.         sc_pSWL = zAddrFunc(MOD_USER, FUNC_SWL)                                   'Get the address of the SetWindowLongA function
  817.         ReDim sc_aSubData(0 To 0) As tSubData                                     'Create the first sc_aSubData element
  818.     
  819.       Else
  820.         nSubIdx = zIdx(lng_hWnd, True)
  821.         If (nSubIdx = -1) Then                                                    'If an sc_aSubData element isn't being re-cycled
  822.             nSubIdx = UBound(sc_aSubData()) + 1                                   'Calculate the next element
  823.             ReDim Preserve sc_aSubData(0 To nSubIdx) As tSubData                  'Create a new sc_aSubData element
  824.         End If
  825.     
  826.         Subclass_Start = nSubIdx
  827.     End If
  828.  
  829.     With sc_aSubData(nSubIdx)
  830.         
  831.         .nAddrSub = GlobalAlloc(GMEM_FIXED, CODE_LEN)                             'Allocate memory for the machine code WndProc
  832.         Call VirtualProtect(ByVal .nAddrSub, CODE_LEN, PAGE_EXECUTE_READWRITE, i) 'Mark memory as executable
  833.         Call RtlMoveMemory(ByVal .nAddrSub, sc_aBuf(1), CODE_LEN)                 'Copy the machine code from the static byte array to the code array in sc_aSubData
  834.     
  835.         .hWnd = lng_hWnd                                                          'Store the hWnd
  836.         .nAddrOrig = SetWindowLongA(.hWnd, GWL_WNDPROC, .nAddrSub)                'Set our WndProc in place
  837.     
  838.         Call zPatchRel(.nAddrSub, PATCH_01, sc_pEbMode)                           'Patch the relative address to the VBA EbMode api function, whether we need to not.. hardly worth testing
  839.         Call zPatchVal(.nAddrSub, PATCH_02, .nAddrOrig)                           'Original WndProc address for CallWindowProc, call the original WndProc
  840.         Call zPatchRel(.nAddrSub, PATCH_03, sc_pSWL)                              'Patch the relative address of the SetWindowLongA api function
  841.         Call zPatchVal(.nAddrSub, PATCH_06, .nAddrOrig)                           'Original WndProc address for SetWindowLongA, unsubclass on IDE stop
  842.         Call zPatchRel(.nAddrSub, PATCH_07, sc_pCWP)                              'Patch the relative address of the CallWindowProc api function
  843.     End With
  844. End Function
  845.  
  846. Private Sub Subclass_StopAll()
  847. 'Stop all subclassing
  848.   
  849.   Dim i As Long
  850.   
  851.     i = UBound(sc_aSubData())                                                     'Get the upper bound of the subclass data array
  852.     Do While i >= 0                                                               'Iterate through each element
  853.         With sc_aSubData(i)
  854.             If (.hWnd <> 0) Then                                                  'If not previously Subclass_Stop'd
  855.                 Call Subclass_Stop(.hWnd)                                         'Subclass_Stop
  856.             End If
  857.         End With
  858.     
  859.         i = i - 1                                                                 'Next element
  860.     Loop
  861. End Sub
  862.  
  863. Private Sub Subclass_Stop(ByVal lng_hWnd As Long)
  864. 'Stop subclassing the passed window handle
  865. 'Parameters:
  866. '   lng_hWnd - The handle of the window to stop being subclassed
  867.   
  868.     With sc_aSubData(zIdx(lng_hWnd))
  869.         Call SetWindowLongA(.hWnd, GWL_WNDPROC, .nAddrOrig)                       'Restore the original WndProc
  870.         Call zPatchVal(.nAddrSub, PATCH_05, 0)                                    'Patch the Table B entry count to ensure no further 'before' callbacks
  871.         Call zPatchVal(.nAddrSub, PATCH_09, 0)                                    'Patch the Table A entry count to ensure no further 'after' callbacks
  872.         Call GlobalFree(.nAddrSub)                                                'Release the machine code memory
  873.         .hWnd = 0                                                                 'Mark the sc_aSubData element as available for re-use
  874.         .nMsgCntB = 0                                                             'Clear the before table
  875.         .nMsgCntA = 0                                                             'Clear the after table
  876.         Erase .aMsgTblB                                                           'Erase the before table
  877.         Erase .aMsgTblA                                                           'Erase the after table
  878.     End With
  879. End Sub
  880.  
  881. '----------------------------------------------------------------------------------------
  882. 'These z??? routines are exclusively called by the Subclass_??? routines.
  883. '----------------------------------------------------------------------------------------
  884.  
  885. Private Sub zAddMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  886. 'Worker sub for Subclass_AddMsg
  887.   
  888.   Dim nEntry  As Long                                                             'Message table entry index
  889.   Dim nOff1   As Long                                                             'Machine code buffer offset 1
  890.   Dim nOff2   As Long                                                             'Machine code buffer offset 2
  891.   
  892.     If (uMsg = ALL_MESSAGES) Then                                                 'If all messages
  893.         nMsgCnt = ALL_MESSAGES                                                    'Indicates that all messages will callback
  894.       Else                                                                        'Else a specific message number
  895.         Do While nEntry < nMsgCnt                                                 'For each existing entry. NB will skip if nMsgCnt = 0
  896.             nEntry = nEntry + 1
  897.         
  898.             If (aMsgTbl(nEntry) = 0) Then                                         'This msg table slot is a deleted entry
  899.                 aMsgTbl(nEntry) = uMsg                                            'Re-use this entry
  900.                 Exit Sub                                                          'Bail
  901.             ElseIf (aMsgTbl(nEntry) = uMsg) Then                                  'The msg is already in the table!
  902.                 Exit Sub                                                          'Bail
  903.             End If
  904.         Loop                                                                      'Next entry
  905.  
  906.         nMsgCnt = nMsgCnt + 1                                                     'New slot required, bump the table entry count
  907.         ReDim Preserve aMsgTbl(1 To nMsgCnt) As Long                              'Bump the size of the table.
  908.         aMsgTbl(nMsgCnt) = uMsg                                                   'Store the message number in the table
  909.     End If
  910.  
  911.     If (When = eMsgWhen.MSG_BEFORE) Then                                          'If before
  912.         nOff1 = PATCH_04                                                          'Offset to the Before table
  913.         nOff2 = PATCH_05                                                          'Offset to the Before table entry count
  914.       Else                                                                        'Else after
  915.         nOff1 = PATCH_08                                                          'Offset to the After table
  916.         nOff2 = PATCH_09                                                          'Offset to the After table entry count
  917.     End If
  918.  
  919.     If (uMsg <> ALL_MESSAGES) Then
  920.         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.
  921.     End If
  922.     Call zPatchVal(nAddr, nOff2, nMsgCnt)                                         'Patch the appropriate table entry count
  923. End Sub
  924.  
  925. Private Function zAddrFunc(ByVal sDLL As String, ByVal sProc As String) As Long
  926. 'Return the memory address of the passed function in the passed dll
  927.     zAddrFunc = GetProcAddress(GetModuleHandleA(sDLL), sProc)
  928.     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
  929. End Function
  930.  
  931. Private Sub zDelMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  932. 'Worker sub for Subclass_DelMsg
  933.   
  934.   Dim nEntry As Long
  935.   
  936.     If (uMsg = ALL_MESSAGES) Then                                                 'If deleting all messages
  937.         nMsgCnt = 0                                                               'Message count is now zero
  938.         If When = eMsgWhen.MSG_BEFORE Then                                        'If before
  939.             nEntry = PATCH_05                                                     'Patch the before table message count location
  940.           Else                                                                    'Else after
  941.             nEntry = PATCH_09                                                     'Patch the after table message count location
  942.         End If
  943.         Call zPatchVal(nAddr, nEntry, 0)                                          'Patch the table message count to zero
  944.       Else                                                                        'Else deleteting a specific message
  945.         Do While nEntry < nMsgCnt                                                 'For each table entry
  946.             nEntry = nEntry + 1
  947.             If (aMsgTbl(nEntry) = uMsg) Then                                      'If this entry is the message we wish to delete
  948.                 aMsgTbl(nEntry) = 0                                               'Mark the table slot as available
  949.                 Exit Do                                                           'Bail
  950.             End If
  951.         Loop                                                                      'Next entry
  952.     End If
  953. End Sub
  954.  
  955. Private Function zIdx(ByVal lng_hWnd As Long, Optional ByVal bAdd As Boolean = False) As Long
  956. 'Get the sc_aSubData() array index of the passed hWnd
  957. 'Get the upper bound of sc_aSubData() - If you get an error here, you're probably Subclass_AddMsg-ing before Subclass_Start
  958.   
  959.     zIdx = UBound(sc_aSubData)
  960.     Do While zIdx >= 0                                                            'Iterate through the existing sc_aSubData() elements
  961.         With sc_aSubData(zIdx)
  962.             If (.hWnd = lng_hWnd) Then                                            'If the hWnd of this element is the one we're looking for
  963.                 If (Not bAdd) Then                                                'If we're searching not adding
  964.                     Exit Function                                                 'Found
  965.                 End If
  966.             ElseIf (.hWnd = 0) Then                                               'If this an element marked for reuse.
  967.                 If (bAdd) Then                                                    'If we're adding
  968.                     Exit Function                                                 'Re-use it
  969.                 End If
  970.             End If
  971.         End With
  972.         zIdx = zIdx - 1                                                           'Decrement the index
  973.     Loop
  974.   
  975.     If (Not bAdd) Then
  976.         Debug.Assert False                                                        'hWnd not found, programmer error
  977.     End If
  978.  
  979. 'If we exit here, we're returning -1, no freed elements were found
  980. End Function
  981.  
  982. Private Sub zPatchRel(ByVal nAddr As Long, ByVal nOffset As Long, ByVal nTargetAddr As Long)
  983. 'Patch the machine code buffer at the indicated offset with the relative address to the target address.
  984.     Call RtlMoveMemory(ByVal nAddr + nOffset, nTargetAddr - nAddr - nOffset - 4, 4)
  985. End Sub
  986.  
  987. Private Sub zPatchVal(ByVal nAddr As Long, ByVal nOffset As Long, ByVal nValue As Long)
  988. 'Patch the machine code buffer at the indicated offset with the passed value
  989.     Call RtlMoveMemory(ByVal nAddr + nOffset, nValue, 4)
  990. End Sub
  991.  
  992. Private Function zSetTrue(ByRef bValue As Boolean) As Boolean
  993. 'Worker function for Subclass_InIDE
  994.     zSetTrue = True
  995.     bValue = True
  996. End Function
  997.