home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 1_2002.ISO / Data / Zips / Subforms i22057722001.psc / files for demo / subformCTL / MOD / subclass.bas
Encoding:
BASIC Source File  |  2001-06-10  |  12.6 KB  |  306 lines

  1. Attribute VB_Name = "MSubclass"
  2. Option Explicit
  3. ' ===========================================================================
  4. ' Copyright « 1998 Steve McMahon (steve@dogma.demon.co.uk)
  5. ' Visit vbAccelerator - free, advanced source code for VB programmers.
  6. '     http://vbaccelerator.com
  7. ' ---------------------------------------------------------------------------
  8. ' FREE SOURCE CODE! - ENJOY.
  9. ' - Please report bugs to the author for incorporation into future releases
  10. ' - Don't sell this code.
  11. ' ===========================================================================
  12. '
  13. 'Note:
  14. 'You are free to use and modify any of the code on the site,
  15. 'but not to distribute modified versions of my downloads,
  16. 'ActiveX DLLs or OCXs with the same filename and/or ProgIds.
  17. 'If you have made changes which you think are beneficial,
  18. 'or have bug reports, then you can email me
  19. '(steve@vbaccelerator.com) and I will do my utmost to get the a new version
  20. 'on the site.
  21. 'You can freely distribute the zips available from this site to other ones,
  22. 'but you must distribute them in their original state and particularly keep
  23. 'the information and disclaimer text file with the zip (if it has one!).
  24. 'Notification would be greatly appreciated!
  25. 'You can freely distribute any compiled code on the site,
  26. 'or any products you build using the code.
  27. 'If you wish to distribute the source code files by any other means
  28. '(i.e. if you want to include it on a CD or any other software media)
  29. 'then the EXPRESS PERMISSION of the author is REQUIRED
  30. ' ===========================================================================
  31.  
  32. ' declares:
  33. Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
  34. Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
  35. Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
  36. Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
  37. Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  38. Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  39. Private 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
  40. Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
  41. Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
  42. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
  43.     lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
  44. Private Const GWL_WNDPROC = (-4)
  45.  
  46. ' SubTimer is independent of VBCore, so it hard codes error handling
  47.  
  48. Public Enum EErrorWindowProc
  49.     eeBaseWindowProc = 13080 ' WindowProc
  50.     eeCantSubclass           ' Can't subclass window
  51.     eeAlreadyAttached        ' Message already handled by another class
  52.     eeInvalidWindow          ' Invalid window
  53.     eeNoExternalWindow       ' Can't modify external window
  54. End Enum
  55.  
  56. Private m_iCurrentMessage As Long
  57. Private m_iProcOld As Long
  58.  
  59. Public Property Get CurrentMessage() As Long
  60.    CurrentMessage = m_iCurrentMessage
  61. End Property
  62.  
  63. Private Sub ErrRaise(e As Long)
  64.     Dim sText As String, sSource As String
  65.     If e > 1000 Then
  66.         sSource = App.EXEName & ".WindowProc"
  67.         Select Case e
  68.         Case eeCantSubclass
  69.             sText = "Can't subclass window"
  70.         Case eeAlreadyAttached
  71.             sText = "Message already handled by another class"
  72.         Case eeInvalidWindow
  73.             sText = "Invalid window"
  74.         Case eeNoExternalWindow
  75.             sText = "Can't modify external window"
  76.         End Select
  77.         Err.Raise e Or vbObjectError, sSource, sText
  78.     Else
  79.         ' Raise standard Visual Basic error
  80.         Err.Raise e, sSource
  81.     End If
  82. End Sub
  83.  
  84. Sub AttachMessage(iwp As ISubclass, ByVal hwnd As Long, _
  85.                   ByVal iMsg As Long)
  86.     Dim procOld As Long, f As Long, c As Long
  87.     Dim iC As Long, bFail As Boolean
  88.     
  89.     ' Validate window
  90.     If IsWindow(hwnd) = False Then ErrRaise eeInvalidWindow
  91.     If IsWindowLocal(hwnd) = False Then ErrRaise eeNoExternalWindow
  92.  
  93.     ' Get the message count
  94.     c = GetProp(hwnd, "C" & hwnd)
  95.     If c = 0 Then
  96.         ' Subclass window by installing window procecure
  97.         procOld = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
  98.         If procOld = 0 Then ErrRaise eeCantSubclass
  99.         ' Associate old procedure with handle
  100.         f = SetProp(hwnd, hwnd, procOld)
  101.         Debug.Assert f <> 0
  102.         ' Count this message
  103.         c = 1
  104.         f = SetProp(hwnd, "C" & hwnd, c)
  105.     Else
  106.         ' Count this message
  107.         c = c + 1
  108.         f = SetProp(hwnd, "C" & hwnd, c)
  109.     End If
  110.     Debug.Assert f <> 0
  111.     
  112.     ' SPM - in this version I am allowing more than one class to
  113.     ' make a subclass to the same hWnd and Msg.  Why am I doing
  114.     ' this?  Well say the class in question is a control, and it
  115.     ' wants to subclass its container.  In this case, we want
  116.     ' all instances of the control on the form to receive the
  117.     ' form notification message.
  118.     c = GetProp(hwnd, hwnd & "#" & iMsg & "C")
  119.     If (c > 0) Then
  120.         For iC = 1 To c
  121.             If (GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC) = ObjPtr(iwp)) Then
  122.                 ErrRaise eeAlreadyAttached
  123.                 bFail = True
  124.                 Exit For
  125.             End If
  126.         Next iC
  127.     End If
  128.                 
  129.     If Not (bFail) Then
  130.         c = c + 1
  131.         ' Increase count for hWnd/Msg:
  132.         f = SetProp(hwnd, hwnd & "#" & iMsg & "C", c)
  133.         Debug.Assert f <> 0
  134.         
  135.         ' Associate object with message at the count:
  136.         f = SetProp(hwnd, hwnd & "#" & iMsg & "#" & c, ObjPtr(iwp))
  137.         Debug.Assert f <> 0
  138.     End If
  139. End Sub
  140.  
  141. Sub DetachMessage(iwp As ISubclass, ByVal hwnd As Long, _
  142.                   ByVal iMsg As Long)
  143.     Dim procOld As Long, f As Long, c As Long
  144.     Dim iC As Long, iP As Long, lPtr As Long
  145.     
  146.     ' Get the message count
  147.     c = GetProp(hwnd, "C" & hwnd)
  148.     If c = 1 Then
  149.         ' This is the last message, so unsubclass
  150.         procOld = GetProp(hwnd, hwnd)
  151.         Debug.Assert procOld <> 0
  152.         ' Unsubclass by reassigning old window procedure
  153.         Call SetWindowLong(hwnd, GWL_WNDPROC, procOld)
  154.         ' Remove unneeded handle (oldProc)
  155.         RemoveProp hwnd, hwnd
  156.         ' Remove unneeded count
  157.         RemoveProp hwnd, "C" & hwnd
  158.     Else
  159.         ' Uncount this message
  160.         c = GetProp(hwnd, "C" & hwnd)
  161.         c = c - 1
  162.         f = SetProp(hwnd, "C" & hwnd, c)
  163.     End If
  164.     
  165.     ' SPM - in this version I am allowing more than one class to
  166.     ' make a subclass to the same hWnd and Msg.  Why am I doing
  167.     ' this?  Well say the class in question is a control, and it
  168.     ' wants to subclass its container.  In this case, we want
  169.     ' all instances of the control on the form to receive the
  170.     ' form notification message.
  171.     
  172.     ' How many instances attached to this hwnd/msg?
  173.     c = GetProp(hwnd, hwnd & "#" & iMsg & "C")
  174.     If (c > 0) Then
  175.         ' Find this iwp object amongst the items:
  176.         For iC = 1 To c
  177.             If (GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC) = ObjPtr(iwp)) Then
  178.                 iP = iC
  179.                 Exit For
  180.             End If
  181.         Next iC
  182.     
  183.         If (iP <> 0) Then
  184.              ' Remove this item:
  185.              For iC = iP + 1 To c
  186.                 lPtr = GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC)
  187.                 SetProp hwnd, hwnd & "#" & iMsg & "#" & (iC - 1), lPtr
  188.              Next iC
  189.         End If
  190.         ' Decrement the count
  191.         RemoveProp hwnd, hwnd & "#" & iMsg & "#" & c
  192.         c = c - 1
  193.         SetProp hwnd, hwnd & "#" & iMsg & "C", c
  194.     
  195.     End If
  196. End Sub
  197.  
  198. Private Function WindowProc(ByVal hwnd As Long, ByVal iMsg As Long, _
  199.                             ByVal wParam As Long, ByVal lParam As Long) _
  200.                             As Long
  201.     Dim procOld As Long, pSubclass As Long, f As Long
  202.     Dim iwp As ISubclass, iwpT As ISubclass
  203.     Dim iPC As Long, iP As Long, bNoProcess As Long
  204.     Dim bCalled As Boolean
  205.     
  206.     ' Get the old procedure from the window
  207.     procOld = GetProp(hwnd, hwnd)
  208.     Debug.Assert procOld <> 0
  209.     
  210.     ' SPM - in this version I am allowing more than one class to
  211.     ' make a subclass to the same hWnd and Msg.  Why am I doing
  212.     ' this?  Well say the class in question is a control, and it
  213.     ' wants to subclass its container.  In this case, we want
  214.     ' all instances of the control on the form to receive the
  215.     ' form notification message.
  216.     
  217.     ' Get the number of instances for this msg/hwnd:
  218.     bCalled = False
  219.     iPC = GetProp(hwnd, hwnd & "#" & iMsg & "C")
  220.     If (iPC > 0) Then
  221.         ' For each instance attached to this msg/hwnd, call the subclass:
  222.         For iP = 1 To iPC
  223.             bNoProcess = False
  224.             ' Get the object pointer from the message
  225.             pSubclass = GetProp(hwnd, hwnd & "#" & iMsg & "#" & iP)
  226.             If pSubclass = 0 Then
  227.                 ' This message not handled, so pass on to old procedure
  228.                 WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
  229.                                             wParam, ByVal lParam)
  230.                 bNoProcess = True
  231.             End If
  232.             
  233.             If Not (bNoProcess) Then
  234.                 ' Turn the pointer into an illegal, uncounted interface
  235.                 CopyMemory iwpT, pSubclass, 4
  236.                 ' Do NOT hit the End button here! You will crash!
  237.                 ' Assign to legal reference
  238.                 Set iwp = iwpT
  239.                 ' Still do NOT hit the End button here! You will still crash!
  240.                 ' Destroy the illegal reference
  241.                 CopyMemory iwpT, 0&, 4
  242.                 ' OK, hit the End button if you must--you'll probably still crash,
  243.                 ' but it will be because of the subclass, not the uncounted reference
  244.                 
  245.                 ' Store the current message, so the client can check it:
  246.                 m_iCurrentMessage = iMsg
  247.                 m_iProcOld = procOld
  248.                 
  249.                 ' Use the interface to call back to the class
  250.                 With iwp
  251.                     ' Preprocess (only check this the first time around):
  252.                     If (iP = 1) Then
  253.                         If .MsgResponse = emrPreprocess Then
  254.                            If Not (bCalled) Then
  255.                               WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
  256.                                                         wParam, ByVal lParam)
  257.                               bCalled = True
  258.                            End If
  259.                         End If
  260.                     End If
  261.                     ' Consume (this message is always passed to all control
  262.                     ' instances regardless of whether any single one of them
  263.                     ' requests to consume it):
  264.                     WindowProc = .WindowProc(hwnd, iMsg, wParam, ByVal lParam)
  265.                     ' PostProcess (only check this the last time around):
  266.                     If (iP = iPC) Then
  267.                     'xxxxxxxx
  268.                         If .MsgResponse = emrPostProcess Then
  269.                            If Not (bCalled) Then
  270.                               WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
  271.                                                         wParam, ByVal lParam)
  272.                               bCalled = True
  273.                            End If
  274.                         End If
  275.                     End If
  276.                 End With
  277.             End If
  278.         Next iP
  279.     Else
  280.         ' This message not handled, so pass on to old procedure
  281.         WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
  282.                                     wParam, ByVal lParam)
  283.     End If
  284. End Function
  285. Public Function CallOldWindowProc( _
  286.       ByVal hwnd As Long, _
  287.       ByVal iMsg As Long, _
  288.       ByVal wParam As Long, _
  289.       ByVal lParam As Long _
  290.    ) As Long
  291.    CallOldWindowProc = CallWindowProc(m_iProcOld, hwnd, iMsg, wParam, lParam)
  292.  
  293. End Function
  294.  
  295. ' Cheat! Cut and paste from MWinTool rather than reusing
  296. ' file because reusing file would cause many unneeded dependencies
  297. Function IsWindowLocal(ByVal hwnd As Long) As Boolean
  298.     Dim idWnd As Long
  299.     Call GetWindowThreadProcessId(hwnd, idWnd)
  300.     IsWindowLocal = (idWnd = GetCurrentProcessId())
  301. End Function
  302. '
  303.  
  304.  
  305.  
  306.