home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 1_2002.ISO / Data / Zips / CODE_UPLOAD1773.psc / subclass.bas < prev   
Encoding:
BASIC Source File  |  1999-11-07  |  11.1 KB  |  278 lines

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