home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 4_2005-2006.ISO / data / Zips / XP_Themed_189767662005.psc / Duncan_XPButton.ctl < prev   
Text File  |  2005-06-06  |  55KB  |  1,234 lines

  1. VERSION 5.00
  2. Begin VB.UserControl Duncan_XPButton 
  3.    AutoRedraw      =   -1  'True
  4.    BackColor       =   &H008080FF&
  5.    ClientHeight    =   2835
  6.    ClientLeft      =   0
  7.    ClientTop       =   0
  8.    ClientWidth     =   4770
  9.    DefaultCancel   =   -1  'True
  10.    BeginProperty Font 
  11.       Name            =   "Tahoma"
  12.       Size            =   8.25
  13.       Charset         =   0
  14.       Weight          =   400
  15.       Underline       =   0   'False
  16.       Italic          =   0   'False
  17.       Strikethrough   =   0   'False
  18.    EndProperty
  19.    MaskColor       =   &H00FF00FF&
  20.    ScaleHeight     =   189
  21.    ScaleMode       =   3  'Pixel
  22.    ScaleWidth      =   318
  23.    ToolboxBitmap   =   "Duncan_XPButton.ctx":0000
  24.    Begin VB.CommandButton Command1 
  25.       Height          =   615
  26.       Left            =   0
  27.       Style           =   1  'Graphical
  28.       TabIndex        =   0
  29.       Top             =   0
  30.       Width           =   1215
  31.    End
  32. End
  33. Attribute VB_Name = "Duncan_XPButton"
  34. Attribute VB_GlobalNameSpace = False
  35. Attribute VB_Creatable = True
  36. Attribute VB_PredeclaredId = False
  37. Attribute VB_Exposed = False
  38. Option Explicit
  39. 'What?
  40. 'A button that when Themes are enabled draws in XP style
  41. 'but when themes are removed reverts to an old 98 style button.
  42.  
  43. 'Why?
  44. 'So that I can have a propper XP style button that I can use and
  45. 'see while developing.
  46. 'Because it doesnt require a manifest.
  47. 'Because I am making a full collection of base controls all
  48. 'like this and this is the latest.
  49.  
  50. 'How?
  51. 'Uses a normal button for unthemed behaviour.
  52. 'Uses XP theme drawing to paint a button if possible.
  53.  
  54. 'Behaviour
  55. 'It should behave just like a normal button.
  56. 'Please report any bugs.
  57. 'Picture works best if it is assigned an Icon rather than a gif or bmp
  58.  
  59. 'Who?
  60. 'Thanks to Paul (programming god) Catton for his amazing subclassing work that has made this project possible
  61. 'http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=54117&lngWId=1
  62.  
  63. 'When?
  64. 'Last Updated : June 2005
  65.  
  66. 'to do:
  67.  
  68.  
  69. '======================================================================================================================================================
  70. 'MY DECLARES FOR THIS CONTROL
  71. '======================================================================================================================================================
  72. 'drawing the picture
  73. Private Const DST_ICON = &H3
  74. Private Const DST_BITMAP = &H4
  75. Private Const DSS_DISABLED = &H20
  76. Private Declare Function DrawState Lib "user32" Alias "DrawStateA" _
  77.        (ByVal hdc As Long, _
  78.         ByVal hBrush As Long, _
  79.         ByVal lpDrawStateProc As Long, _
  80.         ByVal lParam As Long, _
  81.         ByVal wParam As Long, _
  82.         ByVal X As Long, _
  83.         ByVal Y As Long, _
  84.         ByVal cX As Long, _
  85.         ByVal cY As Long, _
  86.         ByVal fuFlags As Long) As Long
  87. 'creating button shape
  88. Private Declare Function CreateRoundRectRgn Lib "gdi32" (ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal X3 As Long, ByVal Y3 As Long) As Long
  89. Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long
  90. Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
  91. 'drawing the themed button
  92. Private Declare Function DrawFocusRect Lib "user32" (ByVal hdc As Long, lpRect As RECT) As Long
  93. Private Const FOCUSPADDING As Long = 4
  94. Private Declare Function OpenThemeData Lib "uxtheme.dll" _
  95.    (ByVal hwnd As Long, ByVal pszClassList As Long) As Long
  96. Private Declare Function CloseThemeData Lib "uxtheme.dll" _
  97.    (ByVal hTheme As Long) As Long
  98. Private Declare Function DrawThemeParentBackground Lib "uxtheme.dll" _
  99.    (ByVal hwnd As Long, ByVal hdc As Long, prc As RECT) As Long
  100. Private Declare Function DrawThemeBackground Lib "uxtheme.dll" _
  101.    (ByVal hTheme As Long, ByVal lHdc As Long, _
  102.     ByVal iPartId As Long, ByVal iStateId As Long, _
  103.     pRect As RECT, pClipRect As RECT) As Long
  104. Private Declare Function GetThemeBackgroundContentRect Lib "uxtheme.dll" _
  105.    (ByVal hTheme As Long, ByVal hdc As Long, _
  106.     ByVal iPartId As Long, ByVal iStateId As Long, _
  107.     pBoundingRect As RECT, pContentRect As RECT) As Long
  108. Private Declare Function DrawThemeText Lib "uxtheme.dll" _
  109.    (ByVal hTheme As Long, ByVal hdc As Long, ByVal iPartId As Long, _
  110.     ByVal iStateId As Long, ByVal pszText As Long, _
  111.     ByVal iCharCount As Long, ByVal dwTextFlag As Long, _
  112.     ByVal dwTextFlags2 As Long, pRect As RECT) As Long
  113. Public Enum eButtonStyle
  114.     Button = 0
  115.     Toolbar = 1
  116. End Enum
  117. Private Enum eButtonState
  118.     Normal = 1
  119.     Hot = 2
  120.     Pressed = 3
  121.     Disabled = 4
  122.     Defaulted = 5
  123. End Enum
  124. Private Type RECT
  125.    Left As Long
  126.    Top As Long
  127.    Right As Long
  128.    Bottom As Long
  129. End Type
  130. Private Enum DrawTextFlags
  131.     DT_TOP = &H0
  132.     DT_LEFT = &H0
  133.     DT_CENTER = &H1
  134.     DT_RIGHT = &H2
  135.     DT_VCENTER = &H4
  136.     DT_BOTTOM = &H8
  137.     DT_WORDBREAK = &H10
  138.     DT_SINGLELINE = &H20
  139.     DT_EXPANDTABS = &H40
  140.     DT_TABSTOP = &H80
  141.     DT_NOCLIP = &H100
  142.     DT_EXTERNALLEADING = &H200
  143.     DT_CALCRECT = &H400
  144.     DT_NOPREFIX = &H800
  145.     DT_INTERNAL = &H1000
  146.     DT_EDITCONTROL = &H2000
  147.     DT_PATH_ELLIPSIS = &H4000
  148.     DT_END_ELLIPSIS = &H8000
  149.     DT_MODIFYSTRING = &H10000
  150.     DT_RTLREADING = &H20000
  151.     DT_WORD_ELLIPSIS = &H40000
  152.     DT_NOFULLWIDTHCHARBREAK = &H80000
  153.     DT_HIDEPREFIX = &H100000
  154.     DT_PREFIXONLY = &H200000
  155. End Enum
  156.  
  157. Public Enum eAlignment
  158.     topleft = DT_TOP Or DT_LEFT Or DT_SINGLELINE  'left top
  159.     topcenter = DT_TOP Or DT_CENTER Or DT_SINGLELINE 'top center
  160.     topright = DT_TOP Or DT_RIGHT Or DT_SINGLELINE 'top right
  161.     middleleft = DT_VCENTER Or DT_LEFT Or DT_SINGLELINE 'middle left
  162.     middlecenter = DT_VCENTER Or DT_CENTER Or DT_SINGLELINE 'middle center
  163.     middleright = DT_VCENTER Or DT_RIGHT Or DT_SINGLELINE 'middle right
  164.     bottomleft = DT_BOTTOM Or DT_LEFT Or DT_SINGLELINE 'bottom left
  165.     bottomcenter = DT_BOTTOM Or DT_CENTER Or DT_SINGLELINE 'bottom center
  166.     bottomright = DT_BOTTOM Or DT_RIGHT Or DT_SINGLELINE 'bottom right
  167. End Enum
  168.  
  169. 'mouse position
  170. Private Type POINTAPI
  171.     X As Long
  172.     Y As Long
  173. End Type
  174. Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
  175. Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
  176. Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
  177. Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
  178.  
  179. 'INTERNAL USE
  180. Private m_Hot As Boolean            'is the mouse over the control?
  181. Private m_MouseDown As Boolean      'is the mouse down?
  182. Private m_StateIdButton As eButtonState 'the current draw state of the button
  183. Private m_Active As Boolean         'are we the active app? ie we dont process mouse over events unless we have focus
  184. Private m_Caption As String            'text for the button
  185. Private m_CaptionAlignment As eAlignment   'alignment for text
  186. Private m_Enabled As Boolean        'Enabled?
  187. Private m_UseThemes As Boolean  'Which button to use
  188. Private m_HasFocus As Boolean       'Does button have focus?
  189. Private m_DisplayAsDefault As Boolean
  190. Private m_FocusRect As Boolean      'Do we draw the focus rectangle?
  191. Private m_ButtonStyle As eButtonStyle   'Which theme to use when drawing
  192. Private m_Picture As StdPicture
  193. Private m_PictureAlignment As eAlignment    'alignment for picture
  194. Private m_PicturePadding As Long    'How many pixels to indent
  195. Private m_CaptionPadding As Long    'How many pixels to indent
  196. 'we dont want to send 2 click events if the user has mouse down
  197. 'over the control and presses enter so we set which input type
  198. 'has priority and only the latest event will be processed
  199. Private m_EBP As eEventBeingProcessed
  200. Private Enum eEventBeingProcessed
  201.     None = 0
  202.     Mouse = 1
  203.     Keyboard = 2
  204. End Enum
  205. 'Public events
  206. Public Event Click()
  207. '======================================================================================================================================================
  208. 'SUBCLASSING DECLARES
  209. '======================================================================================================================================================
  210. Private Enum eMsgWhen
  211.   MSG_AFTER = 1                                                                         'Message calls back after the original (previous) WndProc
  212.   MSG_BEFORE = 2                                                                        'Message calls back before the original (previous) WndProc
  213.   MSG_BEFORE_AND_AFTER = MSG_AFTER Or MSG_BEFORE                                        'Message calls back before and after the original (previous) WndProc
  214. End Enum
  215. Private Type tSubData                                                                   'Subclass data type
  216.   hwnd                               As Long                                            'Handle of the window being subclassed
  217.   nAddrSub                           As Long                                            'The address of our new WndProc (allocated memory).
  218.   nAddrOrig                          As Long                                            'The address of the pre-existing WndProc
  219.   nMsgCntA                           As Long                                            'Msg after table entry count
  220.   nMsgCntB                           As Long                                            'Msg before table entry count
  221.   aMsgTblA()                         As Long                                            'Msg after table array
  222.   aMsgTblB()                         As Long                                            'Msg Before table array
  223. End Type
  224. Private sc_aSubData()                As tSubData                                        'Subclass data array
  225. Private Const ALL_MESSAGES           As Long = -1                                       'All messages added or deleted
  226. Private Const GMEM_FIXED             As Long = 0                                        'Fixed memory GlobalAlloc flag
  227. Private Const GWL_WNDPROC            As Long = -4                                       'Get/SetWindow offset to the WndProc procedure address
  228. Private Const PATCH_04               As Long = 88                                       'Table B (before) address patch offset
  229. Private Const PATCH_05               As Long = 93                                       'Table B (before) entry count patch offset
  230. Private Const PATCH_08               As Long = 132                                      'Table A (after) address patch offset
  231. Private Const PATCH_09               As Long = 137                                      'Table A (after) entry count patch offset
  232. Private Declare Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal Length As Long)
  233. Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
  234. Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
  235. Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
  236. Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
  237. Private Declare Function SetWindowLongA Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  238. 'Window Messages
  239. Private Const WM_ACTIVATE = &H6
  240. Private Const WM_NCACTIVATE As Long = &H86
  241. Private Const WM_MOUSELEAVE As Long = &H2A3
  242. Private Const WM_SYSCOLORCHANGE As Long = &H15
  243. Private Const WM_THEMECHANGED As Long = &H31A
  244.  
  245. '//Mouse tracking declares
  246. Private Declare Function TrackMouseEvent Lib "user32" (lpEventTrack As TRACKMOUSEEVENT_STRUCT) As Long
  247. Private Enum TRACKMOUSEEVENT_FLAGS
  248.     TME_HOVER = &H1&
  249.     TME_LEAVE = &H2&
  250.     TME_QUERY = &H40000000
  251.     TME_CANCEL = &H80000000
  252. End Enum
  253. Private Type TRACKMOUSEEVENT_STRUCT
  254.     cbSize                              As Long
  255.     dwFlags                             As TRACKMOUSEEVENT_FLAGS
  256.     hwndTrack                           As Long
  257.     dwHoverTime                         As Long
  258. End Type
  259.  
  260. 'Subclass handler
  261. 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)
  262. Attribute zSubclass_Proc.VB_MemberFlags = "40"
  263. 'THIS MUST BE THE FIRST PUBLIC ROUTINE IN THIS FILE.
  264. 'That includes public properties also
  265. 'Parameters:
  266.   '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.
  267.   '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
  268.   'lReturn  - Set this variable as per your intentions and requirements, see the MSDN documentation for each individual message value.
  269.   'hWnd     - The window handle
  270.   'uMsg     - The message number
  271.   'wParam   - Message related data
  272.   'lParam   - Message related data
  273.  
  274.     'Debug.Print WMbyName(uMsg) & " " & wParam
  275.     Select Case uMsg
  276.         Case WM_THEMECHANGED, WM_SYSCOLORCHANGE
  277.             InitialiseThemes
  278.             Refresh
  279.         Case WM_MOUSELEAVE
  280.             'called when mouse leaves the control
  281.             If ProcessingMessages Then
  282.                 m_Hot = False
  283.                 m_MouseDown = False
  284.                 If Not m_EBP = Keyboard Then    'if there isnt a keyboard event stored, eg they are holding down the spacebar, then refresh
  285.                     RefreshButton
  286.                 End If
  287.             End If
  288.         Case WM_ACTIVATE, WM_NCACTIVATE
  289.             If wParam Then  '----------------------------------- Activated
  290.                 'Debug.Print "activated " & wParam & " " & lParam & " " & Now
  291.                 m_Active = True
  292.                 m_Hot = CheckForHot
  293.             Else            '----------------------------------- Deactivated
  294.                 'Debug.Print "deactivated " & wParam & " " & lParam & " " & Now
  295.                 m_Active = False
  296.                 m_Hot = False
  297.             End If
  298.             Refresh
  299.     End Select
  300. End Sub
  301.  
  302. '======================================================================================================================================================
  303. 'Functions
  304. '======================================================================================================================================================
  305.  
  306. Public Property Get Caption() As String
  307.     Caption = m_Caption
  308. End Property
  309. Public Property Let Caption(sVal As String)
  310.     If sVal <> m_Caption Then
  311.         m_Caption = sVal
  312.         UserControl.AccessKeys = GetAccessKey '---------- Set AccessKey property if desired
  313.         Command1.Caption = m_Caption
  314.         PropertyChanged "Caption"
  315.         Refresh
  316.     End If
  317. End Property
  318. Public Property Get CaptionAlignment() As eAlignment
  319.     CaptionAlignment = m_CaptionAlignment
  320. End Property
  321. Public Property Let CaptionAlignment(eVal As eAlignment)
  322.     If eVal <> m_CaptionAlignment Then
  323.         m_CaptionAlignment = eVal
  324.         PropertyChanged "CaptionAlignment"
  325.         Refresh
  326.     End If
  327. End Property
  328.  
  329. Public Property Get Enabled() As Boolean
  330.     Enabled = m_Enabled
  331. End Property
  332. Public Property Let Enabled(bVal As Boolean)
  333.     If bVal <> m_Enabled Then
  334.         m_Enabled = bVal
  335.         PropertyChanged "Enabled"
  336.         Command1.Enabled = m_Enabled
  337.         UserControl.Enabled = m_Enabled
  338.         RefreshButton
  339.     End If
  340. End Property
  341. Public Property Get FocusRect() As Boolean
  342.     FocusRect = m_FocusRect
  343. End Property
  344. Public Property Let FocusRect(bVal As Boolean)
  345.     If bVal <> m_FocusRect Then
  346.         m_FocusRect = bVal
  347.         PropertyChanged "FocusRect"
  348.         RefreshButton
  349.     End If
  350. End Property
  351. Public Property Get ButtonStyle() As eButtonStyle
  352.     ButtonStyle = m_ButtonStyle
  353. End Property
  354. Public Property Let ButtonStyle(eVal As eButtonStyle)
  355.     If eVal <> m_ButtonStyle Then
  356.         m_ButtonStyle = eVal
  357.         PropertyChanged "ButtonStyle"
  358.         RefreshButton
  359.     End If
  360. End Property
  361. Public Property Get hwnd() As Long
  362.     If m_UseThemes Then
  363.         hwnd = UserControl.hwnd
  364.     Else
  365.         hwnd = Command1.hwnd
  366.     End If
  367. End Property
  368.  
  369. Public Property Get Picture() As StdPicture
  370.     Set Picture = m_Picture
  371. End Property
  372. Public Property Set Picture(ByVal oVal As StdPicture)
  373.     Set m_Picture = oVal
  374.     Set Command1.Picture = oVal
  375.     Refresh
  376.     PropertyChanged "Picture"
  377. End Property
  378. Public Property Get PictureAlignment() As eAlignment
  379.     PictureAlignment = m_PictureAlignment
  380. End Property
  381. Public Property Let PictureAlignment(eVal As eAlignment)
  382.     If eVal <> m_PictureAlignment Then
  383.         m_PictureAlignment = eVal
  384.         PropertyChanged "PictureAlignment"
  385.         Refresh
  386.     End If
  387. End Property
  388.  
  389. Public Property Get PicturePadding() As Long
  390.     PicturePadding = m_PicturePadding
  391. End Property
  392. Public Property Let PicturePadding(lVal As Long)
  393.     If lVal <> m_PicturePadding Then
  394.         m_PicturePadding = lVal
  395.         PropertyChanged "PicturePadding"
  396.         Refresh
  397.     End If
  398. End Property
  399. Public Property Get CaptionPadding() As Long
  400.     CaptionPadding = m_CaptionPadding
  401. End Property
  402. Public Property Let CaptionPadding(lVal As Long)
  403.     If lVal <> m_CaptionPadding Then
  404.         m_CaptionPadding = lVal
  405.         PropertyChanged "CaptionPadding"
  406.         Refresh
  407.     End If
  408. End Property
  409. Public Property Get Font() As Font
  410.     Set Font = UserControl.Font
  411. End Property
  412. Public Property Set Font(ByVal fVal As Font)
  413.     Set UserControl.Font = fVal
  414.     Set Command1.Font = fVal
  415.     PropertyChanged "Font"
  416.     Refresh
  417. End Property
  418.  
  419. '----------------
  420. 'PUBLIC FUNCTIONS
  421. '----------------
  422.  
  423. Public Sub Refresh()
  424.     m_StateIdButton = -1    'set to invalid value for force update
  425.     RefreshButton
  426. End Sub
  427.  
  428. Private Sub RefreshButton()
  429.     If m_UseThemes Then
  430.         Command1.Visible = False
  431.         DrawThemeButton
  432.     Else
  433.         UserControl.Cls
  434.         Command1.Move 0, 0, UserControl.ScaleWidth, UserControl.ScaleHeight
  435.         Command1.Visible = True
  436.         Command1.Refresh
  437.     End If
  438. End Sub
  439.  
  440.  
  441. Private Sub DrawThemeButton()
  442.     Dim hTheme As Long
  443.     Dim lPartId As Long
  444.     Dim lStateId As eButtonState
  445.     Dim tR As RECT
  446.     Dim tTextR As RECT
  447.     Dim tIconR As RECT
  448.     Dim tImlR As RECT
  449.     Dim tFocusR As RECT
  450.     Dim retval As Long
  451.     Dim L As Long
  452.     Dim T As Long
  453.     
  454.     tR.Left = 0
  455.     tR.Top = 0
  456.     tR.Right = UserControl.ScaleWidth
  457.     tR.Bottom = UserControl.ScaleHeight
  458.    
  459.     lPartId = 1
  460.     
  461.     If m_Enabled Then
  462.         If m_Hot Then
  463.             'the mouse is over us
  464.             'is it pressed or not
  465.             If m_MouseDown Then
  466.                 lStateId = Pressed
  467.             Else
  468.                 lStateId = Hot
  469.             End If
  470.         Else
  471.             'draw normal
  472.             If m_DisplayAsDefault And m_ButtonStyle = Button Then
  473.                 lStateId = Defaulted
  474.             Else
  475.                 lStateId = Normal
  476.             End If
  477.         End If
  478.     Else
  479.         lStateId = Disabled
  480.     End If
  481.     
  482.     If Not UserControl.Ambient.UserMode Then
  483.         'we are in design mode so draw something visible (toolbar isnt in normal mode)
  484.         If Enabled Then
  485.             If m_ButtonStyle = Toolbar Then
  486.                 lStateId = Hot
  487.             End If
  488.         End If
  489.     End If
  490.     
  491.     'if state has changed
  492.     'or we are in design mode
  493.     If ((lStateId <> m_StateIdButton) Or (Not UserControl.Ambient.UserMode)) Then
  494.         'state has changed - redraw the control
  495.         m_StateIdButton = lStateId
  496.         If m_ButtonStyle = Button Then
  497.             hTheme = OpenThemeData(UserControl.hwnd, StrPtr("BUTTON"))
  498.         Else
  499.             hTheme = OpenThemeData(UserControl.hwnd, StrPtr("TOOLBAR"))
  500.         End If
  501.         
  502.         If hTheme <> 0 Then
  503.             UserControl.Cls
  504.             'retval = DrawThemeParentBackground( _
  505.                 UserControl.hWnd, _
  506.                 UserControl.hdc, _
  507.                 tR)
  508.     
  509.             retval = DrawThemeBackground(hTheme, _
  510.                 UserControl.hdc, _
  511.                 lPartId, _
  512.                 lStateId, _
  513.                 tR, tR)
  514.          
  515.          
  516.             If Len(m_Caption) > 0 Then
  517.                 retval = GetThemeBackgroundContentRect( _
  518.                     hTheme, _
  519.                     UserControl.hdc, _
  520.                     lPartId, _
  521.                     lStateId, _
  522.                     tR, _
  523.                     tTextR)
  524.                     
  525.                  Select Case m_CaptionAlignment
  526.                  Case bottomleft
  527.                     tTextR.Bottom = tTextR.Bottom - m_CaptionPadding
  528.                     tTextR.Left = tTextR.Left + m_CaptionPadding
  529.                  Case bottomcenter
  530.                     tTextR.Bottom = tTextR.Bottom - m_CaptionPadding
  531.                  Case bottomright
  532.                     tTextR.Bottom = tTextR.Bottom - m_CaptionPadding
  533.                     tTextR.Right = tTextR.Right - m_CaptionPadding
  534.                  Case middleleft
  535.                     tTextR.Left = tTextR.Left + m_CaptionPadding
  536.                  Case middleright
  537.                     tTextR.Right = tTextR.Right - m_CaptionPadding
  538.                  Case topcenter
  539.                     tTextR.Top = tTextR.Top + m_CaptionPadding
  540.                  Case topleft
  541.                     tTextR.Left = tTextR.Left + m_CaptionPadding
  542.                     tTextR.Top = tTextR.Top + m_CaptionPadding
  543.                  Case topright
  544.                     tTextR.Top = tTextR.Top + m_CaptionPadding
  545.                     tTextR.Right = tTextR.Right - m_CaptionPadding
  546.                  End Select
  547.                  
  548.                 retval = DrawThemeText( _
  549.                    hTheme, _
  550.                    UserControl.hdc, _
  551.                    lPartId, _
  552.                    lStateId, _
  553.                    StrPtr(m_Caption), _
  554.                    -1, _
  555.                    m_CaptionAlignment, _
  556.                    0, _
  557.                    tTextR)
  558.             End If
  559.             
  560.             If FocusRect And m_Enabled Then
  561.                 If m_HasFocus Then
  562.                     Dim lSpacer As Long
  563.                     lSpacer = 4
  564.                     tFocusR.Top = tR.Top + FOCUSPADDING
  565.                     tFocusR.Left = tR.Left + FOCUSPADDING
  566.                     tFocusR.Right = tR.Right - FOCUSPADDING
  567.                     tFocusR.Bottom = tR.Bottom - FOCUSPADDING
  568.                     If tFocusR.Bottom > tFocusR.Top And tFocusR.Right > tFocusR.Left Then
  569.                         DrawFocusRect UserControl.hdc, tFocusR
  570.                     End If
  571.                 End If
  572.             End If
  573.             
  574.             DrawPicture
  575.  
  576.             CloseThemeData hTheme
  577.         End If
  578.     End If
  579.     
  580. End Sub
  581.  
  582. Private Sub Command1_Click()
  583.     If m_Enabled And m_Active Then
  584.         RaiseEvent Click
  585.     End If
  586. End Sub
  587.  
  588. '-----------------
  589. 'PRIVATE FUNCTIONS
  590. '-----------------
  591. Private Function CheckForButton() As Boolean
  592.     'lets you know if the left mouse button is down
  593.     Dim retval As Long
  594.     retval = GetKeyState(vbKeyLButton)  'returns a negative value while the button is being depressed
  595.     If retval < False Then
  596.         CheckForButton = True
  597.     End If
  598. End Function
  599.  
  600. Private Function CheckForHot() As Boolean
  601.     'lets you know if the pointer is over the control
  602.     Dim P As POINTAPI
  603.     Dim H As Long
  604.     'get position of cursor
  605.     GetCursorPos P
  606.     'Get the window under that position
  607.     H = WindowFromPoint(P.X, P.Y)
  608.     If H = UserControl.hwnd Then
  609.         CheckForHot = True
  610.     End If
  611.     
  612. End Function
  613.  
  614. Private Sub SetWindowRegion()
  615.     'Trims the usercontrol into a rounded shape
  616.     Dim RGN As Long
  617.     RGN = CreateRoundRectRgn(0, 0, UserControl.ScaleWidth + 1, UserControl.ScaleHeight + 1, 2, 2)
  618.     'Apply the region
  619.     SetWindowRgn UserControl.hwnd, RGN, True
  620.     'clean up
  621.     DeleteObject RGN
  622. End Sub
  623.  
  624. Private Function ActivityIsInControl(X As Single, Y As Single) As Boolean
  625.     'called from the mouse move and mouse up functions
  626.     'and lets you know if the mouse is over the control
  627.     'it wont be for example if the user pressed down and then
  628.     'dragged outside of the control
  629.     With UserControl
  630.         If X >= 0 And X <= .ScaleWidth And Y >= 0 And Y <= .ScaleHeight Then
  631.             ActivityIsInControl = True
  632.         End If
  633.     End With
  634. End Function
  635.  
  636. Private Function GetAccessKey() As String
  637.     'Extracts and returns the AccessKey
  638.     'Function sourced from LiTe Templer of PSC
  639.     Dim lPos    As Long
  640.     Dim lLen    As Long
  641.     Dim lSearch As Long
  642.     Dim sChr    As String
  643.     lLen = Len(m_Caption)
  644.     If lLen = 0 Then Exit Function
  645.     lPos = 1
  646.     Do While lPos + 1 < lLen
  647.         lSearch = InStr(lPos, m_Caption, "&")
  648.         If lSearch = 0 Or lSearch = lLen Then Exit Do
  649.         sChr = LCase$(Mid$(m_Caption, lSearch + 1, 1))
  650.         If sChr = "&" Then
  651.             lPos = lSearch + 2
  652.         Else
  653.             GetAccessKey = sChr
  654.             Exit Do
  655.         End If
  656.     Loop
  657. End Function
  658.  
  659. Private Function InitialiseThemes() As Boolean
  660.     'tests to see if themes are available
  661.     'if it can then we can draw the XP buttons
  662.     'if it cant then we use the old 98 style buttons
  663.     On Error GoTo Whoops
  664.     Dim hTheme As Long
  665.  
  666.     'opening and closing theme
  667.     hTheme = OpenThemeData(UserControl.hwnd, StrPtr("BUTTON"))
  668.     If hTheme <> 0 Then
  669.         m_UseThemes = True
  670.     Else
  671.         m_UseThemes = False
  672.     End If
  673.     CloseThemeData hTheme
  674.     
  675. Whoops:
  676.     'Not theme enabled
  677. End Function
  678.  
  679. Private Sub TrackMouseLeave()
  680.     'Starts tracking the mouse
  681.     'When the mouse leaves the control the WM_MOUSELEAVE message will be sent
  682.     'Doesnt work for transparent windows :(
  683.     On Error GoTo Errs
  684.     Dim tme As TRACKMOUSEEVENT_STRUCT
  685.     With tme
  686.         .cbSize = Len(tme)
  687.         .dwFlags = TME_LEAVE
  688.         .hwndTrack = UserControl.hwnd
  689.     End With
  690.     Call TrackMouseEvent(tme) '---- Track the mouse leaving the indicated window via subclassing
  691. Errs:
  692. End Sub
  693.  
  694. Private Function ProcessingMessages() As Boolean
  695.     'are we procesing messages from the UserControl?
  696.     If m_Enabled And m_Active And m_UseThemes Then
  697.         ProcessingMessages = True
  698.     End If
  699. End Function
  700.  
  701. Private Sub DrawPicture()
  702.     Dim picWidth As Long
  703.     Dim picHeight As Long
  704.     Dim Top As Single
  705.     Dim Left As Single
  706.     Dim middle As Single    'vertical
  707.     Dim Bottom As Single
  708.     Dim Right As Single
  709.     Dim center As Single    'horizontal
  710.     Dim drawTop As Single
  711.     Dim drawLeft As Single
  712.     Dim drawFlags As Long
  713.     
  714.     Const Padding As Long = 4
  715.     
  716.     If Not m_Picture Is Nothing Then
  717.         'get picture dimensions
  718.         picWidth = UserControl.ScaleX(m_Picture.Width, vbHimetric, vbPixels)
  719.         picHeight = UserControl.ScaleY(m_Picture.Height, vbHimetric, vbPixels)
  720.         'calc positioning
  721.         Top = Padding
  722.         Left = Padding
  723.         middle = (UserControl.ScaleHeight - picHeight) / 2
  724.         Bottom = UserControl.ScaleHeight - (picHeight + Padding)
  725.         Right = UserControl.ScaleWidth - (picWidth + Padding)
  726.         center = (UserControl.ScaleWidth - picWidth) / 2
  727.         
  728.         'assign positioning
  729.         Select Case m_PictureAlignment
  730.         Case topleft
  731.             drawTop = Top + m_PicturePadding
  732.             drawLeft = Left + m_PicturePadding
  733.         Case topcenter
  734.             drawTop = Top + m_PicturePadding
  735.             drawLeft = center
  736.         Case topright
  737.             drawTop = Top + m_PicturePadding
  738.             drawLeft = Right - m_PicturePadding
  739.         Case middleright
  740.             drawTop = middle
  741.             drawLeft = Right + m_PicturePadding
  742.         Case middleleft
  743.             drawTop = middle
  744.             drawLeft = Left + m_PicturePadding
  745.         Case bottomleft
  746.             drawTop = Bottom - m_PicturePadding
  747.             drawLeft = Left + m_PicturePadding
  748.         Case bottomcenter
  749.             drawTop = Bottom - m_PicturePadding
  750.             drawLeft = center
  751.         Case bottomright
  752.             drawTop = Bottom - m_PicturePadding
  753.             drawLeft = Right - m_PicturePadding
  754.         Case Else
  755.             'middlecenter and unknown
  756.             drawTop = middle
  757.             drawLeft = center
  758.         End Select
  759.         
  760.         If m_Picture.Type = vbPicTypeIcon Then
  761.             drawFlags = DST_ICON
  762.         Else
  763.             'presume its a bitmap (vbPicTypeBitmap)
  764.             drawFlags = DST_BITMAP
  765.         End If
  766.         If Not m_Enabled Then drawFlags = drawFlags Or DSS_DISABLED
  767.         DrawState UserControl.hdc, 0, 0, m_Picture.Handle, 0, drawLeft, drawTop, picWidth, picHeight, drawFlags
  768.     End If
  769. End Sub
  770.  
  771.  
  772. '------------
  773. 'USER CONTROL
  774. '------------
  775. Private Sub UserControl_AccessKeyPress(KeyAscii As Integer)
  776.     If ProcessingMessages Then
  777.         RaiseEvent Click
  778.     End If
  779. End Sub
  780.  
  781. Private Sub UserControl_AmbientChanged(PropertyName As String)
  782.     m_DisplayAsDefault = Ambient.DisplayAsDefault
  783.     Command1.Default = Ambient.DisplayAsDefault
  784.     Command1.Cancel = UserControl.Extender.Cancel
  785.     RefreshButton
  786. End Sub
  787.  
  788. Private Sub UserControl_EnterFocus()
  789.     m_HasFocus = True
  790.     Refresh
  791. End Sub
  792.  
  793. Private Sub UserControl_ExitFocus()
  794.     m_HasFocus = False
  795.     m_MouseDown = False
  796.     m_EBP = None
  797.     Refresh
  798. End Sub
  799.  
  800. Private Sub UserControl_InitProperties()
  801.     UserControl.AutoRedraw = True
  802.     InitialiseThemes
  803.     Set UserControl.Font = UserControl.Ambient.Font
  804.     UserControl.BackColor = UserControl.Parent.BackColor
  805.     m_Caption = Extender.Name
  806.     m_CaptionAlignment = middlecenter
  807.     m_PictureAlignment = middleleft
  808.     m_FocusRect = False
  809.     m_Enabled = True
  810.     Refresh
  811. End Sub
  812.  
  813. Private Sub UserControl_KeyDown(KeyCode As Integer, Shift As Integer)
  814.     If ProcessingMessages Then
  815.         Select Case KeyCode
  816.         Case 32, 13 'space,enter
  817.             m_EBP = Keyboard
  818.             m_MouseDown = True
  819.             m_Hot = True
  820.             Refresh
  821.         Case 37, 38             'Left Arrow and Up keys
  822.             SendKeys "+{TAB}"
  823.         Case 39, 40             'Right Arrow and Down keys
  824.             SendKeys "{TAB}"
  825.         Case Else
  826.             'Debug.Print KeyCode & " down"
  827.         End Select
  828.     End If
  829. End Sub
  830.  
  831. Private Sub UserControl_KeyUp(KeyCode As Integer, Shift As Integer)
  832.     If ProcessingMessages Then
  833.         Select Case KeyCode
  834.         Case 32, 13 'space,enter
  835.             If m_EBP = Keyboard Then
  836.                 RaiseEvent Click
  837.                 m_EBP = None
  838.                 m_MouseDown = CheckForButton
  839.                 m_Hot = CheckForHot
  840.                 Refresh
  841.             End If
  842.         Case Else
  843.             'Debug.Print KeyCode & " up"
  844.         End Select
  845.     End If
  846. End Sub
  847.  
  848. Private Sub UserControl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  849.     If ProcessingMessages Then
  850.         If Button = 1 Then
  851.             m_MouseDown = True
  852.             m_EBP = Mouse
  853.             RefreshButton
  854.        End If
  855.     End If
  856. End Sub
  857.  
  858. Private Sub UserControl_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
  859.     If ProcessingMessages Then
  860.         If Button = 1 Then
  861.             If m_EBP = Mouse Then
  862.                 If ActivityIsInControl(X, Y) Then
  863.                     RaiseEvent Click
  864.                     m_EBP = None
  865.                 End If
  866.             End If
  867.         End If
  868.         m_MouseDown = 0
  869.         RefreshButton
  870.     End If
  871. End Sub
  872.  
  873. Private Sub UserControl_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  874.     If ProcessingMessages Then
  875.         If ActivityIsInControl(X, Y) Then
  876.             Call TrackMouseLeave
  877.             m_Hot = True
  878.         Else
  879.             m_Hot = False
  880.         End If
  881.         If Not m_EBP = Keyboard Then
  882.             RefreshButton
  883.         End If
  884.     End If
  885. End Sub
  886.  
  887. Private Sub UserControl_Resize()
  888.     If m_UseThemes Then
  889.         SetWindowRegion
  890.     End If
  891.     RefreshButton
  892. End Sub
  893.  
  894. Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
  895.     On Error Resume Next
  896.     InitialiseThemes
  897.     If Ambient.UserMode Then
  898.         Call Subclass_Start(UserControl.hwnd)
  899.         Call Subclass_AddMsg(UserControl.hwnd, WM_MOUSELEAVE, MSG_AFTER)
  900.  
  901.         Call Subclass_Start(UserControl.Parent.hwnd)
  902.         'Call Subclass_AddMsg(UserControl.Parent.hwnd, ALL_MESSAGES, MSG_BEFORE)
  903.         If UserControl.Parent.MDIChild Then
  904.             Call Subclass_AddMsg(UserControl.Parent.hwnd, WM_NCACTIVATE, MSG_AFTER)
  905.         Else
  906.             Call Subclass_AddMsg(UserControl.Parent.hwnd, WM_ACTIVATE, MSG_AFTER)
  907.         End If
  908.         Call Subclass_AddMsg(UserControl.Parent.hwnd, WM_SYSCOLORCHANGE, MSG_AFTER)
  909.         Call Subclass_AddMsg(UserControl.Parent.hwnd, WM_THEMECHANGED, MSG_AFTER)
  910.     End If
  911.     
  912.     With PropBag
  913.         Set Font = .ReadProperty("Font", Ambient.Font)
  914.         FocusRect = .ReadProperty("FocusRect", True)
  915.         ButtonStyle = .ReadProperty("ButtonStyle", 0)
  916.         Caption = .ReadProperty("Caption", "")
  917.         CaptionAlignment = .ReadProperty("CaptionAlignment", middlecenter)
  918.         m_Enabled = .ReadProperty("Enabled", True)
  919.         Command1.Enabled = .ReadProperty("Enabled", True)
  920.         Set Picture = .ReadProperty("Picture", Nothing)
  921.         PictureAlignment = .ReadProperty("PictureAlignment", middleleft)
  922.         PicturePadding = .ReadProperty("PicturePadding", 0)
  923.         CaptionPadding = .ReadProperty("CaptionPadding", 0)
  924.     End With
  925.     
  926.     UserControl.BackColor = UserControl.Parent.BackColor
  927.     Command1.Default = UserControl.Ambient.DisplayAsDefault
  928.     Command1.Cancel = UserControl.Extender.Cancel
  929. End Sub
  930.  
  931. Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
  932.     With PropBag
  933.         .WriteProperty "Font", UserControl.Font, Ambient.Font
  934.         .WriteProperty "FocusRect", FocusRect, True
  935.         .WriteProperty "ButtonStyle", ButtonStyle, 0
  936.         .WriteProperty "Caption", Caption, ""
  937.         .WriteProperty "CaptionAlignment", CaptionAlignment, middlecenter
  938.         .WriteProperty "Enabled", Enabled, True
  939.         .WriteProperty "Picture", Picture, Nothing
  940.         .WriteProperty "PictureAlignment", PictureAlignment, middleleft
  941.         .WriteProperty "PicturePadding", PicturePadding, 0
  942.         .WriteProperty "CaptionPadding", CaptionPadding, 0
  943.     End With
  944. End Sub
  945.  
  946. Private Sub UserControl_Terminate()
  947.     On Error GoTo Errs
  948.     If Ambient.UserMode Then Call Subclass_StopAll
  949.     Debug.Print "UC terminated"
  950. Errs:
  951. End Sub
  952.  
  953.  
  954.  
  955.  
  956. '========================================================================================
  957. 'Subclass routines below here - The programmer may call any of the following Subclass_??? routines
  958. '======================================================================================================================================================
  959. 'Add a message to the table of those that will invoke a callback. You should Subclass_Start first and then add the messages
  960. Private Sub Subclass_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  961. On Error GoTo Errs
  962. 'Parameters:
  963.   'lng_hWnd  - The handle of the window for which the uMsg is to be added to the callback table
  964.   'uMsg      - The message number that will invoke a callback. NB Can also be ALL_MESSAGES, ie all messages will callback
  965.   'When      - Whether the msg is to callback before, after or both with respect to the the default (previous) handler
  966.   With sc_aSubData(zIdx(lng_hWnd))
  967.     If When And eMsgWhen.MSG_BEFORE Then
  968.       Call zAddMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  969.     End If
  970.     If When And eMsgWhen.MSG_AFTER Then
  971.       Call zAddMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  972.     End If
  973.   End With
  974. Errs:
  975. End Sub
  976.  
  977. 'Delete a message from the table of those that will invoke a callback.
  978. Private Sub Subclass_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  979. On Error GoTo Errs
  980.  
  981. 'Parameters:
  982.   'lng_hWnd  - The handle of the window for which the uMsg is to be removed from the callback table
  983.   'uMsg      - The message number that will be removed from the callback table. NB Can also be ALL_MESSAGES, ie all messages will callback
  984.   'When      - Whether the msg is to be removed from the before, after or both callback tables
  985.   With sc_aSubData(zIdx(lng_hWnd))
  986.     If When And eMsgWhen.MSG_BEFORE Then
  987.       Call zDelMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  988.     End If
  989.     If When And eMsgWhen.MSG_AFTER Then
  990.       Call zDelMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  991.     End If
  992.   End With
  993. Errs:
  994. End Sub
  995.  
  996. 'Return whether we're running in the IDE.
  997. Private Function Subclass_InIDE() As Boolean
  998.   Debug.Assert zSetTrue(Subclass_InIDE)
  999. End Function
  1000.  
  1001. 'Start subclassing the passed window handle
  1002. Private Function Subclass_Start(ByVal lng_hWnd As Long) As Long
  1003. On Error GoTo Errs
  1004. 'Parameters:
  1005.   'lng_hWnd  - The handle of the window to be subclassed
  1006. 'Returns;
  1007.   'The sc_aSubData() index
  1008.   Const CODE_LEN              As Long = 200                                             'Length of the machine code in bytes
  1009.   Const FUNC_CWP              As String = "CallWindowProcA"                             'We use CallWindowProc to call the original WndProc
  1010.   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
  1011.   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
  1012.   Const MOD_USER              As String = "user32"                                      'Location of the SetWindowLongA & CallWindowProc functions
  1013.   Const MOD_VBA5              As String = "vba5"                                        'Location of the EbMode function if running VB5
  1014.   Const MOD_VBA6              As String = "vba6"                                        'Location of the EbMode function if running VB6
  1015.   Const PATCH_01              As Long = 18                                              'Code buffer offset to the location of the relative address to EbMode
  1016.   Const PATCH_02              As Long = 68                                              'Address of the previous WndProc
  1017.   Const PATCH_03              As Long = 78                                              'Relative address of SetWindowsLong
  1018.   Const PATCH_06              As Long = 116                                             'Address of the previous WndProc
  1019.   Const PATCH_07              As Long = 121                                             'Relative address of CallWindowProc
  1020.   Const PATCH_0A              As Long = 186                                             'Address of the owner object
  1021.   Static aBuf(1 To CODE_LEN)  As Byte                                                   'Static code buffer byte array
  1022.   Static pCWP                 As Long                                                   'Address of the CallWindowsProc
  1023.   Static pEbMode              As Long                                                   'Address of the EbMode IDE break/stop/running function
  1024.   Static pSWL                 As Long                                                   'Address of the SetWindowsLong function
  1025.   Dim I                       As Long                                                   'Loop index
  1026.   Dim j                       As Long                                                   'Loop index
  1027.   Dim nSubIdx                 As Long                                                   'Subclass data index
  1028.   Dim sHex                    As String                                                 'Hex code string
  1029.   
  1030. 'If it's the first time through here..
  1031.   If aBuf(1) = 0 Then
  1032.   
  1033. 'The hex pair machine code representation.
  1034.     sHex = "5589E583C4F85731C08945FC8945F8EB0EE80000000083F802742185C07424E830000000837DF800750AE838000000E84D00" & _
  1035.            "00005F8B45FCC9C21000E826000000EBF168000000006AFCFF7508E800000000EBE031D24ABF00000000B900000000E82D00" & _
  1036.            "0000C3FF7514FF7510FF750CFF75086800000000E8000000008945FCC331D2BF00000000B900000000E801000000C3E33209" & _
  1037.            "C978078B450CF2AF75278D4514508D4510508D450C508D4508508D45FC508D45F85052B800000000508B00FF90A4070000C3"
  1038.  
  1039. 'Convert the string from hex pairs to bytes and store in the static machine code buffer
  1040.     I = 1
  1041.     Do While j < CODE_LEN
  1042.       j = j + 1
  1043.       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
  1044.       I = I + 2
  1045.     Loop                                                                                'Next pair of hex characters
  1046.     
  1047. 'Get API function addresses
  1048.     If Subclass_InIDE Then                                                              'If we're running in the VB IDE
  1049.       aBuf(16) = &H90                                                                   'Patch the code buffer to enable the IDE state code
  1050.       aBuf(17) = &H90                                                                   'Patch the code buffer to enable the IDE state code
  1051.       pEbMode = zAddrFunc(MOD_VBA6, FUNC_EBM)                                           'Get the address of EbMode in vba6.dll
  1052.       If pEbMode = 0 Then                                                               'Found?
  1053.         pEbMode = zAddrFunc(MOD_VBA5, FUNC_EBM)                                         'VB5 perhaps
  1054.       End If
  1055.     End If
  1056.     
  1057.     pCWP = zAddrFunc(MOD_USER, FUNC_CWP)                                                'Get the address of the CallWindowsProc function
  1058.     pSWL = zAddrFunc(MOD_USER, FUNC_SWL)                                                'Get the address of the SetWindowLongA function
  1059.     ReDim sc_aSubData(0 To 0) As tSubData                                               'Create the first sc_aSubData element
  1060.   Else
  1061.     nSubIdx = zIdx(lng_hWnd, True)
  1062.     If nSubIdx = -1 Then                                                                'If an sc_aSubData element isn't being re-cycled
  1063.       nSubIdx = UBound(sc_aSubData()) + 1                                               'Calculate the next element
  1064.       ReDim Preserve sc_aSubData(0 To nSubIdx) As tSubData                              'Create a new sc_aSubData element
  1065.     End If
  1066.     
  1067.     Subclass_Start = nSubIdx
  1068.   End If
  1069.  
  1070.   With sc_aSubData(nSubIdx)
  1071.     .hwnd = lng_hWnd                                                                    'Store the hWnd
  1072.     .nAddrSub = GlobalAlloc(GMEM_FIXED, CODE_LEN)                                       'Allocate memory for the machine code WndProc
  1073.     .nAddrOrig = SetWindowLongA(.hwnd, GWL_WNDPROC, .nAddrSub)                          'Set our WndProc in place
  1074.     Call RtlMoveMemory(ByVal .nAddrSub, aBuf(1), CODE_LEN)                              'Copy the machine code from the static byte array to the code array in sc_aSubData
  1075.     Call zPatchRel(.nAddrSub, PATCH_01, pEbMode)                                        'Patch the relative address to the VBA EbMode api function, whether we need to not.. hardly worth testing
  1076.     Call zPatchVal(.nAddrSub, PATCH_02, .nAddrOrig)                                     'Original WndProc address for CallWindowProc, call the original WndProc
  1077.     Call zPatchRel(.nAddrSub, PATCH_03, pSWL)                                           'Patch the relative address of the SetWindowLongA api function
  1078.     Call zPatchVal(.nAddrSub, PATCH_06, .nAddrOrig)                                     'Original WndProc address for SetWindowLongA, unsubclass on IDE stop
  1079.     Call zPatchRel(.nAddrSub, PATCH_07, pCWP)                                           'Patch the relative address of the CallWindowProc api function
  1080.     Call zPatchVal(.nAddrSub, PATCH_0A, ObjPtr(Me))                                     'Patch the address of this object instance into the static machine code buffer
  1081.   End With
  1082. Errs:
  1083. End Function
  1084.  
  1085. 'Stop all subclassing
  1086. Private Sub Subclass_StopAll()
  1087. On Error GoTo Errs
  1088.   Dim I As Long
  1089.   
  1090.   I = UBound(sc_aSubData())                                                             'Get the upper bound of the subclass data array
  1091.   Do While I >= 0                                                                       'Iterate through each element
  1092.     With sc_aSubData(I)
  1093.       If .hwnd <> 0 Then                                                                'If not previously Subclass_Stop'd
  1094.         Call Subclass_Stop(.hwnd)                                                       'Subclass_Stop
  1095.       End If
  1096.     End With
  1097.     I = I - 1                                                                           'Next element
  1098.   Loop
  1099. Errs:
  1100. End Sub
  1101.  
  1102. 'Stop subclassing the passed window handle
  1103. Private Sub Subclass_Stop(ByVal lng_hWnd As Long)
  1104. On Error GoTo Errs
  1105. 'Parameters:
  1106.   'lng_hWnd  - The handle of the window to stop being subclassed
  1107.   With sc_aSubData(zIdx(lng_hWnd))
  1108.     Call SetWindowLongA(.hwnd, GWL_WNDPROC, .nAddrOrig)                                 'Restore the original WndProc
  1109.     Call zPatchVal(.nAddrSub, PATCH_05, 0)                                              'Patch the Table B entry count to ensure no further 'before' callbacks
  1110.     Call zPatchVal(.nAddrSub, PATCH_09, 0)                                              'Patch the Table A entry count to ensure no further 'after' callbacks
  1111.     Call GlobalFree(.nAddrSub)                                                          'Release the machine code memory
  1112.     .hwnd = 0                                                                           'Mark the sc_aSubData element as available for re-use
  1113.     .nMsgCntB = 0                                                                       'Clear the before table
  1114.     .nMsgCntA = 0                                                                       'Clear the after table
  1115.     Erase .aMsgTblB                                                                     'Erase the before table
  1116.     Erase .aMsgTblA                                                                     'Erase the after table
  1117.   End With
  1118. Errs:
  1119. End Sub
  1120.  
  1121. '=======================================================================================================
  1122. 'These z??? routines are exclusively called by the Subclass_??? routines.
  1123.  
  1124. 'Worker sub for Subclass_AddMsg
  1125. Private Sub zAddMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  1126. On Error GoTo Errs
  1127.   Dim nEntry  As Long                                                                   'Message table entry index
  1128.   Dim nOff1   As Long                                                                   'Machine code buffer offset 1
  1129.   Dim nOff2   As Long                                                                   'Machine code buffer offset 2
  1130.   
  1131.   If uMsg = ALL_MESSAGES Then                                                           'If all messages
  1132.     nMsgCnt = ALL_MESSAGES                                                              'Indicates that all messages will callback
  1133.   Else                                                                                  'Else a specific message number
  1134.     Do While nEntry < nMsgCnt                                                           'For each existing entry. NB will skip if nMsgCnt = 0
  1135.       nEntry = nEntry + 1
  1136.       
  1137.       If aMsgTbl(nEntry) = 0 Then                                                       'This msg table slot is a deleted entry
  1138.         aMsgTbl(nEntry) = uMsg                                                          'Re-use this entry
  1139.         Exit Sub                                                                        'Bail
  1140.       ElseIf aMsgTbl(nEntry) = uMsg Then                                                'The msg is already in the table!
  1141.         Exit Sub                                                                        'Bail
  1142.       End If
  1143.     Loop                                                                                'Next entry
  1144.  
  1145.     nMsgCnt = nMsgCnt + 1                                                               'New slot required, bump the table entry count
  1146.     ReDim Preserve aMsgTbl(1 To nMsgCnt) As Long                                        'Bump the size of the table.
  1147.     aMsgTbl(nMsgCnt) = uMsg                                                             'Store the message number in the table
  1148.   End If
  1149.  
  1150.   If When = eMsgWhen.MSG_BEFORE Then                                                    'If before
  1151.     nOff1 = PATCH_04                                                                    'Offset to the Before table
  1152.     nOff2 = PATCH_05                                                                    'Offset to the Before table entry count
  1153.   Else                                                                                  'Else after
  1154.     nOff1 = PATCH_08                                                                    'Offset to the After table
  1155.     nOff2 = PATCH_09                                                                    'Offset to the After table entry count
  1156.   End If
  1157.  
  1158.   If uMsg <> ALL_MESSAGES Then
  1159.     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.
  1160.   End If
  1161.   Call zPatchVal(nAddr, nOff2, nMsgCnt)                                                 'Patch the appropriate table entry count
  1162. Errs:
  1163. End Sub
  1164.  
  1165. 'Return the memory address of the passed function in the passed dll
  1166. Private Function zAddrFunc(ByVal sDLL As String, ByVal sProc As String) As Long
  1167.   zAddrFunc = GetProcAddress(GetModuleHandleA(sDLL), sProc)
  1168.   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
  1169. End Function
  1170.  
  1171. 'Worker sub for Subclass_DelMsg
  1172. Private Sub zDelMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  1173. On Error GoTo Errs
  1174.   Dim nEntry As Long
  1175.   
  1176.   If uMsg = ALL_MESSAGES Then                                                           'If deleting all messages
  1177.     nMsgCnt = 0                                                                         'Message count is now zero
  1178.     If When = eMsgWhen.MSG_BEFORE Then                                                  'If before
  1179.       nEntry = PATCH_05                                                                 'Patch the before table message count location
  1180.     Else                                                                                'Else after
  1181.       nEntry = PATCH_09                                                                 'Patch the after table message count location
  1182.     End If
  1183.     Call zPatchVal(nAddr, nEntry, 0)                                                    'Patch the table message count to zero
  1184.   Else                                                                                  'Else deleteting a specific message
  1185.     Do While nEntry < nMsgCnt                                                           'For each table entry
  1186.       nEntry = nEntry + 1
  1187.       If aMsgTbl(nEntry) = uMsg Then                                                    'If this entry is the message we wish to delete
  1188.         aMsgTbl(nEntry) = 0                                                             'Mark the table slot as available
  1189.         Exit Do                                                                         'Bail
  1190.       End If
  1191.     Loop                                                                                'Next entry
  1192.   End If
  1193. Errs:
  1194. End Sub
  1195.  
  1196. 'Get the sc_aSubData() array index of the passed hWnd
  1197. Private Function zIdx(ByVal lng_hWnd As Long, Optional ByVal bAdd As Boolean = False) As Long
  1198. On Error GoTo Errs
  1199. 'Get the upper bound of sc_aSubData() - If you get an error here, you're probably Subclass_AddMsg-ing before Subclass_Start
  1200.   zIdx = UBound(sc_aSubData)
  1201.   Do While zIdx >= 0                                                                    'Iterate through the existing sc_aSubData() elements
  1202.     With sc_aSubData(zIdx)
  1203.       If .hwnd = lng_hWnd Then                                                          'If the hWnd of this element is the one we're looking for
  1204.         If Not bAdd Then                                                                'If we're searching not adding
  1205.           Exit Function                                                                 'Found
  1206.         End If
  1207.       ElseIf .hwnd = 0 Then                                                             'If this an element marked for reuse.
  1208.         If bAdd Then                                                                    'If we're adding
  1209.           Exit Function                                                                 'Re-use it
  1210.         End If
  1211.       End If
  1212.     End With
  1213.     zIdx = zIdx - 1                                                                     'Decrement the index
  1214.   Loop
  1215.   
  1216. '  If Not bAdd Then
  1217. '    Debug.Assert False                                                                  'hWnd not 9zaixD?du2='   Caenter False      wFlags = DST_ICON
  1218.         Else
  1219.       -
  1220.   Const MOD_USER                 =        i                            =      'h - pData(0 To nSubIdx) As tSubData     Wnd Then                                  u0      tSubData   'h - pDg
  1221.   
  1222. 'If iVal nAddr As r    PATssages
  1223.   SetWindowLongA             sl nAddr As r    PATssages
  1224.   arr= eMsgWhen.MaF   PATter F
  1225.        ssages    ,ATCH_09          &dw under that position
  1226.     H = WindowH_09  
  1227.   WICall Se      ause R                   Val sProc As StringaptionAl  End If
  1228.   StringOhat position                       'Re-use it
  1229.         Endm                    before, afters Long, ByVvb5 else theonal ByValB    742185CseThemes ringOhat position P-6WEmde buffe
  1230.     zIwe wish to delse theonal 
  1231.   End If
  1232.  
  1233.   I0C If
  1234.  shButtxei,   0EE80000ubData        LonTset to th)8siti}}