home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 4_2005-2006.ISO / data / Zips / FYI__Custo2000486122006.psc / clsBorders.cls < prev   
Text File  |  2006-06-12  |  62KB  |  1,084 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 = "cBorders"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14.  
  15. ' VB IDE Safe Subclassing provided by...
  16. '*************************************************************************************************
  17. '* cSelfSub - self-subclassing class template
  18. '*
  19. '* Paul_Caton@hotmail.com
  20. '* Copyright free, use and abuse as you see fit.
  21. '*
  22. '* v1.0 Re-write of the SelfSub/WinSubHook-2 submission to Planet Source Code............ 20060322
  23. '* v1.1 VirtualAlloc memory to prevent Data Execution Prevention faults on Win64......... 20060324
  24. '* v1.2 Thunk redesigned to handle unsubclassing and memory release...................... 20060325
  25. '* v1.3 Data array scrapped in favour of property accessors.............................. 20060405
  26. '*************************************************************************************************
  27.  
  28. ' While I assisted troubleshooting one of TerriTop's apps, I came up with one solution
  29. ' that didn't quite work for him, but he suggested I share it with PSC. So here it is.
  30.  
  31. ' Purpose: Provide custom border colors for some common VB controls. The controls this
  32. '   should work for are those that have static borders. In other words, during normal,
  33. '   click, and enable events, the border does not change. VB Command Buttons are a
  34. '   prime example of borders that do change, therefore, this wouldn't work for them.
  35. '   Not all static border controls have been tested. The ones in this example have been
  36. '   and I could include others by request, but the borders must be static, not dynamic.
  37.  
  38. ' The complex part of this project was trying to determine how borders were drawn on
  39. '   the controls. Some controls have actual border window styles (i.e., WS_Border,
  40. '   WS_EX_ClientEdge, etc) and those can be drawn over during a WM_NCPaint message.
  41. '   But others have their borders drawn on their client area or one of their children's
  42. '   client area. Another exception is the ImageCombo which is an owner-drawn control.
  43.  
  44.  
  45. Option Explicit
  46.  
  47. '-Selfsub declarations----------------------------------------------------------------------------
  48. Private Enum eMsgWhen                                                       'When to callback
  49.   MSG_BEFORE = 1                                                            'Callback before the original WndProc
  50.   MSG_AFTER = 2                                                             'Callback after the original WndProc
  51.   MSG_BEFORE_AFTER = MSG_BEFORE Or MSG_AFTER                                'Callback before and after the original WndProc
  52. End Enum
  53.  
  54. Private Const ALL_MESSAGES  As Long = -1                                    'All messages callback
  55. Private Const MSG_ENTRIES   As Long = 32                                    'Number of msg table entries
  56. Private Const CODE_LEN      As Long = 240                                   'Thunk length in bytes
  57. Private Const WNDPROC_OFF   As Long = &H30                                  'WndProc execution offset
  58. Private Const MEM_LEN       As Long = CODE_LEN + (8 * (MSG_ENTRIES + 1))    'Bytes to allocate per thunk, data + code + msg tables
  59. Private Const PAGE_RWX      As Long = &H40                                  'Allocate executable memory
  60. Private Const MEM_COMMIT    As Long = &H1000                                'Commit allocated memory
  61. Private Const GWL_WNDPROC   As Long = -4                                    'SetWindowsLong WndProc index
  62. Private Const IDX_SHUTDOWN  As Long = 1                                     'Shutdown flag data index
  63. Private Const IDX_HWND      As Long = 2                                     'hWnd data index
  64. Private Const IDX_EBMODE    As Long = 3                                     'EbMode data index
  65. Private Const IDX_CWP       As Long = 4                                     'CallWindowProc data index
  66. Private Const IDX_SWL       As Long = 5                                     'SetWindowsLong data index
  67. Private Const IDX_FREE      As Long = 6                                     'VirtualFree data index
  68. Private Const IDX_ME        As Long = 7                                     'Owner data index
  69. Private Const IDX_WNDPROC   As Long = 8                                     'Original WndProc data index
  70. Private Const IDX_CALLBACK  As Long = 9                                     'zWndProc data index
  71. Private Const IDX_BTABLE    As Long = 10                                    'Before table data index
  72. Private Const IDX_ATABLE    As Long = 11                                    'After table data index
  73. Private Const IDX_EBX       As Long = 14                                    'Data code index
  74.  
  75. Private z_Base              As Long                                         'Data pointer base
  76. Private z_Funk              As Collection                                   'hWnd/thunk-address collection
  77. Private z_TblEnd            As Long                                         'End of the vTable
  78. Private z_Code(29)          As Currency                                     'Thunk machine-code initialised here
  79.  
  80. Private Declare Function CallWindowProcA Lib "user32" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  81. Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
  82. Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
  83. Private Declare Function IsBadCodePtr Lib "kernel32" (ByVal lpfn As Long) As Long
  84. Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
  85. Private Declare Function SetWindowLongA Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  86. Private Declare Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
  87. Private Declare Sub RtlMoveMemory Lib "kernel32" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)
  88. '-------------------------------------------------------------------------------------------------
  89.  
  90.  
  91. Private Declare Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  92. Private Declare Function GetWindowLong Lib "user32.dll" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  93. Private Declare Function SetWindowPos Lib "user32.dll" (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
  94. Private Declare Function ScreenToClient Lib "user32.dll" (ByVal hwnd As Long, ByRef lpPoint As RECT) As Long
  95. Private Declare Function GetWindowRect Lib "user32.dll" (ByVal hwnd As Long, ByRef lpRect As RECT) As Long
  96. Private Declare Function OffsetRect Lib "user32.dll" (ByRef lpRect As RECT, ByVal x As Long, ByVal y As Long) As Long
  97. Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
  98. Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
  99. Private Declare Function GetWindowDC Lib "user32.dll" (ByVal hwnd As Long) As Long
  100. Private Declare Function FrameRect Lib "user32.dll" (ByVal hDC As Long, ByRef lpRect As RECT, ByVal hBrush As Long) As Long
  101. Private Declare Function ReleaseDC Lib "user32.dll" (ByVal hwnd As Long, ByVal hDC As Long) As Long
  102. Private Declare Function GetSystemMetrics Lib "user32.dll" (ByVal nIndex As Long) As Long
  103. Private Declare Function GetParent Lib "user32.dll" (ByVal hwnd As Long) As Long
  104. Private Declare Function InflateRect Lib "user32.dll" (ByRef lpRect As RECT, ByVal x As Long, ByVal y As Long) As Long
  105. Private Declare Function InvalidateRect Lib "user32.dll" (ByVal hwnd As Long, ByRef lpRect As Any, ByVal bErase As Long) As Long
  106. Private Declare Function UpdateWindow Lib "user32.dll" (ByVal hwnd As Long) As Long
  107.  
  108. Private Declare Function Rectangle Lib "gdi32.dll" (ByVal hDC As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
  109. Private Declare Function CreateSolidBrush Lib "gdi32.dll" (ByVal crColor As Long) As Long
  110. Private Declare Function CreatePen Lib "gdi32.dll" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
  111. Private Declare Function SelectObject Lib "gdi32.dll" (ByVal hDC As Long, ByVal hObject As Long) As Long
  112. Private Declare Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long
  113. Private Declare Function GetBkColor Lib "gdi32.dll" (ByVal hDC As Long) As Long
  114. Private Declare Function MoveToEx Lib "gdi32.dll" (ByVal hDC As Long, ByVal x As Long, ByVal y As Long, ByRef lpPoint As Any) As Long
  115. Private Declare Function LineTo Lib "gdi32.dll" (ByVal hDC As Long, ByVal x As Long, ByVal y As Long) As Long
  116. Private Declare Function GetGDIObject Lib "gdi32.dll" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, ByRef lpObject As Any) As Long
  117.  
  118. Private Const SWP_FRAMECHANGED As Long = &H20
  119. Private Const SWP_NOACTIVATE As Long = &H10
  120. Private Const SWP_NOMOVE As Long = &H2
  121. Private Const SWP_NOOWNERZORDER As Long = &H200
  122. Private Const SWP_NOSENDCHANGING As Long = &H400
  123. Private Const SWP_NOSIZE As Long = &H1
  124. Private Const SWP_NOZORDER As Long = &H4
  125. Private Const WS_EX_CLIENTEDGE As Long = &H200&
  126. Private Const WS_BORDER As Long = &H800000
  127. Private Const WM_NCPAINT As Long = &H85
  128. Private Const WM_PAINT As Long = &HF&
  129. Private Const WM_DRAWITEM As Long = &H2B
  130. Private Const GWL_STYLE As Long = -16
  131. Private Const GWL_EXSTYLE As Long = -20
  132. Private Const SM_CYBORDER As Long = 6
  133. Private Const SM_CYEDGE As Long = 46
  134. Private Const COLOR_BTNHIGHLIGHT As Long = 20
  135. Private Const COLOR_BTNSHADOW As Long = 16
  136. Private Const COLOR_3DDKSHADOW As Long = 21
  137. Private Const COLOR_3DLIGHT As Long = 22
  138. Private Const COLOR_WINDOWFRAME As Long = 6
  139. Private Const LBS_NOINTEGRALHEIGHT As Long = &H100&
  140. Private Const CBS_NOINTEGRALHEIGHT As Long = &H400&
  141. Private Const WM_CTLCOLOREDIT As Long = &H133
  142. Private Const WM_CTLCOLORLISTBOX As Long = &H134
  143. Private Type RECT
  144.     Left As Long
  145.     Top As Long
  146.     Right As Long
  147.     Bottom As Long
  148. End Type
  149. Private Type LOGBRUSH
  150.     lbStyle As Long
  151.     lbColor As Long
  152.     lbHatch As Long
  153. End Type
  154.  
  155. Private Settings() As Long
  156. Public Enum Specialcolors 'sneaky way of exposing public "constants" from a class
  157.     bsSysDefault = -2
  158.     bsAutoShade = -1
  159. End Enum
  160. Public Enum BorderStyleOptions
  161.     [_bs_Max] = 3
  162.     [_bsBackColor] = -3
  163.     bsFlat1Color = 0    ' 1 color, 1 pixel border around control
  164.     bsFlat2Color = 1    ' 2 color, 1 pixel border
  165.     bsSunken = 2        ' 4 color, 2 pixel border
  166.     bsRaised = 3        ' 4 color, 2 pixel border
  167. '    bsNone = 4          ' no borders at all -- not coded/not sure it will be
  168. End Enum
  169. Public Enum vbControlType
  170.     ctOther = 0         ' controls that have a true border (non-client area)
  171.     ctComboBox = 1      ' some draw on client and some on non-client
  172.     ctImageCombo = 2    ' owner-drawn by VB
  173.     ctListBox = 3       ' control height can change when changing border styles
  174.     ctTextBox = 4       ' when flat style, VB draws border on client area
  175. End Enum
  176.  
  177. Private Borders As Collection
  178.  
  179. Public Sub SetBorder(ByVal hwnd As Long, BorderStyle As BorderStyleOptions, _
  180.                     Optional ByVal ctrlType As vbControlType = ctOther, _
  181.                     Optional ByVal Shadow As Long = vbButtonShadow, _
  182.                     Optional ByVal DarkShadow As Long = bsAutoShade, _
  183.                     Optional ByVal Highlight As Long = bsAutoShade, _
  184.                     Optional ByVal LightShadow As Long = bsAutoShade)
  185.     
  186.     ' The control will be subclassed to allow custom borders. Therefore it is
  187.     ' best to call ResetBorder on any control loaded with Controls.Add before
  188.     ' that control is removed if you sent that added control here.
  189.     
  190.     ' ///// Border Styles \\\\\
  191.     ' bsFlat1Color. Solid 1-pixel border, 1 color (i.e., flat).
  192.     '       Uses Shadow only
  193.     ' bsFlat2Color. Left/Top borders are 1 color, right/bottom are another
  194.     '       Uses Shadow & Highlight only
  195.     ' bsSunken. Left/Top outer border are Shadow, Right/Bottom outer are HighLight
  196.     '           Left/Top inner border are DarkShadow, Right/Bottom inner are LightShadow
  197.     ' bsRaised. Left/Top outer border are HighLight, Right/Bottom outer are DarkShadow
  198.     '           Left/Top inner border are LightShadow, Right/Bottom outer are Shadow
  199.     
  200.     ' ///// colors \\\\\ vb system colors can be passed
  201.     ' Shadow: 2nd darkest of 4 color borders; color for a single color border
  202.     ' DarkShadow: the darkest of 4 color borders
  203.     ' LightShadow: 2nd lightest of 4 color borders
  204.     ' Highlight: lightest of 4 color borders
  205.     ' Special values for the above 4 colors
  206.     '   -1 = AutoShade. DarkShadow, LightShadow & Highlight are shades of Shadow
  207.     '           DarkShadow = Shadow darkened to 15% from black
  208.     '           LightShadow = Shadow lightened by 85% of its lightest value (white)
  209.     '           Highlight = Shadow lightened by 100% (or vbWhite)
  210.     '   -2 = System colors: vb3DDKShadow, vbButtonShadow, vb3DLight, vbHighlight respectively
  211.     '   -3 & -4 (Reserved) are used by the class to fake single borders on combo boxes
  212.     
  213.     ' ///// Control Type \\\\\
  214.     ' Some controls have their borders drawn by VB on the control's client area whereas
  215.     '   others are drawn in the non-client area as expected. Think of a form with no
  216.     '   borders but you want borders so you draw it on the form (non-client area).
  217.     '   VB combo boxes are very much like that scenario. Therefore, the control type
  218.     '   needs to be known in advance so the class can handle those special cases.
  219.     '   There are more special cases too & those known are handled herein
  220.     ' ctComboBox: use for comboboxes and drivecombo
  221.     ' ctImageCombo: use for the image combobox
  222.     ' ctListBox: use for listboxes and file listboxes
  223.     ' ctTextBox: use for the textbox control
  224.     ' ctOther: use for other controls like treeview, listview, progressbar, etc
  225.     
  226.     
  227.     ' sanity checks first & don't allow user to pass a reserved color code
  228.     If BorderStyle < bsFlat1Color Or BorderStyle > [_bs_Max] Then Exit Sub
  229.     If DarkShadow = -3 Or DarkShadow = -4 Then DarkShadow = 0
  230.     If LightShadow = -3 Or LightShadow = -4 Then LightShadow = 0
  231.     If Shadow = -3 Or Shadow = -4 Then Shadow = 0
  232.     If Highlight = -3 Or Highlight = -4 Then Highlight = 0
  233.     
  234.     Dim Index As Long, lFlags As Long, isSubclassed As Boolean
  235.     Dim lExStyle As Long, lStyle As Long, lOldStyle As Long
  236.     Dim cHwnd As Long, wRect As RECT, cRect As RECT
  237.     Dim borderSize As Long, borderSizeNew As Long
  238.     ' flags for special case control scenarios
  239.     Dim bIntegralHT As Boolean, bFlatTextBox As Boolean, bHasBorder As Boolean
  240.     
  241.     On Error Resume Next
  242.     If Borders Is Nothing Then          ' first time thru
  243.         Set Borders = New Collection
  244.         Index = 1
  245.         ReDim Settings(1 To 8)
  246.         Borders.Add Index, "h" & hwnd
  247.     Else
  248.         Index = Borders("h" & hwnd)     ' been here before, is this hWnd already ours?
  249.         If Index = 0 Then               ' nope, set it up
  250.             Index = UBound(Settings) + 1
  251.             ReDim Preserve Settings(1 To Index + 7)
  252.             Borders.Add Index, "h" & hwnd
  253.             If Err Then Err.Clear
  254.         Else
  255.             isSubclassed = True         ' yep, use cached settings
  256.         End If
  257.     End If
  258.     
  259.     ' cache colors & style settings
  260.     Settings(Index) = CLng(BorderStyle Or (ctrlType * &H100&))
  261.     Settings(Index + 1) = Shadow
  262.     Settings(Index + 2) = Highlight
  263.     Settings(Index + 3) = DarkShadow
  264.     Settings(Index + 4) = LightShadow
  265.     Settings(Index + 7) = hwnd        ' needed when re-indexing if control is unsubclassed
  266.     If isSubclassed Then
  267.         lStyle = Settings(Index + 5)
  268.         lExStyle = Settings(Index + 6)
  269.     Else
  270.         lExStyle = GetWindowLong(hwnd, GWL_EXSTYLE)
  271.         lStyle = GetWindowLong(hwnd, GWL_STYLE)
  272.         Settings(Index + 5) = lStyle
  273.         Settings(Index + 6) = lExStyle
  274.     End If
  275.     ' setwindowpos flags
  276.     lFlags = SWP_FRAMECHANGED Or SWP_NOACTIVATE Or SWP_NOMOVE Or SWP_NOOWNERZORDER Or SWP_NOSIZE Or SWP_NOZORDER
  277.     
  278.     ' special case for combo boxes. We can't remove the borders on these, because
  279.     ' they really aren't borders for the most part; they are drawn on the client
  280.     ' ComboBox.Style=1 is an exception. It has borders come from its child's borders,
  281.     ' but to make things a little bit easier we will treat all combos the same
  282.     If ctrlType = ctComboBox Or ctrlType = ctImageCombo Then
  283.         If BorderStyle = bsFlat2Color Or BorderStyle = bsFlat1Color Then
  284.             ' to fake a 1-pixel border, we will draw the inner level of borders
  285.             ' in the same backcolor as the control. Special flags are set for this.
  286.             ' Note: GetBkColor does not return the control's BackColor property.
  287.             ' These controls are sent a wm_ctlcoloredit or wm_ctlcolorlistbox message
  288.             ' forcing the parent object to provide the bacground color brush
  289.             Settings(Index + 3) = [_bsBackColor]
  290.             Settings(Index + 4) = [_bsBackColor] - 1
  291.             ' normally a 1 pixel border only requires 2 colors and only an outer border
  292.             ' however, we'll tweak so 4 colors are used & also an inner border
  293.             Settings(Index) = CLng(bsSunken Or (ctrlType * &H100&))
  294.             If BorderStyle = bsFlat1Color Then Settings(Index + 2) = Settings(Index + 1)
  295.         End If
  296.     Else
  297.         ' combo styles are not modified, but color tweaked above to appear so
  298.         Select Case BorderStyle
  299.         Case bsFlat1Color, bsFlat2Color
  300.             If ctrlType = ctTextBox Then
  301.                 ' another exception: textbox with flat border style draws border on client
  302.                 If (lExStyle And WS_EX_CLIENTEDGE) = 0 Then ' flat?
  303.                     lStyle = lStyle And Not WS_BORDER
  304.                     bFlatTextBox = True
  305.                 Else
  306.                     lStyle = lStyle Or WS_BORDER
  307.                 End If
  308.             Else
  309.                 lStyle = lStyle Or WS_BORDER
  310.             End If
  311.             lExStyle = lExStyle And Not WS_EX_CLIENTEDGE
  312.         Case Else
  313.             If ctrlType = ctTextBox Then
  314.                 ' another exception: textbox with flat border style draws border on client
  315.                 If (Settings(Index + 6) And WS_EX_CLIENTEDGE) = 0 Then ' flat?
  316.                     ' we need 2 borders, one drawn on client & we provide the 2nd one via WS_Border
  317.                     lStyle = lStyle Or WS_BORDER
  318.                     lExStyle = lExStyle And Not WS_EX_CLIENTEDGE
  319.                     bFlatTextBox = True
  320.                 Else
  321.                     lStyle = lStyle And Not WS_BORDER
  322.                     lExStyle = lExStyle Or WS_EX_CLIENTEDGE
  323.                 End If
  324.             Else
  325.                 lStyle = lStyle And Not WS_BORDER
  326.                 lExStyle = lExStyle Or WS_EX_CLIENTEDGE
  327.             End If
  328.         End Select
  329.         
  330.         ' now should a user be setting a listbox (IntegralHeight=True) border to
  331.         ' raised/sunken when it was previously set to flat, simply changing the borderstyles
  332.         ' will result in the listbox shrinking in size. We should account for that
  333.         
  334.         ' get current bordersize
  335.         lOldStyle = GetWindowLong(hwnd, GWL_STYLE)
  336.         ' get current bordersize
  337.         If (lOldStyle And WS_BORDER) = WS_BORDER Then
  338.             borderSize = GetSystemMetrics(SM_CYBORDER)
  339.             bHasBorder = True
  340.         End If
  341.         If (GetWindowLong(hwnd, GWL_EXSTYLE) And WS_EX_CLIENTEDGE) = WS_EX_CLIENTEDGE Then
  342.             borderSize = borderSize + GetSystemMetrics(SM_CYEDGE)
  343.         Else
  344.             bHasBorder = False
  345.         End If
  346.         GetWindowRect hwnd, wRect
  347.         cRect = wRect
  348.         OffsetRect wRect, -wRect.Left, -wRect.Top
  349.  
  350.         If ctrlType = ctListBox Then
  351.             bIntegralHT = ((lOldStyle And LBS_NOINTEGRALHEIGHT) = 0)
  352.             
  353.             ' get adjusted bordersize
  354.             If (lStyle And WS_BORDER) = WS_BORDER Then borderSizeNew = GetSystemMetrics(SM_CYBORDER)
  355.             If (lExStyle And WS_EX_CLIENTEDGE) = WS_EX_CLIENTEDGE Then borderSizeNew = borderSizeNew + GetSystemMetrics(SM_CYEDGE)
  356.             If bIntegralHT = True Then
  357.                 If borderSizeNew > borderSize Then
  358.                     ' adjust height as needed
  359.                     wRect.Bottom = wRect.Bottom + (borderSizeNew - borderSize) * 2
  360.                     lFlags = lFlags And Not SWP_NOSIZE
  361.                 End If
  362.             End If
  363.         End If
  364.         If bHasBorder Then
  365.             ' some controls (FileList, Treeview, ListView) have an extra border.
  366.             ' It is same color as the form's back color an is the far outer border.
  367.             ' We will be removing that but to ensure the control looks as though it
  368.             ' stays at the same X,Y coordinates after we remove it, we will shift
  369.             ' the control 1 pixel
  370.             ScreenToClient GetParent(hwnd), cRect
  371.             OffsetRect cRect, 1, 1
  372.             lFlags = lFlags And Not SWP_NOMOVE
  373.         Else
  374.             cRect.Left = 0: cRect.Top = 0
  375.         End If
  376.         SetWindowLong hwnd, GWL_EXSTYLE, lExStyle
  377.         SetWindowLong hwnd, GWL_STYLE, lStyle
  378.     End If
  379.     
  380.     If Not isSubclassed Then    ' haven't subclassed this hWnd yet
  381.         sc_Subclass hwnd
  382.         If ctrlType = ctImageCombo Then
  383.             sc_AddMsg hwnd, WM_DRAWITEM, MSG_AFTER ' ownerdrawn control
  384.             sc_AddMsg hwnd, WM_CTLCOLOREDIT, MSG_BEFORE
  385.         ElseIf ctrlType = ctComboBox Or bFlatTextBox = True Then
  386.             ' draw border on client
  387.             sc_AddMsg hwnd, WM_PAINT, MSG_AFTER
  388.             sc_AddMsg hwnd, WM_CTLCOLOREDIT, MSG_BEFORE
  389.             sc_AddMsg hwnd, WM_CTLCOLORLISTBOX, MSG_BEFORE
  390.         Else
  391.             sc_AddMsg hwnd, WM_NCPAINT, MSG_AFTER ' draw border on non-client
  392.         End If
  393.     End If
  394.     
  395.     ' force any border changes now
  396.     SetWindowPos hwnd, 0, cRect.Left, cRect.Top, wRect.Right, wRect.Bottom, lFlags
  397.         
  398.     ' force repaint. SetWindowPos seems not to do this for all controls
  399.     InvalidateRect hwnd, ByVal 0&, True
  400.     
  401. End Sub
  402.  
  403. Public Sub ReSetBorder(ByVal hwnd As Long, Optional Refresh As Boolean = True)
  404.  
  405.     ' function removes an custom borders & resets to system defaults
  406.     ' For extra info on comments, see SetBorder. This is basically the
  407.     ' opposite logic that was applied in SetBorder
  408.     
  409.     If Borders Is Nothing Then Exit Sub
  410.     
  411.     Dim Index As Long
  412.     Dim wRect As RECT, cRect As RECT
  413.     Dim lStyle As Long, lStyleEX As Long
  414.     Dim borderSize As Long, borderSizeNew As Long
  415.     Dim lFlags As Long
  416.     Dim ctrlType As Long
  417.     Dim bIntegralHT As Boolean, bHasBorder As Boolean
  418.     
  419.     On Error Resume Next
  420.     Index = Borders("h" & hwnd) ' did we subclass this one?
  421.     If Index = 0 Then
  422.         If Err Then Err.Clear   ' nope, nothing to do
  423.         Exit Sub
  424.     End If
  425.     On Error GoTo 0
  426.  
  427.     sc_UnSubclass hwnd  ' unsubclass it
  428.     ' these will be the flags used for SetWindowPos
  429.     lFlags = SWP_FRAMECHANGED Or SWP_NOACTIVATE Or SWP_NOMOVE Or SWP_NOOWNERZORDER Or SWP_NOZORDER Or SWP_NOSIZE
  430.     
  431.     ctrlType = ((Settings(Index) \ &HFF) And &HFF)
  432.     
  433.     ' comboboxes go through un-modified, don't need to run extra code for those
  434.     If Not (ctrlType = ctComboBox Or ctrlType = ctImageCombo) Then
  435.         ' listboxes and comboboxes could shrink in height if there IntegralHeight
  436.         ' property is set to True. This is because leaving the control the same
  437.         ' size but adding extra border pixels to it would reduce the total client
  438.         ' area and thus force the control to resize itself smaller. Account for this.
  439.         ' Additionally, other controls like FileList, Treeview, ListView had
  440.         ' and extra border level we compensated for by shifting its X,Y position.
  441.         ' Re-shift it if needed.
  442.         
  443.         GetWindowRect hwnd, wRect
  444.         cRect = wRect
  445.         OffsetRect wRect, -wRect.Left, -wRect.Top
  446.     
  447.         ' calculate the original bordersize
  448.         If (Settings(Index + 5) And WS_BORDER) = WS_BORDER Then
  449.             borderSizeNew = GetSystemMetrics(SM_CYBORDER)
  450.             bHasBorder = True
  451.         End If
  452.         If (Settings(Index + 6) And WS_EX_CLIENTEDGE) = WS_EX_CLIENTEDGE Then
  453.             borderSizeNew = borderSizeNew + GetSystemMetrics(SM_CYEDGE)
  454.         Else
  455.             bHasBorder = False
  456.         End If
  457.     
  458.         If ctrlType = ctListBox Then
  459.             bIntegralHT = ((Settings(Index + 5) And LBS_NOINTEGRALHEIGHT) = 0)
  460.             
  461.             ' calculate the current vertical bordersize
  462.             lStyle = GetWindowLong(hwnd, GWL_STYLE)
  463.             lStyleEX = GetWindowLong(hwnd, GWL_EXSTYLE)
  464.             If (lStyle And WS_BORDER) = WS_BORDER Then borderSize = GetSystemMetrics(SM_CYBORDER)
  465.             If (lStyleEX And WS_EX_CLIENTEDGE) = WS_EX_CLIENTEDGE Then borderSize = borderSize + GetSystemMetrics(SM_CYEDGE)
  466.             
  467.             ' now calculate any difference in height
  468.             If bIntegralHT Then ' our control doesn't have IntegralHeight=False
  469.                 If borderSizeNew > borderSize Then
  470.                     ' need to resize the control when we re-apply the original borders
  471.                     ' add the extra height needed so the client area can remain same size
  472.                     wRect.Bottom = wRect.Bottom + (borderSizeNew - borderSize) * 2
  473.                     lFlags = lFlags And Not SWP_NOSIZE ' we want to resize the control
  474.                 End If
  475.             End If
  476.         End If
  477.         If bHasBorder Then
  478.             ScreenToClient GetParent(hwnd), cRect
  479.             OffsetRect cRect, -1, -1
  480.             lFlags = lFlags And Not SWP_NOMOVE
  481.         Else
  482.             cRect.Left = 0: cRect.Top = 0
  483.         End If
  484.     
  485.     End If
  486.     ' set the new border styles as needed, then force a resize/redraw
  487.     SetWindowLong hwnd, GWL_STYLE, Settings(Index + 5)
  488.     SetWindowLong hwnd, GWL_EXSTYLE, Settings(Index + 6)
  489.     SetWindowPos hwnd, 0, cRect.Left, cRect.Top, wRect.Right, wRect.Bottom, lFlags
  490.  
  491.     ' now clean up our collection
  492.     If Borders.Count = 1 Then
  493.         Set Borders = Nothing
  494.         Erase Settings
  495.     Else
  496.         If Index + 8 < UBound(Settings) Then
  497.             ' move the last item in our collection over the unsubclassed item
  498.             lFlags = Borders.Item(Borders.Count)
  499.             RtlMoveMemory VarPtr(Settings(Index)), VarPtr(Settings(lFlags)), 32&
  500.             ' now rebuild the collection item
  501.             Borders.Remove "h" & Settings(Index + 7)
  502.             Borders.Add Index, "h" & Settings(Index + 7), "h" & hwnd
  503.         End If
  504.         Borders.Remove "h" & hwnd
  505.         ReDim Preserve Settings(1 To UBound(Settings) - 8)
  506.     End If
  507.  
  508.     ' force repaint. SetWindowPos seems not to do this for all controls
  509.     If Refresh Then InvalidateRect hwnd, ByVal 0&, True
  510.  
  511. End Sub
  512.  
  513. Private Sub RGBToHSL(inColor As Long, Hue As Single, _
  514.                 Luminance As Single, Saturation As Single)
  515.  
  516. ' various versions on the net. This one needed some tweaking but will accurately
  517. ' return the HSL +/- 2 on a single RGB value compared to the color picker.
  518.  
  519. ' The HSL values are ratios btwn 0:100 & can be applied to any maximum value with a
  520. ' minimum value of zero. For example: Luminance is a ratio btwn 0:100 and can be
  521. ' multiplied against 240 to return a minimum/maximum Luminance of 0 to 240 similar
  522. ' to how the color picker displays it.  It can also be applied against 256, the
  523. ' maximum number of shades of light.
  524.     Dim maxRGB As Single, minRGB As Single, hueDelta As Single
  525.     Dim Red As Single, Green As Single, Blue As Single
  526.     Dim bRGB(0 To 3) As Byte
  527.     
  528.     ' get long color into RGB bytes & convert to a ratio
  529.     bRGB(0) = inColor Mod &H100
  530.     bRGB(1) = (inColor \ &H100) Mod &H100
  531.     bRGB(2) = (inColor \ &H10000) Mod &H100
  532.     
  533.     Red = bRGB(0) / 255
  534.     Green = bRGB(1) / 255
  535.     Blue = bRGB(2) / 255
  536.     
  537.     ' simple little routine to get the largest/smallest of the RGB bytes
  538.     If Red > Green Then
  539.         minRGB = Green
  540.         maxRGB = Red
  541.     Else
  542.         minRGB = Red
  543.         maxRGB = Green
  544.     End If
  545.     If Blue > maxRGB Then
  546.         maxRGB = Blue
  547.     Else
  548.         If Blue < minRGB Then minRGB = Blue
  549.     End If
  550.  
  551.     ' calculate Luminance or Light as some know it
  552.     Luminance = (maxRGB + minRGB) / 2
  553.  
  554.     ' calculate Saturation
  555.     If maxRGB = minRGB Then
  556.         ' grayscale, only Luminance is used
  557.         Saturation = 0#
  558.         Hue = 0#
  559.     Else
  560.         If Luminance <= 0.5 Then
  561.            Saturation = (maxRGB - minRGB) / (maxRGB + minRGB)
  562.         Else
  563.            Saturation = (maxRGB - minRGB) / (2 - maxRGB - minRGB)
  564.         End If
  565.         ' now for the Hue; much more complicated
  566.         
  567.         ' get the difference between the largest/smallest color range
  568.         hueDelta = maxRGB - minRGB
  569.         
  570.         ' this is a bit complicated, but Hue is calculated here as a relation
  571.         ' to a 360 degree circle.
  572.         
  573.         ' The IF's below determine between what 2 of the 3 primary colors this color falls
  574.         ' in order to calculate the Hue
  575.         If Red = maxRGB Then
  576.            ' falls betweeen green & blue
  577.             Hue = (Green - Blue) / hueDelta
  578.         ElseIf Green = maxRGB Then
  579.            ' falls between blue and red
  580.             Hue = 2# + (Blue - Red) / hueDelta
  581.         Else    'Blue = maxRGB
  582.            ' falls between red & green
  583.             Hue = 4# + (Red - Green) / hueDelta
  584.         End If
  585.         ' convert to degrees
  586.         Hue = Hue * 60
  587.         ' check for reverse rotation & adjust by adding 360
  588.         If Hue < 0# Then Hue = Hue + 360#
  589.         ' now we can convert Hue & Saturation to a simple ratio before returning
  590.         Hue = Hue / 360# * 100#
  591.         Saturation = Saturation * 100#
  592.     End If
  593.     ' convert to proper ratio before returning
  594.     Luminance = Luminance * 100#
  595.     ' done: return result.
  596. End Sub
  597.  
  598. Private Function HSLToRGB(ByVal Hue As Single, _
  599.         ByVal Luminance As Single, ByVal Saturation As Single) As Long
  600.  
  601. ' various versions on the net. This one needed some tweaking but will accurately
  602. ' return the RGB +/- 2 on a single HSL value when compared to the color picker.
  603.  
  604. ' When the parameters are passed as percentages calculated from the RGBtoHSL
  605. ' routine, this routine will convert HSL to RGB with 100% accuracy.
  606.  
  607. ' function will return the RGB as a long:
  608. ' 1st byte=Red, 2nd byte=Blue , 3rd byte=Green, 4th byte is unused
  609.  
  610.     Dim Red As Single, Green As Single, Blue As Single
  611.     Dim minRGB As Single, maxRGB As Single
  612.     Dim bRGB(0 To 3) As Byte
  613.     
  614.     ' ensure passed parameters are within 0:100 range
  615.         If Saturation < 0 Then
  616.             Saturation = 0
  617.         ElseIf Saturation > 100 Then
  618.             Saturation = 100
  619.         End If
  620.         If Luminance < 0 Then
  621.             Luminance = 0
  622.         ElseIf Luminance > 100 Then
  623.             Luminance = 100
  624.         End If
  625.         If Hue < 0 Then
  626.             Hue = 0
  627.         ElseIf Hue > 100 Then
  628.             Hue = 100
  629.         End If
  630.     ' now convert ratios to a 0:1 range
  631.     Saturation = Saturation / 100
  632.     Luminance = Luminance / 100
  633.  
  634.     If Saturation = 0 Then
  635.        ' grayscale
  636.        Red = Luminance
  637.        Green = Luminance
  638.        Blue = Luminance
  639.     
  640.     Else
  641.         Hue = Hue / 100
  642.         ' calculate the least RGB value
  643.         If Luminance <= 0.5 Then
  644.             minRGB = Luminance * (1 - Saturation)
  645.         Else
  646.             minRGB = Luminance - Saturation * (1 - Luminance)
  647.         End If
  648.         ' calculate the greatest RGB value
  649.         maxRGB = 2 * Luminance - minRGB
  650.           
  651.         ' Hue is related to a circle or quadrants in this case
  652.         ' We will use ratio on 360 degree to determine reverse rotation
  653.         Hue = Hue * 360
  654.         ' Any angle calculated > 299.999 is a reverse rotation and the ratio
  655.         ' is actually a negative number. We need to get this negative value.
  656.         ' Note: This section checked by running all possible RGB combinations
  657.         ' thru the code to ensure RGB > HSL > RGB returned original color.
  658.         If Hue >= 300 Then Hue = Hue - 360
  659.         ' now to get the final 0:1 ratio...
  660.         Hue = Hue / 60
  661.         
  662.         ' this is the reverse of the RGBtoHSL routine & is where the magic happens
  663.         If (Hue < 1) Then
  664.            Red = maxRGB
  665.            If (Hue < 0) Then
  666.               Green = minRGB
  667.               Blue = Green - Hue * (maxRGB - minRGB)
  668.            Else
  669.               Blue = minRGB
  670.               Green = Hue * (maxRGB - minRGB) + Blue
  671.            End If
  672.         ElseIf (Hue < 3) Then
  673.            Green = maxRGB
  674.            If (Hue < 2) Then
  675.               Blue = minRGB
  676.               Red = Blue - (Hue - 2) * (maxRGB - minRGB)
  677.            Else
  678.               Red = minRGB
  679.               Blue = (Hue - 2) * (maxRGB - minRGB) + Red
  680.            End If
  681.         Else
  682.            Blue = maxRGB
  683.            If (Hue < 4) Then
  684.               Red = minRGB
  685.               Green = Red - (Hue - 4) * (maxRGB - minRGB)
  686.            Else
  687.               Green = minRGB
  688.               Red = (Hue - 4) * (maxRGB - minRGB) + Green
  689.            End If
  690.     
  691.         End If
  692.              
  693.     End If
  694.     
  695.     ' convert the ratios to RGB & return the result
  696.     bRGB(0) = CByte(Red * 255): bRGB(1) = Green * 255: bRGB(2) = Blue * 255
  697.     HSLToRGB = RGB(bRGB(0), bRGB(1), bRGB(2))
  698.  
  699. End Function
  700.  
  701. Private Function ConvertColor(tColor As Long) As Long
  702.  
  703. ' Converts VB color constants to real color values
  704.     If tColor < 0 Then
  705.         ConvertColor = GetSysColor(tColor And &HFF&)
  706.     Else
  707.         ConvertColor = tColor
  708.     End If
  709. End Function
  710.  
  711.  
  712. Private Sub Class_Terminate()
  713.   'Terminate all subclassing
  714.   
  715.   Dim I As Integer
  716.   If Not Borders Is Nothing Then
  717.     For I = Borders.Count To 1 Step -1
  718.         ReSetBorder Settings(Borders.Item(I) + 7), False
  719.     Next
  720.   End If
  721.   sc_Terminate
  722.   Set Borders = Nothing
  723.   Erase Settings
  724. End Sub
  725.  
  726. '-SelfSub code------------------------------------------------------------------------------------
  727. Private Function sc_Subclass(ByVal lng_hWnd As Long) As Boolean             'Subclass the specified window handle
  728.   Dim nAddr As Long
  729.   
  730.   If IsWindow(lng_hWnd) = 0 Then                                            'Ensure the window handle is valid
  731.     zError "sc_Subclass", "Invalid window handle"
  732.   End If
  733.  
  734.   If z_Funk Is Nothing Then                                                 'If first time
  735.     nAddr = zAddressOf(1)                                                   'Get the address of the final private method
  736.     
  737.     If nAddr = 0 Then
  738.       zError "sc_Subclass", "Callback method not found"
  739.     End If
  740.     
  741.     Set z_Funk = New Collection                                             'Create the hWnd/thunk-memory-address collection
  742.     
  743.     z_Code(6) = -490736517001394.5807@: z_Code(7) = 484417356483292.94@: z_Code(8) = -171798741966746.6996@: z_Code(9) = 843649688964536.7412@: z_Code(10) = -330085705188364.0817@: z_Code(11) = 41621208.9739@: z_Code(12) = -900372920033759.9903@: z_Code(13) = 291516653989344.1016@: z_Code(14) = -621553923181.6984@: z_Code(15) = 291551690021556.6453@: z_Code(16) = 28798458374890.8543@: z_Code(17) = 86444073845629.4399@: z_Code(18) = 636540268579660.4789@: z_Code(19) = 60911183420250.2143@: z_Code(20) = 846934495644380.8767@: z_Code(21) = 14073829823.4668@: z_Code(22) = 501055845239149.5051@: z_Code(23) = 175724720056981.1236@: z_Code(24) = 75457451135513.7931@: z_Code(25) = -576850389355798.3357@: z_Code(26) = 146298060653075.5445@: z_Code(27) = 850256350680294.7583@: z_Code(28) = -4888724176660.092@: z_Code(29) = 21456079546.6867@
  744.     
  745.     z_Base = VarPtr(z_Code(0))                                              'Map the address of z_Code()'s first element to the zData() array
  746.     zData(IDX_EBMODE) = zFnAddr("vba6", "EbMode")                           'Store the EbMode function address in the thunk data
  747.     zData(IDX_CWP) = zFnAddr("user32", "CallWindowProcA")                   'Store CallWindowProc function address in the thunk data
  748.     zData(IDX_SWL) = zFnAddr("user32", "SetWindowLongA")                    'Store the SetWindowLong function address in the thunk data
  749.     zData(IDX_FREE) = zFnAddr("kernel32", "VirtualFree")                    'Store the VirtualFree function address in the thunk data
  750.     zData(IDX_ME) = ObjPtr(Me)                                              'Store my object address in the thunk data
  751.     zData(IDX_CALLBACK) = nAddr                                             'Store the zWndProc address in the thunk data
  752.   End If
  753.   
  754.   z_Base = VirtualAlloc(0, MEM_LEN, MEM_COMMIT, PAGE_RWX)                   'Allocate executable memory
  755.   RtlMoveMemory z_Base, VarPtr(z_Code(0)), CODE_LEN                         'Copy the thunk to the allocated memory
  756.  
  757.   On Error GoTo Catch                                                       'Catch double subclassing
  758.     z_Funk.Add z_Base, "h" & lng_hWnd                                       'Add the hWnd/thunk-address to the collection
  759.   On Error GoTo 0
  760.  
  761.   zData(IDX_EBX) = z_Base                                                   'Patch the data address
  762.   zData(IDX_HWND) = lng_hWnd                                                'Store the window handle in the thunk data
  763.   zData(IDX_BTABLE) = z_Base + CODE_LEN                                     'Store the address of the before table in the thunk data
  764.   zData(IDX_ATABLE) = zData(IDX_BTABLE) + ((MSG_ENTRIES + 1) * 4)           'Store the address of the after table in the thunk data
  765.   zData(IDX_WNDPROC) = _
  766.                 SetWindowLongA(lng_hWnd, GWL_WNDPROC, z_Base + WNDPROC_OFF) 'Set the new WndProc and store the original WndProc in the thunk data
  767.   sc_Subclass = True                                                        'Indicate success
  768.   Exit Function                                                             'Exit
  769.  
  770. Catch:
  771.   zError "sc_Subclass", "Window handle is already subclassed"
  772. End Function
  773.  
  774. 'Terminate all subclassing
  775. Private Sub sc_Terminate()
  776.   Dim I     As Long
  777.   Dim nAddr As Long
  778.  
  779.   If z_Funk Is Nothing Then                                                 'Ensure that subclassing has been started
  780.     zError "sc_UnSubAll", "Subclassing hasn't been started", False
  781.   Else
  782.     With z_Funk
  783.       For I = .Count To 1 Step -1                                           'Loop through the collection of window handles in reverse order
  784.         nAddr = .Item(I)                                                    'Map zData() to the hWnd thunk address
  785.         If IsBadCodePtr(nAddr) = 0 Then                                     'Ensure that the thunk hasn't already freed itself
  786.           z_Base = nAddr                                                    'Map the thunk memory to the zData() array
  787.           sc_UnSubclass zData(IDX_HWND)                                     'UnSubclass
  788.         End If
  789.       Next I                                                                'Next member of the collection
  790.     End With
  791.     
  792.     Set z_Funk = Nothing                                                    'Destroy the hWnd/thunk-address collection
  793.   End If
  794. End Sub
  795.  
  796. 'UnSubclass the specified window handle
  797. Public Sub sc_UnSubclass(ByVal lng_hWnd As Long)
  798.   If z_Funk Is Nothing Then                                                 'Ensure that subclassing has been started
  799.     zError "UnSubclass", "Subclassing hasn't been started", False
  800.   Else
  801.     zDelMsg lng_hWnd, ALL_MESSAGES, IDX_BTABLE                              'Delete all before messages
  802.     zDelMsg lng_hWnd, ALL_MESSAGES, IDX_ATABLE                              'Delete all after messages
  803.     z_Base = zMap_hWnd(lng_hWnd)                                            'Map the thunk memory to the zData() array
  804.     zData(IDX_SHUTDOWN) = -1                                                'Set the shutdown indicator
  805.     z_Funk.Remove "h" & lng_hWnd                                            'Remove the specified window handle from the collection
  806.   End If
  807. End Sub
  808.  
  809. 'Add the message value to the window handle's specified callback table
  810. Private Sub sc_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = eMsgWhen.MSG_AFTER)
  811.   If When And MSG_BEFORE Then                                               'If the message is to be added to the before original WndProc table...
  812.     zAddMsg lng_hWnd, uMsg, IDX_BTABLE                                      'Add the message to the before table
  813.   End If
  814.  
  815.   If When And MSG_AFTER Then                                                'If message is to be added to the after original WndProc table...
  816.     zAddMsg lng_hWnd, uMsg, IDX_ATABLE                                      'Add the message to the after table
  817.   End If
  818. End Sub
  819.  
  820. 'Delete the message value from the window handle's specified callback table
  821. Private Sub sc_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = eMsgWhen.MSG_AFTER)
  822.   If When And MSG_BEFORE Then                                               'If the message is to be deleted from the before original WndProc table...
  823.     zDelMsg lng_hWnd, uMsg, IDX_BTABLE                                      'Delete the message from the before table
  824.   End If
  825.  
  826.   If When And MSG_AFTER Then                                                'If the message is to be deleted from the after original WndProc table...
  827.     zDelMsg lng_hWnd, uMsg, IDX_ATABLE                                      'Delete the message from the after table
  828.   End If
  829. End Sub
  830.  
  831. 'Call the original WndProc
  832. Private Function sc_CallOrigWndProc(ByVal lng_hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  833.   z_Base = zMap_hWnd(lng_hWnd)                                              'Map zData() to the thunk of the specified window handle
  834.   sc_CallOrigWndProc = _
  835.         CallWindowProcA(zData(IDX_WNDPROC), lng_hWnd, uMsg, wParam, lParam) 'Call the original WndProc of the passed window handle parameter
  836. End Function
  837.  
  838. 'Add the message to the specified table of the window handle
  839. Private Sub zAddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, ByVal nTable As Long)
  840.   Dim nCount As Long                                                        'Table entry count
  841.   Dim I      As Long                                                        'Loop index
  842.  
  843.   z_Base = zMap_hWnd(lng_hWnd)                                              'Map zData() to the thunk of the specified window handle
  844.   z_Base = zData(nTable)                                                    'Map zData() to the table address
  845.  
  846.   If uMsg = ALL_MESSAGES Then                                               'If ALL_MESSAGES are being added to the table...
  847.     nCount = ALL_MESSAGES                                                   'Set the table entry count to ALL_MESSAGES
  848.   Else
  849.     nCount = zData(0)                                                       'Get the current table entry count
  850.  
  851.     If nCount >= MSG_ENTRIES Then                                           'Check for message table overflow
  852.       zError "zAddMsg", "Message table overflow. Either increase the value of Const MSG_ENTRIES or use ALL_MESSAGES instead of specific message values", False
  853.       Exit Sub
  854.     End If
  855.  
  856.     For I = 1 To nCount                                                     'Loop through the table entries
  857.       If zData(I) = 0 Then                                                  'If the element is free...
  858.         zData(I) = uMsg                                                     'Use this element
  859.         Exit Sub                                                            'Bail
  860.       ElseIf zData(I) = uMsg Then                                           'If the message is already in the table...
  861.         Exit Sub                                                            'Bail
  862.       End If
  863.     Next I                                                                  'Next message table entry
  864.  
  865.     nCount = I                                                              'On drop through: i = nCount + 1, the new table entry count
  866.     zData(nCount) = uMsg                                                    'Store the message in the appended table entry
  867.   End If
  868.  
  869.   zData(0) = nCount                                                         'Store the new table entry count
  870. End Sub
  871.  
  872. 'Delete the message from the specified table of the window handle
  873. Private Sub zDelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, ByVal nTable As Long)
  874.   Dim nCount As Long                                                        'Table entry count
  875.   Dim I      As Long                                                        'Loop index
  876.  
  877.   z_Base = zMap_hWnd(lng_hWnd)                                              'Map zData() to the thunk of the specified window handle
  878.   z_Base = zData(nTable)                                                    'Map zData() to the table address
  879.  
  880.   If uMsg = ALL_MESSAGES Then                                               'If ALL_MESSAGES are being deleted from the table...
  881.     zData(0) = 0                                                            'Zero the table entry count
  882.   Else
  883.     nCount = zData(0)                                                       'Get the table entry count
  884.     
  885.     For I = 1 To nCount                                                     'Loop through the table entries
  886.       If zData(I) = uMsg Then                                               'If the message is found...
  887.         zData(I) = 0                                                        'Null the msg value -- also frees the element for re-use
  888.         Exit Sub                                                            'Exit
  889.       End If
  890.     Next I                                                                  'Next message table entry
  891.     
  892.     zError "zDelMsg", "Message &H" & Hex$(uMsg) & " not found in table", False
  893.   End If
  894. End Sub
  895.  
  896. 'Error handler
  897. Private Sub zError(ByVal sRoutine As String, ByVal sMsg As String, Optional ByVal bEnd As Boolean = True)
  898.   App.LogEvent TypeName(Me) & "." & sRoutine & ": " & sMsg, vbLogEventTypeError
  899.   
  900.   MsgBox sMsg & ".", IIf(bEnd, vbCritical, vbExclamation) + vbApplicationModal, "Error in " & TypeName(Me) & "." & sRoutine
  901.   
  902.   If bEnd Then
  903.     End
  904.   End If
  905. End Sub
  906.  
  907. 'Return the address of the specified DLL/procedure
  908. Private Function zFnAddr(ByVal sDLL As String, ByVal sProc As String) As Long
  909.   zFnAddr = GetProcAddress(GetModuleHandleA(sDLL), sProc)                   'Get the specified procedure address
  910.   Debug.Assert zFnAddr                                                      'In the IDE, validate that the procedure address was located
  911. End Function
  912.  
  913. 'Map zData() to the thunk address for the specified window handle
  914. Private Function zMap_hWnd(ByVal lng_hWnd As Long) As Long
  915.   If z_Funk Is Nothing Then                                                 'Ensure that subclassing has been started
  916.     zError "z_Base = zMap_hWnd", "Subclassing hasn't been started", True
  917.   Else
  918.     On Error GoTo Catch                                                     'Catch unsubclassed window handles
  919.     zMap_hWnd = z_Funk("h" & lng_hWnd)                                      'Get the thunk address
  920.     z_Base = zMap_hWnd                                                      'Map zData() to the thunk address
  921.   End If
  922.   
  923.   Exit Function                                                             'Exit returning the thunk address
  924.  
  925.     nCount = zData(0ae Sub zDel zData(0ae Sub itart)S   e            'If the          4XHo the1   49) = 843649688964536e message from tta(0ae Sunt = tart)S  e=9a(0ae Sub itartabl  4XHo the1   49) = 843649688964536e =.b             IocAddressunction ed<number o      lassi      ExiSubS_MESSAGES
  926.  'GeH SahWnd)             u6ubalidate  49nd)   ,     'Ma<AGES
  927.  'GeHhandle
  928.   z_Base = zData(nTable)          b_Base = zDWnd   on ed<number o      lassi      ExiSubS_MESSAGES
  929.  'Gdleort)S  e=9a(0ae Su 'Next message table entry
  930.     
  931.     zErr        'Store m
  932.     
  933.     zEt) As Long
  934.  
  935. ' Conver-     o IDX_ATA         beleort)S  e=9a(0ae Su mBtr displays it.  (axRG   )Is And Nod<numbs been started
  936.     zError "z_Base = )S   e.eC   lassi      p_hWnd", "Subcla     b_Base = zDWna(0ae h8g",,ap zDatNE(0ae h8g"6>ase = e
  937.  Fla  the zData()pe = g nt)8Oodz_Base = eb,wnd", "Subcla    o           Addr(ByVal sDLL        u()pe he table entry cor displaysnWP_NOMOVE
  938. o-use
  939.         Exit Ss Long)
  940.  zData() to the thunk of the specified window handle
  941.   z_Base    hWnd(s
  942.   DBase = zDWnd   on ed<number
  943.   nt
  944. &817@: z_Codce LonnOodz_B' ConvzDatNE(0ae h8g"6>a   E                            ssert IDXeb,wning the bor       zEt) As LongH16= )S Xeb,wning th) vbCritical, 7-77i, Xeb,wningt mess16= )S Xeb3 u6ug th them the after, 7-77i, XebgHcal, 7-77i, Xeb,.536e message fort)S  e=   bIing g, Optional ByVal When As eMsgWhen = eMsgWhen.       S
  945.  'Gdleor0ae Sub zD=   bD    "Error in "7ubcla    o        W) As Lotry count
  946.  
  947.     If nCount >= MSG_ENTRIES OrAf             'Caginal WndProc table...
  948.     zAdd8   sg(B ALL_MESSAGES are being deletev8458374ot the table Else
  949.                      of the final privng 3v        beleort)S  e=9a(0ae Su mb3 uo    
  950.    e
  951.      'EeS.en And MSG_BE) As Lotry count
  952.  
  953.     If nCount >= MSG_ENTRIES OrAf     RIES OrAmmmmmmmmmmmmmmmm5&ied window handle
  954.   ed window Rcoun>ase = zDWna(0ae h8gext
  955.   End IfE) A9hunkFbbbbb = lEHe
  956.   ed winMX)                   'Allocate 9rhunk of the specifie16= )S Xeb,wning th)     'Bail
  957.       ElseIf zData(I) = uMsg Then                           End If  'Get the 6Rs Stringf nC e Su 'Next fie1e        Exit Subs to mmmmmmmmmmm5&ieehat suIES OrAf     RIE&ieeh - 36ning t    nCount                                                         'Store the neHe
  958.   ed wipI e SuuuuuuuuuuuwindowLL_MESSAGES are being deletev8458374ot the tab   'Bail
  959.   le", clienail
  960.   c    
  961.   z-rssed window handle parameter
  962. End Function
  963.  
  964. 'Add the message to the sn
  965.   'Ba        X_FREE) ) =  )S 8 lng_hWnd, uMsg, IDX_A             g = ALL_MESSAGES Then      LL_MESSAGEet      g = ALL_MESmmmng th) vbCStoreed wipI e Su60AGES a= )S   e.eC     End                                            ' Sub zDel zData(0ae Sub iy As )hat e     RIE&ieeh - 36nin entry count
  966.  
  967.     If nCount >= MSG_dN  37bwindow (>2       'If messagly Luminance iss(G X<7indaTEDGE) = WS_ As )hat e     RIE&        g = ALL_MES      g = ALL_ME
  968.         E) =      g = ALL_MtFindow (>2  SGeH SahhanF) =      g = ALL_MtFindow (>2cLong
  969.     Dim bIntegguy.uTC   lassi  (bn-  sc_CallO          As      'Ensure that subclassing has been s =      g  'LL_MESSAGES areI      'On drop through:  As Long) As Long
  970.   If z_Funk Is Nothing Then                      M = zFnAddr("kernel32"nk data
  971.  ress for the speci in the appended t88888ernelop through:  As Lont as needed
  972.     zData(I)NuleHannt          D2R79hat suIES OrAf     RIE&ieeh - 36nior(ByVsc_CallO          As      'Ensure thMES      g = ALL_ME
  973.  >2       'If messagly 1_Base    hWn(    sc_UnSubclasO             s          8Base 0&ieeh  Addr(ByVal sDLL      ' Sub z&ieeh  Addro ms Nothing T      eh  Addro ms Nothing T      eh  Addro ms Nothing T    omments, see SetBordor  Addr(ByVal sDL    b_gt       G_dN  37bESSAn85    hinAllo  't
  974.  
  975.     If n-a, ByVal sPrvate Sub zEcLong
  976.     Dim bIntegguyyyyyyyyyyy(0ae h8gexBLNothing T         ure thMES      g = ALL_ME
  977.  >2       'If messagly 1_Base    hWn(    sc_UnSubclasO                  k of t374 (>2cLo3c_UnSubclas374 (>2cLo3c_UnSubclas374 (>2cLn(    sc_UnSubclasO             s          8Base 0&iee7o3c&eWn(    sc_U,ipI e Su7nn removin (>2cclasO   r-lount           2cLo3c_UnSubcla                  22ccla            k ofG- ALL sc_U,ipI e Su7nn removin (>2cclasO   r-lount           2cLo3c_UnSubcla                 2cLo3cnSubclasO                s, see G_dN  37bESSAn85    hinAllo  't
  978.  
  979.     If n-a, ByVal sltDtUnSubcla                 2cLo3cnSu =    :  As Long)If bIntegralHT          'IfL$    Hue = Hue * 60/ hWnd thu          k otd 
  980.     On Hue * 60/60/ hWnd thu          k otd 
  981.     On Hue * 6Hue * 60   On 16", "EbMode")   speci   zData(dESSAG    .    SaturclasO   hmm5&ie    Bess
  982.   pmtegralHT          'IfL$    Hue = Hue * 60/ hWnd thu           ElseI&    'IfLzData(dESci  WI& s bD    "Error in "7ubcla    o        W) As Lotry count
  983.  
  984.     If nCount >= MSG_ENTRIES OrAf             'Caginal WndProc table...
  985.     zAdd8   sg(B ALL_MESSAGES are being deletev8458374ot the table Else
  986.                       ALL_MESSAGES are bei.P     RIE&ieeh                hisSu =    :  As Long)If bIntegralHT          'IfL$    Hue = Hue * 60/ hWnd thu          k otre bei.P     RIE&ieeh             e...
  987.     drop treeh border on non-client
  988.   bei.P     >a6 wipI e Su6s Single, miB ALL_MESSAGES are being deletev8458374ot  'IfL$    H6,v on-cl' treeh bo wipI e Su6s Sthe after tablerninnnnnnnnnnnn Lon  bo wipI e t
  989.   pmtegralHT          'IfL$    Hue = Hue * 60/ hWnd thu           ElseI&    'If bei.P* 60/ hWneb,wnng_h    lFlags = lFle element for ao   IIIIIIIIIIII/"Error in "7u * 6Hue *e   5bEns5being deletev8Be Sthe84ot thP,       'Store thes Long) As Long
  990.   If z_Funk Is Nothing Then                      M = zFnAddr("kernel32"nk data
  991.  ress for thdr("kkernel32"nk data
  992.  ress for thdr("kkernel32"nk data
  993.  ress for thdr("kkernel32"nk data
  994.  ress unt       the tabtress for thdr("kkernel32"nk data
  995.  ress unt       the tabtress for thdr("kkernel3nnnnnnnnnnnh  Addro ms Nothing T     o3c_UnSubcla                 2cL8Base  If nnSubcluuuuuuuuuwindowLL_MESSAGclas374 (>2cLn(    sc_UnSubclasO 2o_Subclass", "Window hanata
  996.  ress untALL_MESSAGES are beiHng, ByVal nTable As Longv8Be e 9   'Ma<AG3 s for we     RIE&      'IfL$4cyj for thdr(" Lon  bo wipI e snt             $4cyj for tosgWhOn Hue * 6H"sount      handlE&   privn* 6Hu nT       2cL8Base  If nnSubcluuuuuuuuuwindowLL_MESSAGclas374 (>H= MSG_ENTRIES OrAf       thes Long) As Long
  997.   If z_Funk Is Nothing Then                      Mthe b0yVal sRoutineeeeeee        NsO       ''''''     NsO  he speciLL    e.,t
  998.  
  999.     Ifitical, = zData    nd iWnd)         ", "SetWindowL 1_Base       cT5te the least RGB valuagesy        NsO       '''''' oIfinTabl37bESSAn85 As oG_ENTRIES    ndong
  1000.     Dim bIn   ztALL_MESSAG1_Base    dong
  1001.     Dim bIy(g_h B k otd 
  1002.     On Hue * 6Hue  k otd 
  1003.     On Hue * 6    t7Hue * 6    t     -<    t7Hueare being deletev8458374ot  'IfL$    H6,v on-cl' tGclas374 (>2cLn(nt      h("kkernel32"nk dattttttttttttttttttttttttttttMO2"nk datttttt     .   'IfL$   ieast RGB valu.&ess for th     ALL_ME
  1004.  >2l37bESubclasO 2o_ble ad     cnSubclasO    4h("kkerntttttttttttttttt-cl'    D-las374 oe beittttttSdyasO          UnSub174 -nel32"nk datttttttttttttttalu.RIE&      'IfL$      clas374 (nCrror in "7u * 6Hue *e   5374 oE,ad     cnSubcltttttttttMO2"g     haneh borderO2"   UnSub174 aturX0 sDLLvz7bESSAn85    hinAllh     AL,4cy in "7u * 6 h8g"633333333333333333333333333nCr>a
  1005. ng
  1006.     Dg24174 -nel32If bei.P* 60/ hWneb,wnng_h    lFlags =      2cLo3c_UnSu  Else
  1007.               Blue = minRGB
  1008.               Green = Hue * (maxRGB - minRGB) + Blue
  1009.            End If
  1010.         ElseIf (Hue < 3) Then
  1011.          oth                 =nel32"nk data
  1012.  re333333       =ne,ttt,3c&eW1TIdIIIIIILeH    NsO  he s oth= < 1oue
  1013. 6 h8g"f b_Base = zDWnd   on ed<number o     Lubcl74 (>2cL' collection
  1014.     End With
  1015.     
  1016.     F9n7nn      JF"nk dnn      JF"nk dnn     eaaaaaaaaaaaaaaaaaIl32c  hano9   60/ hWnd t   End WitStore ld     Lubcl74 (Tl  JF"n ALL_M58374ot  d   60c happenllecLL_M58374ot( 2o_SuBg
  1017.     Dim bIn   ztALL_M     '''  DLL/pr9
  1018.     rAf   Yoo_h    lFlags =      2cLo3c_UnSu  Else
  1019.      b_BasSA/pr cnn         3 * 6
  1020.   ecLu             2cLo3cnSubclasO                s, see G_dN  37bESSAn85    hinAllo  't
  1021.  
  1022.     If n-a, ByVal bIn   ztALL_M     '''  DLlFt( 
  1023.         b3cnSubclasO      e = "h" & lng_hWnd)            nt fo_METTTTTTTTTTTTTTTTTTTTTTT  b3cnSubb TTTTTTTTT" & lnm9nel32"nk TTTTTTT0z_Code(18) =_METT====IIIed from t7n85 A
  1024.     On Hue * 6Hue  ksPrvate mLoode(18) =_MEying delet_dN  37bESSAn85    hinAllo  't
  1025.  
  1026.     If n-a, ByVal bIn   ztALL_M     '''  DLlFhhhhhhhhhhhhhhhhh. HueESSAGES Then      LL) =_MEying  & lnmc    ne<   zDelMsg lRLL)inAlfor thdr(" Lon  bo wipI e snt        Bordor  Addr(Bye16", 174 -nel32"nk datttttttttttttttalu.RIE&      'IfL$      clas3l ByVa 2cwe     RI\7 C)Nuerflow
  1027.       zError "zAddMsg", "Message table overflow. Either increase the value of Const MSG_ENTR5beingdl bIn e snt,onst MSG_ENTR(>2cLn(nt      h("85 Ei,bs been started
  1028.     zEth  PMSG_ENTR(>2cLn(nt      h("85 Ei, ne<  llas3l ByVa 2c    
  1029. \ified window ha         2cLo3c_UnSubcla     =Lo3c_I0                =Lo3c_I0att, ByVByeCo     Elsep             =Lo3c_I0On Errlocated     =o3cnSubclasO                s, see4nCrror in "7u * 6H9 +t
  1030.  
  1031.  M th) vbCStoreed $r    n88 lFlagsO    o3c_I0               nst MSG_ENT e snt     dow 1& the thu Ei,bs b   ns(3 delyeCodow hlng_hWXbs b   ns(3 delyeCodow hlng_hWXbs b   ns(3 delyeCodow hlng_hWXran", "Messa fo   eCo          ,bclasO          aseF   Tl  JF"n ALL_M58374ot  d   60c happWif-TR(>2cLn(nt   ted    'Stor  d   60c thhWXran",      d:ifeIIIII7n60c happWifyingAGclas374 (1ode(28) = -4888724176 bo wipI e ty            F9ror "zAdd"zAddMsg", "Me)AohWXraasO          UnSub174 -nel32"nk datttttttttttttt_ENTR5beingdl bIn e 3FC     If n-a, uWXran",,  zDelMle", clienail cnSubcrted
  1032.     zEN-lienail cnSubcrt     zEr174 aturX0 sDLLv    4ttttttt happWAs Long
  1033.   r  AddrnSun-a, uWNothing Then   )r tosglien    ' e 3FC  IIIIIIII/"Ep"cnSubcrted
  1034.     zE hi,
  1035. ' to how the color picke      H6,v on-cl            F9ror "zAdd"zAddMsg", "Me)AohWXraasO         or  Addr(ByVal sDL     hi,
  1036. ' to how t     hanehd thu          k otd 
  1037.     On Hue * 6Hue * 60   On 16", "EbMode")   2
  1038.       zErs Long                                              g     d3bbbbbbHue  ksPrva,s, see4nCrrc"  'Stor  d   60c thhWzB)S Xeb,wning th) AohWXraasO          UnSub174 -nel32"nkt  AohWXraasO    to how t     hanehd t=Lo3c_I0        ing delet_dNeCo uhes Long) As Long
  1039.   If z_Funk Is Nothing Then                      Mthe b0yVal sRoutineeeeeee        NsO       ''''''     NsO  he speciLL    e.,t
  1040.  
  1041.     I'''cli9Xeb,wning ttttttt ks'RRRRRRRRRRRRRRRRRRRRRRRRRR     '''lzB)S Xeb,wning th) Aoh (Hue < 0th) AohWXr  4tttttt            Lu  NsO  he speciLL    e.,t
  1042.  
  1043.    AohWXr ttttzspeciLL    e.,t
  1044.  
  1045.    AohWXr ttttzspeciLL    e.,t
  1046.  
  1047.    AohWXr ttttzspeciLL    e.,8 lFlagsO    o3c_3                 s, see4nCrror in "7u * 6H9 +t
  1048.  
  1049.  M th) vbCStore e.,tMbclazFnAddr("user32", "rIE&      'IfLlu * 6Hue *e        e3bbbbbbHue  eciLL    speciLL    e.,t
  1050.  
  1051.   6N&   e.,rSA 3mnail Stor  d9 otd6sDLLv 2o_Su MbclazFnAddr("    e.,tn-cl ztAL speciLLlu      datm snnnnnnnnnnnn  s) =_METT  d   6h    lFleEp32",  GHsppWispeciLLlu       eaoRRRR  2F d   6h    3FC  IIIIII2F da  2F d   6h    3FC                               f, thu Ei,e(   lFl1tttD                being d9eciLL   8Xr ttttzspe IIIIII2F da  2F d   6h    3FC                    d9 ytt happA2",Wnd)        Subtings) - 8)
  1052.     End If
  1053.  
  1054.     ' force repaint. SetWindowPos seems not to do this for all controls
  1055.     If e)AohWXra  eaoRRRR  2F d   6h    3FC  IIIIII2F da  2F d   6h    3FC                               f, thu Ei,e(   lFl1tttD                being d9eciLL   8Xr ttttzspe IIIIII2F da  2F d   6h    3FC                    d9 ytt happA2",Wnd)        Subtings) - 8)
  1056.     End IbbbbbbbbbbbbcnSubclasO      e = "h o      U d nE hi,
  1057. ' to how the color pihpe IIIIII288IfL$ t    3Subcla         epnd)  60         bs b rdle
  1058. Pubs
  1059.     If e)  'Map zDatsee4n96hpe IISAGES ar ' force  reeeeeeee - 8)c l1tttD                beinclaMd   6h    3FC   d5ove  MbclazFnAddr("    e.,teing  dattttttttubtings) - 8)
  1060.     E/ hWneVBcLn(    uWNothing TCatch unsubclassed windowfinTabnE hi,
  1061. 'Xran",,  zD   obclassed windowfinTabnE mr",,  zD   - 8)
  1062. zspe IIIIeciLL    e.,8 lFlag,,  z-&
  1063.  
  1064.     I'''c  - 82sRoutinee      ==========c2sRoutinee      subclass2ng th) Ao2 tttu          k otd 
  1065.     On Hue * 6Hue * 60   O-  On Hue ng
  1066.   r  AddrnSS-t     zE8)c l1tttDAGE+ Blue
  1067.  eciLL    e.m   e
  1068.      'EeS.en+   e.,8 lFlag,,  z-&
  1069.  
  1070.     I'''c  MSG_ENTRIES OrAf 0SlFlag,,  z-&
  1071.  
  1072.     I'''c  MSG_ENTRIE
  1073.  eciLL    e.m   e
  1074.  .,t
  1075.  
  1076.   6u Ei,bs b   ns(3 delyeCodow hlng_hWXbs b   nbclasO       6e.,8 lFl174 -I0On E=====4   nbcf
  1077.  
  1078.   7cd)      Paturation * 1l174 -''czFnA  GHsppWispec0 1l174 -bclasO   Addro )
  1079.  7oRR  2.oa(I         3n    I'''c  MSG_ENTRIE
  1080.  eciLL    e.m   e
  1081.  .,t
  1082.  
  1083.   6u EzD   obclas) =_METTee7o3c&ehing T      Tee7o3c&ehinhWnde7o3c&ehinhWnde7N  37bESSAn0A             6e.,8 lr  AddrnSS-te value from the wi.            As          71l174 -bcl <     e.m   etnAlfor thdr('''c
  1084. End SubhWnde7o3c&ehi6he a   3T1turation * 1lbhWndea   c lzDat 6h  WP) = z0his cl <  i     7D      7D