home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 3_2004-2005.ISO / Data / Zips / Titlebar_B184924262005.psc / Duncan_TitleButton.ctl < prev    next >
Text File  |  2005-02-06  |  46KB  |  961 lines

  1. VERSION 5.00
  2. Begin VB.UserControl Duncan_TitleButton 
  3.    BackColor       =   &H008080FF&
  4.    ClientHeight    =   2535
  5.    ClientLeft      =   0
  6.    ClientTop       =   0
  7.    ClientWidth     =   4800
  8.    HasDC           =   0   'False
  9.    ScaleHeight     =   169
  10.    ScaleMode       =   3  'Pixel
  11.    ScaleWidth      =   320
  12.    ToolboxBitmap   =   "Duncan_TitleButton.ctx":0000
  13.    Begin VB.PictureBox picButton 
  14.       Appearance      =   0  'Flat
  15.       BackColor       =   &H80000005&
  16.       BorderStyle     =   0  'None
  17.       ForeColor       =   &H80000008&
  18.       Height          =   855
  19.       Left            =   480
  20.       ScaleHeight     =   855
  21.       ScaleWidth      =   975
  22.       TabIndex        =   0
  23.       Top             =   480
  24.       Width           =   975
  25.    End
  26.    Begin VB.Image picClassic 
  27.       Height          =   210
  28.       Index           =   3
  29.       Left            =   4200
  30.       Picture         =   "Duncan_TitleButton.ctx":0312
  31.       Top             =   1560
  32.       Width           =   240
  33.    End
  34.    Begin VB.Image picClassic 
  35.       Height          =   210
  36.       Index           =   0
  37.       Left            =   3120
  38.       Picture         =   "Duncan_TitleButton.ctx":05F6
  39.       Top             =   1560
  40.       Width           =   240
  41.    End
  42.    Begin VB.Image picSilver 
  43.       Height          =   315
  44.       Index           =   3
  45.       Left            =   4200
  46.       Picture         =   "Duncan_TitleButton.ctx":08DA
  47.       Top             =   1200
  48.       Width           =   315
  49.    End
  50.    Begin VB.Image picSilver 
  51.       Height          =   315
  52.       Index           =   2
  53.       Left            =   3840
  54.       Picture         =   "Duncan_TitleButton.ctx":0E5E
  55.       Top             =   1200
  56.       Width           =   315
  57.    End
  58.    Begin VB.Image picSilver 
  59.       Height          =   315
  60.       Index           =   1
  61.       Left            =   3480
  62.       Picture         =   "Duncan_TitleButton.ctx":13E2
  63.       Top             =   1200
  64.       Width           =   315
  65.    End
  66.    Begin VB.Image picSilver 
  67.       Height          =   315
  68.       Index           =   0
  69.       Left            =   3120
  70.       Picture         =   "Duncan_TitleButton.ctx":1966
  71.       Top             =   1200
  72.       Width           =   315
  73.    End
  74.    Begin VB.Image picOlive 
  75.       Height          =   315
  76.       Index           =   3
  77.       Left            =   4200
  78.       Picture         =   "Duncan_TitleButton.ctx":1EEA
  79.       Top             =   840
  80.       Width           =   315
  81.    End
  82.    Begin VB.Image picOlive 
  83.       Height          =   315
  84.       Index           =   2
  85.       Left            =   3840
  86.       Picture         =   "Duncan_TitleButton.ctx":246E
  87.       Top             =   840
  88.       Width           =   315
  89.    End
  90.    Begin VB.Image picOlive 
  91.       Height          =   315
  92.       Index           =   1
  93.       Left            =   3480
  94.       Picture         =   "Duncan_TitleButton.ctx":29F2
  95.       Top             =   840
  96.       Width           =   315
  97.    End
  98.    Begin VB.Image picOlive 
  99.       Height          =   315
  100.       Index           =   0
  101.       Left            =   3120
  102.       Picture         =   "Duncan_TitleButton.ctx":2F76
  103.       Top             =   840
  104.       Width           =   315
  105.    End
  106.    Begin VB.Image picSource 
  107.       Height          =   315
  108.       Index           =   3
  109.       Left            =   4200
  110.       Top             =   120
  111.       Width           =   315
  112.    End
  113.    Begin VB.Image picSource 
  114.       Height          =   315
  115.       Index           =   2
  116.       Left            =   3840
  117.       Top             =   120
  118.       Width           =   315
  119.    End
  120.    Begin VB.Image picSource 
  121.       Height          =   315
  122.       Index           =   1
  123.       Left            =   3480
  124.       Top             =   120
  125.       Width           =   315
  126.    End
  127.    Begin VB.Image picSource 
  128.       Height          =   315
  129.       Index           =   0
  130.       Left            =   3120
  131.       Top             =   120
  132.       Width           =   315
  133.    End
  134.    Begin VB.Image picBlue 
  135.       Height          =   315
  136.       Index           =   3
  137.       Left            =   4200
  138.       Picture         =   "Duncan_TitleButton.ctx":34FA
  139.       Top             =   480
  140.       Width           =   315
  141.    End
  142.    Begin VB.Image picBlue 
  143.       Height          =   315
  144.       Index           =   2
  145.       Left            =   3840
  146.       Picture         =   "Duncan_TitleButton.ctx":3A7E
  147.       Top             =   480
  148.       Width           =   315
  149.    End
  150.    Begin VB.Image picBlue 
  151.       Height          =   315
  152.       Index           =   1
  153.       Left            =   3480
  154.       Picture         =   "Duncan_TitleButton.ctx":4002
  155.       Top             =   480
  156.       Width           =   315
  157.    End
  158.    Begin VB.Image picBlue 
  159.       Height          =   315
  160.       Index           =   0
  161.       Left            =   3120
  162.       Picture         =   "Duncan_TitleButton.ctx":4586
  163.       Top             =   480
  164.       Width           =   315
  165.    End
  166. End
  167. Attribute VB_Name = "Duncan_TitleButton"
  168. Attribute VB_GlobalNameSpace = False
  169. Attribute VB_Creatable = True
  170. Attribute VB_PredeclaredId = False
  171. Attribute VB_Exposed = False
  172. Option Explicit
  173. 'What does this do?
  174. 'It adds a button to the caption bar of your form.
  175. 'You only need to add one per form.
  176.  
  177. 'Why?
  178. 'So you can have a "minimise to system tray" button
  179. 'designed so that everything is in one usercontrol - easy to "drop in" to new projects that way
  180.  
  181. 'How?
  182. 'subclasses the picturebox, the usercontrol.
  183. 'moves the usercontrol to the parent of the form
  184. 'and then positions it so that it
  185. 'looks like it is part of the original caption bar
  186.  
  187. 'Who ?
  188. 'Thanks to Paul Catton for his work on subclassing - sourced from planetsourcecode.com (54117)
  189. 'Thanks to ABSoftware for original idea, code and images - sourced from planetsourcecode.com (58679)
  190.  
  191. 'When?
  192. 'Last Updated : 5 feb 2005
  193.  
  194. 'Testing?
  195. 'Has only been tested on an XP machine
  196.  
  197. 'To do?
  198. '1) theme change is ok for windows themes and classic view but is no good on custom colours in classic view. should ideally change the classic button to be drawn from scratch
  199. '2) limit to only one copy on the form at any time - cant figure out how
  200. '3) at random times the frame of the parent can change. havent yet pinned this down
  201.  
  202. '======================================================================================================================================================
  203. 'MY DECLARES FOR THIS CONTROL
  204. '======================================================================================================================================================
  205. Private Type RECT
  206.     Left As Long
  207.     Top As Long
  208.     Right As Long
  209.     Bottom As Long
  210. End Type
  211. Private Type POINTAPI
  212.    X As Long
  213.    Y As Long
  214. End Type
  215.  
  216. Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
  217. Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
  218. Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
  219. Private Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long
  220. Private Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
  221. Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  222. Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal W As Long, ByVal H As Long, ByVal wFlags As Long) As Long
  223. 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
  224. Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long
  225.  
  226. 'so window does not appear in taskbar
  227. Private Const WS_EX_TOOLWINDOW As Long = &H80&
  228.  
  229. 'for moving the window
  230. Private Const HWND_TOP = 0
  231. Private Const SWP_NOMOVE As Long = &H2
  232. Private Const SWP_FRAMECHANGED = &H20
  233. Private Const SWP_SHOWWINDOW = &H40
  234. Private Const SWP_NOSIZE = &H1
  235.  
  236.  
  237. 'for getting windows dimensions
  238. Private Const GWL_STYLE = (-16)
  239. Private Const GWL_EXSTYLE = -20
  240. Private Const SM_CXFRAME = 32
  241. Private Const SM_CYCAPTION = 4
  242. Private Const SM_CXDLGFRAME = 7
  243.  
  244. 'These correspond to the index of the image to be shown
  245. Private Enum eIconStyle
  246.     ICON_INACTIVE = 0   ' Inactive icon
  247.     ICON_NORMAL = 1     ' Normal icon
  248.     ICON_HOT = 2        ' When the mouse is over the icon
  249.     ICON_MOUSEDOWN = 3  ' When the mouse is down and over the icon
  250. End Enum
  251.  
  252. Private m_hForm As Long
  253. Private m_Active As Boolean
  254.  
  255. Public Event Click()
  256.  
  257. '-----------------------
  258. 'DECLARATIONS FOR THEMES
  259. '-----------------------
  260. Private Declare Function OpenThemeData Lib "uxtheme.dll" _
  261.    (ByVal hwnd As Long, ByVal pszClassList As Long) As Long
  262. Private Declare Function CloseThemeData Lib "uxtheme.dll" _
  263.    (ByVal hTheme As Long) As Long
  264.  
  265. Private Declare Function GetCurrentThemeName Lib "uxtheme.dll" ( _
  266.     ByVal pszThemeFileName As Long, _
  267.     ByVal dwMaxNameChars As Long, _
  268.     ByVal pszColorBuff As Long, _
  269.     ByVal cchMaxColorChars As Long, _
  270.     ByVal pszSizeBuff As Long, _
  271.     ByVal cchMaxSizeChars As Long _
  272.    ) As Long
  273.  
  274. Private Const THEME_BLUE = 1
  275. Private Const THEME_OLIVE = 2
  276. Private Const THEME_SILVER = 3
  277.  
  278.  
  279. '======================================================================================================================================================
  280. 'SUBCLASSING DECLARES
  281. '======================================================================================================================================================
  282. Private Enum eMsgWhen
  283.   MSG_AFTER = 1                                                                         'Message calls back after the original (previous) WndProc
  284.   MSG_BEFORE = 2                                                                        'Message calls back before the original (previous) WndProc
  285.   MSG_BEFORE_AND_AFTER = MSG_AFTER Or MSG_BEFORE                                        'Message calls back before and after the original (previous) WndProc
  286. End Enum
  287. Private Type tSubData                                                                   'Subclass data type
  288.   hwnd                               As Long                                            'Handle of the window being subclassed
  289.   nAddrSub                           As Long                                            'The address of our new WndProc (allocated memory).
  290.   nAddrOrig                          As Long                                            'The address of the pre-existing WndProc
  291.   nMsgCntA                           As Long                                            'Msg after table entry count
  292.   nMsgCntB                           As Long                                            'Msg before table entry count
  293.   aMsgTblA()                         As Long                                            'Msg after table array
  294.   aMsgTblB()                         As Long                                            'Msg Before table array
  295. End Type
  296. Private sc_aSubData()                As tSubData                                        'Subclass data array
  297. Private Const ALL_MESSAGES           As Long = -1                                       'All messages added or deleted
  298. Private Const GMEM_FIXED             As Long = 0                                        'Fixed memory GlobalAlloc flag
  299. Private Const GWL_WNDPROC            As Long = -4                                       'Get/SetWindow offset to the WndProc procedure address
  300. Private Const PATCH_04               As Long = 88                                       'Table B (before) address patch offset
  301. Private Const PATCH_05               As Long = 93                                       'Table B (before) entry count patch offset
  302. Private Const PATCH_08               As Long = 132                                      'Table A (after) address patch offset
  303. Private Const PATCH_09               As Long = 137                                      'Table A (after) entry count patch offset
  304. Private Declare Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal Length As Long)
  305. Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
  306. Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
  307. Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
  308. Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
  309. Private Declare Function SetWindowLongA Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  310. 'Window Messages
  311. Private Const WM_MOVE As Long = &H3
  312. Private Const WM_SIZING As Long = &H214
  313. Private Const WM_EXITSIZEMOVE As Long = &H232
  314. Private Const WM_NCPAINT As Long = &H85
  315. Private Const WM_SHOWWINDOW As Long = &H18
  316. Private Const WM_ACTIVATE As Long = &H6
  317. Private Const WM_MOUSELEAVE As Long = &H2A3
  318. Private Const WM_MOUSEHOVER As Long = &H2A1
  319. Private Const WM_THEMECHANGED As Long = &H31A
  320.  
  321. '//Mouse tracking declares
  322. Private Declare Function TrackMouseEvent Lib "user32" (lpEventTrack As TRACKMOUSEEVENT_STRUCT) As Long
  323. Private Enum TRACKMOUSEEVENT_FLAGS
  324.     TME_HOVER = &H1&
  325.     TME_LEAVE = &H2&
  326.     TME_QUERY = &H40000000
  327.     TME_CANCEL = &H80000000
  328. End Enum
  329. Private Type TRACKMOUSEEVENT_STRUCT
  330.     cbSize                              As Long
  331.     dwFlags                             As TRACKMOUSEEVENT_FLAGS
  332.     hwndTrack                           As Long
  333.     dwHoverTime                         As Long
  334. End Type
  335.  
  336. 'Subclass handler
  337. 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)
  338. Attribute zSubclass_Proc.VB_MemberFlags = "40"
  339. 'THIS MUST BE THE FIRST PUBLIC ROUTINE IN THIS FILE.
  340. 'That includes public properties also
  341.     Dim X As Long
  342.     Dim Y As Long
  343.     
  344.     Select Case lng_hWnd
  345.     Case m_hForm  'messages sent to the form
  346.         Select Case uMsg
  347.           Case WM_NCPAINT
  348.               SetButtonPosition SWP_NOMOVE
  349.           Case WM_MOVE, WM_SIZING
  350.               SetButtonPosition
  351.           Case WM_SHOWWINDOW:
  352.               'put the button on the forms titlebar
  353.                'lParam = 0 indicates that the message originated from a ShowWindow call
  354.                 If lParam = 0 And wParam = 0 Then 'being hidden
  355.                    'return control of the UC to the form
  356.                    Call SetParent(UserControl.hwnd, m_hForm)
  357.                    Debug.Print "window parent reset to form"
  358.                    ShowStyles
  359.                 ElseIf lParam = 0 And wParam = 1 Then 'being shown
  360.                    'set window to have toolbar properties
  361.                    'this prevents it showing in the taskbar
  362.                    Call SetWindowLong(UserControl.hwnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW)
  363.                    'move the control out of the form
  364.                    Call SetParent(UserControl.hwnd, GetParent(m_hForm))
  365.                    Debug.Print "window set to formparent"
  366.                    ShowStyles
  367.                    'set starting position
  368.                    'SetButtonPosition
  369.                 End If
  370.           Case WM_ACTIVATE
  371.               If wParam Then  '----------------------------------- Activated
  372.                   m_Active = True
  373.                   SetButton ICON_NORMAL
  374.               Else            '----------------------------------- Deactivated
  375.                   m_Active = False
  376.                   SetButton ICON_INACTIVE
  377.               End If
  378.           Case WM_THEMECHANGED
  379.               AlignButtonsToTheme
  380.               'change to same button in new theme
  381.               X = picButton.Tag
  382.               picButton.Tag = ""
  383.               SetButton X
  384.         End Select
  385.     Case picButton.hwnd
  386.         If uMsg = WM_MOUSELEAVE Then
  387.             If m_Active Then
  388.                 SetButton ICON_NORMAL
  389.             Else
  390.                 SetButton ICON_INACTIVE
  391.             End If
  392.         End If
  393.     End Select
  394. End Sub
  395. '======================================================================================================================================================
  396. 'Functions
  397. '======================================================================================================================================================
  398. '---------
  399. 'PICBUTTON
  400. '---------
  401. Private Sub picButton_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  402.     'respond to movements
  403.     If Button Then
  404.         If X > picButton.ScaleLeft And _
  405.            X < picButton.ScaleWidth And _
  406.            Y > picButton.ScaleTop And _
  407.            Y < picButton.ScaleHeight Then
  408.             SetButton ICON_MOUSEDOWN
  409.         Else
  410.             If m_Active Then
  411.                 SetButton ICON_NORMAL
  412.             Else
  413.                 SetButton ICON_INACTIVE
  414.             End If
  415.         End If
  416.     Else
  417.         'make sure the tool tips are in sync
  418.         If picButton.ToolTipText <> UserControl.Extender.ToolTipText Then
  419.             picButton.ToolTipText = UserControl.Extender.ToolTipText
  420.         End If
  421.         'make sure hot button is showing
  422.         SetButton ICON_HOT
  423.     End If
  424.     
  425.     Call TrackMouseLeave
  426. End Sub
  427. Private Sub picButton_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  428.    If Button = vbLeftButton Then
  429.       SetButton ICON_MOUSEDOWN
  430.    End If
  431.    
  432.    UserControl.Parent.SetFocus
  433. End Sub
  434.  
  435. Private Sub picButton_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
  436.     If Button = vbLeftButton Then
  437.         If X > picButton.ScaleLeft And _
  438.            X < picButton.ScaleWidth And _
  439.            Y > picButton.ScaleTop And _
  440.            Y < picButton.ScaleHeight Then
  441.             'the mouse was UP inside the control
  442.             RaiseEvent Click
  443.         End If
  444.         SetButton ICON_NORMAL
  445.     End If
  446.  
  447. End Sub
  448.  
  449. '-----------------
  450. 'PRIVATE FUNCTIONS
  451. '-----------------
  452. Private Function ShowStyles()
  453.  
  454.     Debug.Print "Window = Form " & m_hForm
  455.     Debug.Print "GWL_STYLE = " & GetWindowLong(m_hForm, GWL_STYLE)
  456.     Debug.Print "GWL_EXSTYLE = " & GetWindowLong(m_hForm, GWL_EXSTYLE)
  457.  
  458.     Debug.Print "Window = Usercontrol " & UserControl.hwnd
  459.     Debug.Print "GWL_STYLE = " & GetWindowLong(UserControl.hwnd, GWL_STYLE)
  460.     Debug.Print "GWL_EXSTYLE = " & GetWindowLong(UserControl.hwnd, GWL_EXSTYLE)
  461.  
  462. End Function
  463. Private Sub SetButtonPosition(Optional lFlag As Long = SWP_FRAMECHANGED)
  464.     'works out where on the screen the button should be placed
  465.     'SWP_NOMOVE
  466.     'SWP_FRAMECHANGED
  467.     Dim R As RECT
  468.     Dim X As Long
  469.     Dim Y As Long
  470.     Dim CX As Long
  471.     Dim CY As Long
  472.     Dim lStyle As Long
  473.     
  474.     'First find out where the form is
  475.     GetWindowRect m_hForm, R
  476.     'establish the size of the caption
  477.     CY = GetSystemMetrics(SM_CYCAPTION)
  478.     lStyle = GetWindowLong(m_hForm, GWL_STYLE)
  479.     Select Case lStyle And &H80
  480.         Case &H80:       CX = GetSystemMetrics(SM_CXDLGFRAME)
  481.         Case Else:       CX = GetSystemMetrics(SM_CXFRAME)
  482.     End Select
  483.     'crop back our rectangle to exclude borders
  484.     R.Left = R.Left + CX
  485.     R.Right = R.Right
  486.     R.Top = R.Top + CX
  487.     R.Bottom = (R.Top + CY) - 1
  488.     'R should now represent the caption bar
  489.     'Debug.Print R.Top & "," & R.Bottom & "-" & R.Left & "," & R.Right
  490.     'calc positioning
  491.     X = R.Right - ((4 * (picButton.Width - 1)) + (3 * CX))
  492.     Y = R.Top + ((R.Bottom - R.Top) - (picButton.Height - 1)) / 2
  493.     'move window
  494.     Call SetWindowPos(UserControl.hwnd, HWND_TOP, X, Y, picButton.Width - 1, picButton.Height - 1, lFlag)
  495.  
  496. End Sub
  497.  
  498. Private Sub SetButton(iIndex As eIconStyle)
  499.     'changes what button is being shown
  500.     If picButton.Tag <> CStr(iIndex) Then
  501.         Set picButton.Picture = picSource(iIndex).Picture
  502.         picButton.Tag = CStr(iIndex)
  503.     End If
  504. End Sub
  505.  
  506. Private Sub TrackMouseLeave()
  507.     'Starts tracking the mouse
  508.     'When the mouse leaves the control the WM_MOUSELEAVE message will be sent
  509.     'Doesnt work for transparent windows :(
  510.     On Error GoTo Errs
  511.     Dim tme As TRACKMOUSEEVENT_STRUCT
  512.     With tme
  513.         .cbSize = Len(tme)
  514.         .dwFlags = TME_LEAVE    'Or TME_HOVER
  515.         .hwndTrack = picButton.hwnd
  516.         '.dwHoverTime = HOVER_DEFAULT
  517.     End With
  518.     Call TrackMouseEvent(tme) '---- Track the mouse leaving the indicated window via subclassing
  519. Errs:
  520. End Sub
  521.  
  522. Private Function GetCurrentTheme(hwnd As Long) As Long
  523.     'returns what Theme is currently being used by the OS
  524.     On Error GoTo Out
  525.     Dim hTheme As Long
  526.     Dim sThemeFile As String, sColorName As String
  527.     Dim lPtrThemeFile As Long
  528.     Dim lPtrColorName As Long
  529.     Dim hRes As Long
  530.     
  531.     hTheme = OpenThemeData(hwnd, StrPtr("BUTTON"))
  532.    
  533.     If Not (hTheme = 0) Then
  534.         ReDim bThemeFile(0 To 260 * 2) As Byte
  535.         lPtrThemeFile = VarPtr(bThemeFile(0))
  536.         ReDim bColorName(0 To 260 * 2) As Byte
  537.         lPtrColorName = VarPtr(bColorName(0))
  538.         hRes = GetCurrentThemeName(lPtrThemeFile, 260, lPtrColorName, 260, 0, 0)
  539.     
  540.         sThemeFile = bThemeFile
  541.         If InStr(LCase(sThemeFile), "luna.msstyles") Then
  542.             sColorName = bColorName
  543.             If InStr(LCase(sColorName), "normalcolor") Then
  544.                 GetCurrentTheme = THEME_BLUE
  545.             End If
  546.             If InStr(LCase(sColorName), "homestead") Then
  547.                 GetCurrentTheme = THEME_OLIVE
  548.             End If
  549.             If InStr(LCase(sColorName), "metallic") Then
  550.                 GetCurrentTheme = THEME_SILVER
  551.             End If
  552.         End If
  553.       
  554.         CloseThemeData hTheme
  555.     End If
  556. Out:
  557.  
  558. End Function
  559.  
  560. '------------
  561. 'USER CONTROL
  562. '------------
  563. Private Sub UserControl_Initialize()
  564.     'position picturebox
  565.         picButton.Left = 0
  566.         picButton.Top = 0
  567.     'sync buttons to theme
  568.         AlignButtonsToTheme
  569.     'Put a picture in the box
  570.         SetButton ICON_NORMAL
  571.         
  572. End Sub
  573. Private Sub AlignButtonsToTheme()
  574.     'copy the appropriate button into picSource to represent the Theme being used
  575.     Dim RGN As Long
  576.     Dim I As Long
  577.     Dim Theme As Long
  578.     
  579.     'what theme?
  580.     Theme = GetCurrentTheme(UserControl.hwnd)
  581.     
  582.     'Apply buttons
  583.     Select Case Theme
  584.     Case THEME_BLUE
  585.         For I = 0 To 3
  586.             picSource(I).Picture = picBlue(I).Picture
  587.         Next
  588.     Case THEME_OLIVE
  589.         For I = 0 To 3
  590.             picSource(I).Picture = picOlive(I).Picture
  591.         Next
  592.     Case THEME_SILVER
  593.         For I = 0 To 3
  594.             picSource(I).Picture = picSilver(I).Picture
  595.         Next
  596.     Case Else
  597.         For I = 0 To 2
  598.             picSource(I).Picture = picClassic(0).Picture
  599.         Next
  600.         picSource(3).Picture = picClassic(3).Picture
  601.     End Select
  602.     
  603.     picButton.Width = picSource(0).Width + 1
  604.     picButton.Height = picSource(0).Height + 1
  605.     
  606.     'Apply region to usercontrol so that the button has correct shape
  607.     If Theme > 0 Then
  608.         'Initialise the picturebox to display a rounded button
  609.         'Create the region for the round rectangle
  610.         RGN = CreateRoundRectRgn(0, 0, picButton.Width, picButton.Width, 2, 2)
  611.         'Apply the region
  612.         SetWindowRgn UserControl.hwnd, RGN, True
  613.     Else
  614.         'Apply a blank region - will reset display area to "show all"
  615.         'which is what we want for the square "classic view" buttons
  616.         SetWindowRgn UserControl.hwnd, RGN, True
  617.     End If
  618.  
  619. End Sub
  620.  
  621. Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
  622.     Dim retval As Long
  623.     
  624.     If Ambient.UserMode Then
  625.         'store window handles - needed later to return ownership
  626.         m_hForm = UserControl.Parent.hwnd
  627.         
  628.         'Install Subclassing
  629.         Call Subclass_Start(m_hForm)
  630.         Call Subclass_AddMsg(m_hForm, WM_NCPAINT, MSG_AFTER)
  631.         Call Subclass_AddMsg(m_hForm, WM_MOVE, MSG_AFTER)
  632.         Call Subclass_AddMsg(m_hForm, WM_SIZING, MSG_AFTER)
  633.         Call Subclass_AddMsg(m_hForm, WM_SHOWWINDOW, MSG_AFTER)
  634.         Call Subclass_AddMsg(m_hForm, WM_ACTIVATE, MSG_AFTER)
  635.         Call Subclass_AddMsg(m_hForm, WM_THEMECHANGED, MSG_AFTER)
  636.         
  637.         Call Subclass_Start(picButton.hwnd)
  638.         Call Subclass_AddMsg(picButton.hwnd, WM_MOUSELEAVE, MSG_AFTER)
  639.         'Call Subclass_AddMsg(picButton.hwnd, WM_MOUSEHOVER, MSG_AFTER)
  640.     End If
  641. End Sub
  642.  
  643. Private Sub UserControl_Resize()
  644.     If Not Ambient.UserMode Then
  645.         'make control tidy when designing
  646.         UserControl.Width = picButton.Width * Screen.TwipsPerPixelX
  647.         UserControl.Height = picButton.Height * Screen.TwipsPerPixelY
  648.     End If
  649. End Sub
  650.  
  651.  
  652. Private Sub UserControl_InitProperties()
  653. 'Trying to limit it so that only one control can be on a form at any time
  654. '    On Error Resume Next
  655. '    Dim C As Control
  656. '    For Each C In Parent.Controls
  657. '        If TypeOf C Is Duncan_TitleButton Then
  658. '            If C.Name = Extender.Name Then
  659. '                Set C = Nothing
  660. '                Debug.Print Extender.Name; " set to nothing"
  661. '            End If
  662. '        End If
  663. '    Next C
  664. End Sub
  665.  
  666. Private Sub UserControl_Terminate()
  667.     'unload subclassing
  668.     On Error GoTo Errs
  669.     If Ambient.UserMode Then Call Subclass_StopAll
  670. Errs:
  671. End Sub
  672.  
  673.  
  674. '========================================================================================
  675. 'Subclass routines below here - The programmer may call any of the following Subclass_??? routines
  676. '======================================================================================================================================================
  677. 'Add a message to the table of those that will invoke a callback. You should Subclass_Start first and then add the messages
  678. Private Sub Subclass_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  679. On Error GoTo Errs
  680. 'Parameters:
  681.   'lng_hWnd  - The handle of the window for which the uMsg is to be added to the callback table
  682.   'uMsg      - The message number that will invoke a callback. NB Can also be ALL_MESSAGES, ie all messages will callback
  683.   'When      - Whether the msg is to callback before, after or both with respect to the the default (previous) handler
  684.   With sc_aSubData(zIdx(lng_hWnd))
  685.     If When And eMsgWhen.MSG_BEFORE Then
  686.       Call zAddMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  687.     End If
  688.     If When And eMsgWhen.MSG_AFTER Then
  689.       Call zAddMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  690.     End If
  691.   End With
  692. Errs:
  693. End Sub
  694.  
  695. 'Delete a message from the table of those that will invoke a callback.
  696. Private Sub Subclass_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
  697. On Error GoTo Errs
  698.  
  699. 'Parameters:
  700.   'lng_hWnd  - The handle of the window for which the uMsg is to be removed from the callback table
  701.   'uMsg      - The message number that will be removed from the callback table. NB Can also be ALL_MESSAGES, ie all messages will callback
  702.   'When      - Whether the msg is to be removed from the before, after or both callback tables
  703.   With sc_aSubData(zIdx(lng_hWnd))
  704.     If When And eMsgWhen.MSG_BEFORE Then
  705.       Call zDelMsg(uMsg, .aMsgTblB, .nMsgCntB, eMsgWhen.MSG_BEFORE, .nAddrSub)
  706.     End If
  707.     If When And eMsgWhen.MSG_AFTER Then
  708.       Call zDelMsg(uMsg, .aMsgTblA, .nMsgCntA, eMsgWhen.MSG_AFTER, .nAddrSub)
  709.     End If
  710.   End With
  711. Errs:
  712. End Sub
  713.  
  714. 'Return whether we're running in the IDE.
  715. Private Function Subclass_InIDE() As Boolean
  716.   Debug.Assert zSetTrue(Subclass_InIDE)
  717. End Function
  718.  
  719. 'Start subclassing the passed window handle
  720. Private Function Subclass_Start(ByVal lng_hWnd As Long) As Long
  721. On Error GoTo Errs
  722. 'Parameters:
  723.   'lng_hWnd  - The handle of the window to be subclassed
  724. 'Returns;
  725.   'The sc_aSubData() index
  726.   Const CODE_LEN              As Long = 200                                             'Length of the machine code in bytes
  727.   Const FUNC_CWP              As String = "CallWindowProcA"                             'We use CallWindowProc to call the original WndProc
  728.   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
  729.   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
  730.   Const MOD_USER              As String = "user32"                                      'Location of the SetWindowLongA & CallWindowProc functions
  731.   Const MOD_VBA5              As String = "vba5"                                        'Location of the EbMode function if running VB5
  732.   Const MOD_VBA6              As String = "vba6"                                        'Location of the EbMode function if running VB6
  733.   Const PATCH_01              As Long = 18                                              'Code buffer offset to the location of the relative address to EbMode
  734.   Const PATCH_02              As Long = 68                                              'Address of the previous WndProc
  735.   Const PATCH_03              As Long = 78                                              'Relative address of SetWindowsLong
  736.   Const PATCH_06              As Long = 116                                             'Address of the previous WndProc
  737.   Const PATCH_07              As Long = 121                                             'Relative address of CallWindowProc
  738.   Const PATCH_0A              As Long = 186                                             'Address of the owner object
  739.   Static aBuf(1 To CODE_LEN)  As Byte                                                   'Static code buffer byte array
  740.   Static pCWP                 As Long                                                   'Address of the CallWindowsProc
  741.   Static pEbMode              As Long                                                   'Address of the EbMode IDE break/stop/running function
  742.   Static pSWL                 As Long                                                   'Address of the SetWindowsLong function
  743.   Dim I                       As Long                                                   'Loop index
  744.   Dim j                       As Long                                                   'Loop index
  745.   Dim nSubIdx                 As Long                                                   'Subclass data index
  746.   Dim sHex                    As String                                                 'Hex code string
  747.   
  748. 'If it's the first time through here..
  749.   If aBuf(1) = 0 Then
  750.   
  751. 'The hex pair machine code representation.
  752.     sHex = "5589E583C4F85731C08945FC8945F8EB0EE80000000083F802742185C07424E830000000837DF800750AE838000000E84D00" & _
  753.            "00005F8B45FCC9C21000E826000000EBF168000000006AFCFF7508E800000000EBE031D24ABF00000000B900000000E82D00" & _
  754.            "0000C3FF7514FF7510FF750CFF75086800000000E8000000008945FCC331D2BF00000000B900000000E801000000C3E33209" & _
  755.            "C978078B450CF2AF75278D4514508D4510508D450C508D4508508D45FC508D45F85052B800000000508B00FF90A4070000C3"
  756.  
  757. 'Convert the string from hex pairs to bytes and store in the static machine code buffer
  758.     I = 1
  759.     Do While j < CODE_LEN
  760.       j = j + 1
  761.       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
  762.       I = I + 2
  763.     Loop                                                                                'Next pair of hex characters
  764.     
  765. 'Get API function addresses
  766.     If Subclass_InIDE Then                                                              'If we're running in the VB IDE
  767.       aBuf(16) = &H90                                                                   'Patch the code buffer to enable the IDE state code
  768.       aBuf(17) = &H90                                                                   'Patch the code buffer to enable the IDE state code
  769.       pEbMode = zAddrFunc(MOD_VBA6, FUNC_EBM)                                           'Get the address of EbMode in vba6.dll
  770.       If pEbMode = 0 Then                                                               'Found?
  771.         pEbMode = zAddrFunc(MOD_VBA5, FUNC_EBM)                                         'VB5 perhaps
  772.       End If
  773.     End If
  774.     
  775.     pCWP = zAddrFunc(MOD_USER, FUNC_CWP)                                                'Get the address of the CallWindowsProc function
  776.     pSWL = zAddrFunc(MOD_USER, FUNC_SWL)                                                'Get the address of the SetWindowLongA function
  777.     ReDim sc_aSubData(0 To 0) As tSubData                                               'Create the first sc_aSubData element
  778.   Else
  779.     nSubIdx = zIdx(lng_hWnd, True)
  780.     If nSubIdx = -1 Then                                                                'If an sc_aSubData element isn't being re-cycled
  781.       nSubIdx = UBound(sc_aSubData()) + 1                                               'Calculate the next element
  782.       ReDim Preserve sc_aSubData(0 To nSubIdx) As tSubData                              'Create a new sc_aSubData element
  783.     End If
  784.     
  785.     Subclass_Start = nSubIdx
  786.   End If
  787.  
  788.   With sc_aSubData(nSubIdx)
  789.     .hwnd = lng_hWnd                                                                    'Store the hWnd
  790.     .nAddrSub = GlobalAlloc(GMEM_FIXED, CODE_LEN)                                       'Allocate memory for the machine code WndProc
  791.     .nAddrOrig = SetWindowLongA(.hwnd, GWL_WNDPROC, .nAddrSub)                          'Set our WndProc in place
  792.     Call RtlMoveMemory(ByVal .nAddrSub, aBuf(1), CODE_LEN)                              'Copy the machine code from the static byte array to the code array in sc_aSubData
  793.     Call zPatchRel(.nAddrSub, PATCH_01, pEbMode)                                        'Patch the relative address to the VBA EbMode api function, whether we need to not.. hardly worth testing
  794.     Call zPatchVal(.nAddrSub, PATCH_02, .nAddrOrig)                                     'Original WndProc address for CallWindowProc, call the original WndProc
  795.     Call zPatchRel(.nAddrSub, PATCH_03, pSWL)                                           'Patch the relative address of the SetWindowLongA api function
  796.     Call zPatchVal(.nAddrSub, PATCH_06, .nAddrOrig)                                     'Original WndProc address for SetWindowLongA, unsubclass on IDE stop
  797.     Call zPatchRel(.nAddrSub, PATCH_07, pCWP)                                           'Patch the relative address of the CallWindowProc api function
  798.     Call zPatchVal(.nAddrSub, PATCH_0A, ObjPtr(Me))                                     'Patch the address of this object instance into the static machine code buffer
  799.   End With
  800. Errs:
  801. End Function
  802.  
  803. 'Stop all subclassing
  804. Private Sub Subclass_StopAll()
  805. On Error GoTo Errs
  806.   Dim I As Long
  807.   
  808.   I = UBound(sc_aSubData())                                                             'Get the upper bound of the subclass data array
  809.   Do While I >= 0                                                                       'Iterate through each element
  810.     With sc_aSubData(I)
  811.       If .hwnd <> 0 Then                                                                'If not previously Subclass_Stop'd
  812.         Call Subclass_Stop(.hwnd)                                                       'Subclass_Stop
  813.       End If
  814.     End With
  815.     I = I - 1                                                                           'Next element
  816.   Loop
  817. Errs:
  818. End Sub
  819.  
  820. 'Stop subclassing the passed window handle
  821. Private Sub Subclass_Stop(ByVal lng_hWnd As Long)
  822. On Error GoTo Errs
  823. 'Parameters:
  824.   'lng_hWnd  - The handle of the window to stop being subclassed
  825.   With sc_aSubData(zIdx(lng_hWnd))
  826.     Call SetWindowLongA(.hwnd, GWL_WNDPROC, .nAddrOrig)                                 'Restore the original WndProc
  827.     Call zPatchVal(.nAddrSub, PATCH_05, 0)                                              'Patch the Table B entry count to ensure no further 'before' callbacks
  828.     Call zPatchVal(.nAddrSub, PATCH_09, 0)                                              'Patch the Table A entry count to ensure no further 'after' callbacks
  829.     Call GlobalFree(.nAddrSub)                                                          'Release the machine code memory
  830.     .hwnd = 0                                                                           'Mark the sc_aSubData element as available for re-use
  831.     .nMsgCntB = 0                                                                       'Clear the before table
  832.     .nMsgCntA = 0                                                                       'Clear the after table
  833.     Erase .aMsgTblB                                                                     'Erase the before table
  834.     Erase .aMsgTblA                                                                     'Erase the after table
  835.   End With
  836. Errs:
  837. End Sub
  838.  
  839. '=======================================================================================================
  840. 'These z??? routines are exclusively called by the Subclass_??? routines.
  841.  
  842. 'Worker sub for Subclass_AddMsg
  843. Private Sub zAddMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  844. On Error GoTo Errs
  845.   Dim nEntry  As Long                                                                   'Message table entry index
  846.   Dim nOff1   As Long                                                                   'Machine code buffer offset 1
  847.   Dim nOff2   As Long                                                                   'Machine code buffer offset 2
  848.   
  849.   If uMsg = ALL_MESSAGES Then                                                           'If all messages
  850.     nMsgCnt = ALL_MESSAGES                                                              'Indicates that all messages will callback
  851.   Else                                                                                  'Else a specific message number
  852.     Do While nEntry < nMsgCnt                                                           'For each existing entry. NB will skip if nMsgCnt = 0
  853.       nEntry = nEntry + 1
  854.       
  855.       If aMsgTbl(nEntry) = 0 Then                                                       'This msg table slot is a deleted entry
  856.         aMsgTbl(nEntry) = uMsg                                                          'Re-use this entry
  857.         Exit Sub                                                                        'Bail
  858.       ElseIf aMsgTbl(nEntry) = uMsg Then                                                'The msg is already in the table!
  859.         Exit Sub                                                                        'Bail
  860.       End If
  861.     Loop                                                                                'Next entry
  862.  
  863.     nMsgCnt = nMsgCnt + 1                                                               'New slot required, bump the table entry count
  864.     ReDim Preserve aMsgTbl(1 To nMsgCnt) As Long                                        'Bump the size of the table.
  865.     aMsgTbl(nMsgCnt) = uMsg                                                             'Store the message number in the table
  866.   End If
  867.  
  868.   If When = eMsgWhen.MSG_BEFORE Then                                                    'If before
  869.     nOff1 = PATCH_04                                                                    'Offset to the Before table
  870.     nOff2 = PATCH_05                                                                    'Offset to the Before table entry count
  871.   Else                                                                                  'Else after
  872.     nOff1 = PATCH_08                                                                    'Offset to the After table
  873.     nOff2 = PATCH_09                                                                    'Offset to the After table entry count
  874.   End If
  875.  
  876.   If uMsg <> ALL_MESSAGES Then
  877.     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.
  878.   End If
  879.   Call zPatchVal(nAddr, nOff2, nMsgCnt)                                                 'Patch the appropriate table entry count
  880. Errs:
  881. End Sub
  882.  
  883. 'Return the memory address of the passed function in the passed dll
  884. Private Function zAddrFunc(ByVal sDLL As String, ByVal sProc As String) As Long
  885.   zAddrFunc = GetProcAddress(GetModuleHandleA(sDLL), sProc)
  886.   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
  887. End Function
  888.  
  889. 'Worker sub for Subclass_DelMsg
  890. Private Sub zDelMsg(ByVal uMsg As Long, ByRef aMsgTbl() As Long, ByRef nMsgCnt As Long, ByVal When As eMsgWhen, ByVal nAddr As Long)
  891. On Error GoTo Errs
  892.   Dim nEntry As Long
  893.   
  894.   If uMsg = ALL_MESSAGES Then                                                           'If deleting all messages
  895.     nMsgCnt = 0                                                                         'Message count is now zero
  896.     If When = eMsgWhen.MSG_BEFORE Then                                                  'If before
  897.       nEntry = PATCH_05                                                                 'Patch the before table message count location
  898.     Else                                                                                'Else after
  899.       nEntry = PATCH_09                                                                 'Patch the after table message count location
  900.     End If
  901.     Call zPatchVal(nAddr, nEntry, 0)                                                    'Patch the table message count to zero
  902.   Else                                                                                  'Else deleteting a specific message
  903.     Do While nEntry < nMsgCnt                                                           'For each table entry
  904.       nEntry = nEntry + 1
  905.       If aMsgTbl(nEntry) = uMsg Then                                                    'If this entry is the message we wish to delete
  906.         aMsgTbl(nEntry) = 0                                                             'Mark the table slot as available
  907.         Exit Do                                                                         'Bail
  908.       End If
  909.     Loop                                                                                'Next entry
  910.   End If
  911. Errs:
  912. End Sub
  913.  
  914. 'Get the sc_aSubData() array index of the passed hWnd
  915. Private Function zIdx(ByVal lng_hWnd As Long, Optional ByVal bAdd As Boolean = False) As Long
  916. On Error GoTo Errs
  917. 'Get the upper bound of sc_aSubData() - If you get an error here, you're probably Subclass_AddMsg-ing before Subclass_Start
  918.   zIdx = UBound(sc_aSubData)
  919.   Do While zIdx >= 0                                                                    'Iterate through the existing sc_aSubData() elements
  920.     With sc_aSubData(zIdx)
  921.       If .hwnd = lng_hWnd Then                                                          'If the hWnd of this element is the one we're looking for
  922.         If Not bAdd Then                                                                'If we're searching not adding
  923.           Exit Function                                                                 'Found
  924.         End If
  925.       ElseIf .hwnd = 0 Then                                                             'If this an element marked for reuse.
  926.         If bAdd Then                                                                    'If we're adding
  927.           Exit Function                                                                 'Re-use it
  928.         End If
  929.       End If
  930.     End With
  931.     zIdx = zIdx - 1                                                                     'Decrement the index
  932.   Loop
  933.   
  934. '  If Not bAdd Then
  935. '    Debug.Assert False                                                                  'hWnd not found, programmer error
  936. '  End If
  937. Errs:
  938.  
  939. 'If we exit here, we're returning -1, no freed elements were found
  940. End Function
  941.  
  942. 'Patch the machine code buffer at the indicated offset with the relative address to the target address.
  943. Private Sub zPatchRel(ByVal nAddr As Long, ByVal nOffset As Long, ByVal nTargetAddr As Long)
  944.   Call RtlMoveMemory(ByVal nAddr + nOffset, nTargetAddr - nAddr - nOffset - 4, 4)
  945. End Sub
  946.  
  947. 'Patch the machine code buffer at the indicated offset with the passed value
  948. Private Sub zPatchVal(ByVal nAddr As Long, ByVal nOffset As Long, ByVal nValue As Long)
  949.   Call RtlMoveMemory(ByVal nAddr + nOffset, nValue, 4)
  950. End Sub
  951.  
  952. 'Worker function for Subclass_InIDE
  953. Private Function zSetTrue(ByRef bValue As Boolean) As Boolean
  954.   zSetTrue = True
  955.   bValue = True
  956. End Function
  957.  
  958. 'END Subclassing Code===================================================================================
  959.  
  960.  
  961.