VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "cFormEx"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

' ************************************************************************************************
'
'   [ WIP ] Work In Progress!
'
'   cFormEx - Form Extensions [ VB6 is not dead! ]
'
'   Extends VB's Forms with some useful events/properties & functions
'
'   v0.0.1 [ 24. 07. 2007. ] - pre-alpha version...
'   v0.0.2 [ 10. 09. 2007. ] - first beta version
'
'   by Aleksandar Ruzicic
'   based on SelfSub 2.1 by Paul Caton (without it this code would not be possible! THANKS Paul!)
'   for SysTray implementation used code from VB Acellerator and pscode (LaVolpe)
'
' ************************************************************************************************

Option Explicit

Public Enum TrayActionConstants
    taMouseMove = &H200                 ' cursor is moving over the tray icon
    taLeftButtonDown                    ' user pressed left mouse button
    taLeftButtonUp                      ' user released left mouse button
    taLeftButtonDblClick                ' user double-clicked the left mouse button
    taRightButtonDown                   ' user pressed right mouse button
    taRightButtonUp                     ' user released right mouse button
    taRightButtonDblClick               ' user double-clicked the right mouse button
End Enum

Public Enum BaloonActionConstants
    baShow = &H402                      ' baloon is displayed
    baHide                              ' baloon is hidden
    baTimeout                           ' baloon is hidden due to timeout
    baClick                             ' user clicked baloon
End Enum

Public Enum BalloonStyleConstants
   bsDefault = 0                        ' No icon
   bsInformation = 1                    ' An information icon.
   bsWarning = 2                        ' A warning icon.
   bsError = 3                          ' A error icon.
   bsSilent = &H10                      ' Do not play the associated sound.
End Enum

Public Enum AppCommandConstants
    acBrowserBackward = 1               ' Navigate backward.
    acBrowserForward                    ' Navigate forward.
    acBrowserRefresh                    ' Refresh page.
    acBrowserStop                       ' Stop download.
    acBrowserSearch                     ' Open search.
    acBrowserFavorites                  ' Open favorites.
    acBrowserHome                       ' Navigate home.
    acVolumeMute                        ' Mute the volume.
    acVolumeUp                          ' Raise the volume.
    acVolumeDown                        ' Lower the volume.
    acMediaNextTrack                    ' Go to next track.
    acMediaPreviousTrack                ' Go to previous track.
    acMediaStop                         ' Stop playback.
    acMediaPlayPause                    ' Play or pause playback.
    acLaunchMail                        ' Open mail.
    acLaunchMediaSelect                 ' Go to Media Select mode.
    acLaunchApp1                        ' Start App1.
    acLaunchApp2                        ' Start App2.
    acBassDown                          ' Decrease the bass.
    acBassBoost                         ' Toggle the bass boost off and on.
    acBassUp                            ' Increase the bass.
    acTrebleDown                        ' Decrease the treble.
    acTrebleUp                          ' Increase the treble.
    ' available on XP and up:
    acMicrophoneMute                    ' Mute the microphone.
    acMicrophoneVolumeDown              ' Decrease microphone volume.
    acMicrophoneVolumeUp                ' Increase microphone volume.
    acHelp                              ' Open the Help dialog.
    acFind                              ' Open the Find dialog.
    acNew                               ' Create a new window.
    acOpen                              ' Open a window.
    acClose                             ' Close the window (not the application).
    acSave                              ' Save current document.
    acPrint                             ' Print current document.
    acUndo                              ' Undo last action.
    acRedo                              ' Redo last action.
    acCopy                              ' Copy the selection.
    acCut                               ' Cut the selection.
    acPaste                             ' Paste.
    acReplyToMail                       ' Reply to a mail message.
    acForwardMail                       ' Forward a mail message.
    acSendMail                          ' Send a mail message.
    acSpellCheck                        ' Initiate a spell check.
    acDictateOrCommandControl           ' Toggles between two modes of speech input: dictation and command/control (giving commands to an application or accessing menus).
    acMicrophoneToggle                  ' Toggle the microphone.
    acCorrectionList                    ' Brings up the correction list when a word is incorrectly identified during speech input.
    ' available on XP SP1 and up:
    acMediaPlay                         ' Begin playing at the current position.
    acMediaPause                        ' Pause.
    acMediaRecord                       ' Begin recording the current stream.
    acMediaFastForward                  ' Increase the speed of stream playback.
    acMediaRewind                       ' Go backwards in a stream at a higher rate of speed.
    acMediaChannelUp                    ' Increment the channel value, for example, for a TV or radio tuner.
    acMediaChannelDown                  ' Decrement the channel value, for example, for a TV or radio tuner.
End Enum

Public Enum SpecialFolderConstants
    sfDesktop = &H0                     ' folder representing the Windows desktop, the root of the namespace.
    sfInternet = &H1                    ' virtual folder representing the Internet.
    sfStartMenuPrograms = &H2           ' directory that contains the user's program groups (which are themselves file system directories).
    sfControlPanel = &H3                ' folder containing icons for the Control Panel applications.
    sfPrinters = &H4                    ' virtual folder containing installed printers.
    sfMyDocuments = &H5                 ' directory used to physically store a user's common repository of documents.
    sfFavorites = &H6                   ' directory that serves as a common repository for the user's favorite items.
    sfStartup = &H7                     ' directory that corresponds to the user's Startup program group.
    sfMyRecentDocuments = &H8           ' directory that contains shortcuts to the user's most recently used documents.
    sfSendTo = &H9                      ' directory that contains Send To menu items.
    altRecycleBin = &HA                 ' virtual folder containing the objects in the user's Recycle Bin.
    sfStartMenu = &HB                   ' directory containing Start menu items.
    sfVirtualMyDocuments = &HC          ' virtual folder representing the My Documents desktop item.
    sfMyMusic = &HD                     ' directory that serves as a common repository for music files.
    sfMyVideos = &HE                    ' directory that serves as a common repository for video files.
    sfDesktopDir = &H10                 ' file system directory used to physically store file objects on the desktop (not to be confused with the desktop folder itself).
    sfMyComputer = &H11                 ' virtual folder representing My Computer, containing everything on the local computer: storage devices, printers, and Control Panel.
    sfNetworkNeighborhood = &H12        ' virtual folder representing Network Neighborhood, the root of the network namespace hierarchy.
    sfMyNetworkPlaces = &H13            ' directory containing the link objects that may exist in the My Network Places virtual folder.
    sfFonts = &H14                      ' virtual folder containing fonts.
    sfTemplates = &H15                  ' directory that serves as a common repository for document templates.
    sfCommonStartMenu = &H16            ' directory that contains the programs and folders that appear on the Start menu for all users.
    sfCommonPrograms = &H17             ' directory that contains the directories for the common program groups that appear on the Start menu for all users.
    sfCommonStartup = &H18              ' directory that contains the programs that appear in the Startup folder for all users.
    sfCommonDesktop = &H19              ' directory that contains files and folders that appear on the desktop for all users.
    sfApplicationData = &H1A            ' directory that serves as a common repository for application-specific data.
    sfPrintHood = &H1B                  ' directory that contains the link objects that can exist in the Printers virtual folder.
    sfLocalApplicationData = &H1C       ' directory that serves as a data repository for local (nonroaming) applications.
    sfAltStartup = &H1D                 ' directory wich corresponds to the user's nonlocalized Startup program group.
    sfCommonAltStartup = &H1E           ' directory that corresponds to the nonlocalized Startup program group for all users.
    sfCommonFavorites = &H1F            ' directory that serves as a common repository for favorite items common to all users.
    sfTemporaryInternetFiles = &H20     ' directory that serves as a common repository for temporary Internet files.
    sfCookies = &H21                    ' directory that serves as a common repository for Internet cookies.
    sfHistory = &H22                    ' directory that serves as a common repository for Internet history items.
    sfCommonApplicationData = &H23      ' directory containing application data for all users.
    sfWindows = &H24                    ' The Windows directory or SYSROOT.
    sfSystem = &H25                     ' The Windows System folder.
    sfProgramFiles = &H26               ' The Program Files folder.
    sfMyPictures = &H27                 ' directory that serves as a common repository for image files.
    sfProfile = &H28                    ' The user's profile folder.
    sfSharedProgramFiles = &H2B         ' for components that are shared across applications.
    sfCommonTemplates = &H2D            ' directory that contains the templates that are available to all users.
    sfCommonMyDocuments = &H2E          ' directory that contains documents that are common to all users.
    sfCommonAdminTools = &H2F           ' directory containing administrative tools for all users of the computer.
    sfAdminTools = &H30                 ' directory used to store administrative tools for an individual user.
    sfCDBurnArea = &H3B                 ' directory acting as a staging area for files waiting to be written to CD.
    sfCommonMyMusic = &H35              ' directory that serves as a repository for music files common to all users.
    sfCommonMyPictures = &H36           ' directory that serves as a repository for image files common to all users.
    sfCommonMyVideos = &H37             ' directory that serves as a repository for video files common to all users.
    sfProfiles = &H3E                   ' directory containing user profile folders.
    sfTemp = &HBEEF                     ' directory designated for temporary files.
End Enum

Public Enum InputDeviceConstants
    idKeyboard = 0                      ' User pressed a key.
    idMouse = &H8000&                   ' User clicked a mouse button.
    idOEM = &H1000&                     ' An unidentified hardware source generated the event. It could be a mouse or a keyboard event.
End Enum

Public Enum VirtualKeyConstants
    vkControl = &H8                     ' The CTRL key is down.
    vkLeftButton = &H1                  ' The left mouse button is down.
    vkMiddleButton = &H10               ' The middle mouse button is down.
    vkRightButton = &H2                 ' The right mouse button is down.
    vkShift = &H4                       ' The SHIFT key is down.
    vkXButton1 = &H20                   ' The first X button is down.
    vkXButton2 = &H40                   ' The second X button is down.
End Enum

Public Enum OSInfoConstants
    osiVersion                          ' one of the OSConstants
    osiMajorVersion                     ' Major version number of the operating system.
    osiMinorVersion                     ' Minor version number of the operating system.
    osiBuildNumber                      ' Build number of the operating system.
    osiNTPlatform                       ' returns True for NT systems
    osiServicePack                      ' returns Service Pack string (or vbNullString if no SP)
    osiServicePackMajor                 ' Major version number of the latest Service Pack installed on the system.
    osiServicePackMinor                 ' Minor version number of the latest Service Pack installed on the system.
    osiSignature                        ' returns full os signature string
    osiEdition                          ' one of the OSEditionConstants
End Enum

Public Enum OSConstants
    Windows32s
    Windows95
    Windows98
    WindowsNT
    WindowsMillenium
    Windows2000
    WindowsXP
    Windows2003
    Windows2003R2
    WindowsXP64
    WindowsVista
    Windows2008
End Enum

Public Enum OSEditionConstants
    edStandard
    edWorkstation
    edServer
    edHome
    edProfessional
    edEnterprise
    edDatacenter
    edWeb
    edDatacenterServer
    edEnterpriseServer
    edAdvancedServer
    edOSR2
    edSecondEdition
    edDatacenterItanium
    edEnterpriseItanium
    edDatacenter64
    edEnterprise64
    edStandard64
End Enum


Public Event onAppCommand(ByVal Command As AppCommandConstants, ByRef Implemented As Boolean, ByVal Handle As Long, ByVal Device As InputDeviceConstants, ByVal VirtualKey As VirtualKeyConstants)
Attribute onAppCommand.VB_Description = "This is called when user generated an application command event, for example, by clicking an application command button using the mouse or typing an application command key on the keyboard. (Extended Keyboards and Mice support)"
Public Event onMediaArrived(ByVal DriveLetter As String)
Attribute onMediaArrived.VB_Description = "Called when new removeable media or cd/dvd rom is arrived."
Public Event onMediaRemoved(ByVal DriveLetter As String)
Attribute onMediaRemoved.VB_Description = "Called when removeable media or cd/dvd rom is removed."
Public Event onTrayAction(ByVal Action As TrayActionConstants)
Public Event onBaloonAction(ByVal Action As BaloonActionConstants)
Public Event onMouseWheel(ByVal x As Single, ByVal y As Single, ByVal Lines As Long, ByVal VirtualKey As VirtualKeyConstants)
Public Event onActivate(ByVal Clicked As Boolean, ByVal Minimized As Boolean, ByVal DeactivatedWindow As Long)
Public Event onDeactivate(ByVal Minimized As Boolean, ByVal ActivatedWindow As Long)


Private Const WM_GETMINMAXINFO              As Long = &H24      ' sent to a window when the size or position of the window is about to change
Private Const WM_APPCOMMAND                 As Long = &H319     ' notifies an application with focus when one of new Internet or multimedia events occurs.
Private Const WM_DEVICECHANGE               As Long = &H219     ' notifies an application of a change to the hardware configuration of a device or the computer.
Private Const WM_MOUSEMOVE                  As Long = &H200     ' posted to a window when the cursor moves.
Private Const WM_NCLBUTTONDOWN              As Long = &HA1      ' posted when the user presses the left mouse button while the cursor is within the nonclient area of a window.
Private Const WM_MOVING                     As Long = &H216     ' sent to a window that the user is moving.
Private Const WM_SIZING                     As Long = &H214     ' sent to a window that the user is resizing.
Private Const WM_LBUTTONDBLCLK              As Long = &H203     ' posted when the user double-clicks the left mouse button while the cursor is in the client area of a window.
Private Const WM_LBUTTONDOWN                As Long = &H201     ' posted when the user presses the left mouse button while the cursor is in the client area of a window.
Private Const WM_LBUTTONUP                  As Long = &H202     ' posted when the user releases the left mouse button while the cursor is in the client area of a window.
Private Const WM_RBUTTONDBLCLK              As Long = &H206     ' posted when the user double-clicks the right mouse button while the cursor is in the client area of a window.
Private Const WM_RBUTTONDOWN                As Long = &H204     ' posted when the user presses the right mouse button while the cursor is in the client area of a window.
Private Const WM_RBUTTONUP                  As Long = &H205     ' posted when the user releases the right mouse button while the cursor is in the client area of a window.
Private Const WM_USER                       As Long = &H400     ' used by applications to help define private messages for use by private window classes
Private Const WM_DESTROY                    As Long = &H2       ' sent when a window is being destroyed.
Private Const WM_CONTEXTMENU                As Long = &H7B      ' notifies a window that the user clicked the right mouse button ( right clicked) in the window.
Private Const WM_SYSCOMMAND                 As Long = &H112     ' window receives this message when the user chooses a command from the Window menu (formerly known as the system or control menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
Private Const WM_SHOWWINDOW                 As Long = &H18      ' sent to a window when the window is about to be hidden or shown.
Private Const WM_MOUSEWHEEL                 As Long = &H20A     ' sent to the focus window when the mouse wheel is rotated.
Private Const WA_ACTIVE                     As Long = &H1       ' sent to both the window being activated and the window being deactivated.




Private Const FAPPCOMMAND_MASK              As Long = &HF000&
Private Const FILE_ATTRIBUTE_DIRECTORY      As Long = &H10
Private Const DBT_DEVICEARRIVAL             As Long = &H8000&
Private Const DBT_DEVICEREMOVECOMPLETE      As Long = &H8004&
Private Const DBTF_MEDIA                    As Long = &H1
Private Const DBT_DEVTYP_VOLUME             As Long = &H2
Private Const HTCAPTION                     As Long = &H2
Private Const SPI_GETWORKAREA               As Long = 48
Private Const NIF_ICON                      As Long = &H2
Private Const NIF_MESSAGE                   As Long = &H1
Private Const NIF_TIP                       As Long = &H4
Private Const NIF_STATE                     As Long = &H8
Private Const NIF_INFO                      As Long = &H10
Private Const NIM_ADD                       As Long = &H0
Private Const NIM_MODIFY                    As Long = &H1
Private Const NIM_DELETE                    As Long = &H2
Private Const NIM_SETFOCUS                  As Long = &H3
Private Const NOTIFYICONDATAA_V1_SIZE_A     As Long = 88
Private Const NOTIFYICONDATAA_V1_SIZE_U     As Long = 152
Private Const NOTIFYICONDATAA_V2_SIZE_A     As Long = 488
Private Const NOTIFYICONDATAA_V2_SIZE_U     As Long = 936
Private Const WM_NOTIFYICON                 As Long = WM_USER + 42
Private Const NIN_BALLOONSHOW               As Long = WM_USER + 2
Private Const NIN_BALLOONHIDE               As Long = WM_USER + 3
Private Const NIN_BALLOONTIMEOUT            As Long = WM_USER + 4
Private Const NIN_BALLOONUSERCLICK          As Long = WM_USER + 5
Private Const CSIDL_FLAG_CREATE             As Long = &H8000&
Private Const GWL_EXTSTYLE                  As Long = -20
Private Const WS_EX_APPWINDOW               As Long = &H40000
Private Const WS_EX_TOOLWINDOW              As Long = &H80&
Private Const DS_MODALFRAME                 As Long = &H80
Private Const SW_HIDE                       As Long = 0
Private Const SW_SHOW                       As Long = 5
Private Const SW_SHOWMINNOACTIVE            As Long = 7
Private Const SW_RESTORE                    As Long = 9
Private Const SC_CLOSE                      As Long = &HF060&
Private Const SC_MAXIMIZE                   As Long = &HF030&
Private Const SC_MINIMIZE                   As Long = &HF020&
Private Const SC_RESTORE                    As Long = &HF120&
Private Const IDANI_CAPTION                 As Long = &H3
Private Const WHEEL_DELTA                   As Long = 120
Private Const SPI_GETWHEELSCROLLLINES       As Long = 104
Private Const WA_CLICKACTIVE                As Long = 2
Private Const WA_INACTIVE                   As Long = 0
Private Const WM_ACTIVATE                   As Long = &H6
Private Const TB_BUTTONCOUNT                As Long = WM_USER + 24
Private Const TB_GETBUTTONTEXT              As Long = WM_USER + 45
Private Const TB_GETSTATE                   As Long = WM_USER + 18
Private Const TB_SETSTATE                   As Long = WM_USER + 17



Private Declare Function GetFileAttributes Lib "kernel32.dll" Alias "GetFileAttributesA" (ByVal lpFileName As String) As Long
Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" (ByVal lpVersionInformation As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, ByRef phkResult As Long) As Long
Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, ByRef lpType As Long, ByRef lpData As Any, ByRef lpcbData As Long) As Long
Private Declare Function ReleaseCapture Lib "user32.dll" () As Long
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Sub GetSystemInfo Lib "kernel32.dll" (ByRef lpSystemInfo As SYSTEM_INFO)
Private Declare Sub GetNativeSystemInfoAPI Lib "kernel32.dll" Alias "GetNativeSystemInfo" (ByRef lpSystemInfo As SYSTEM_INFO)
Private Declare Function GetSystemMetrics Lib "user32.dll" (ByVal nIndex As Long) As Long
Private Declare Function GetClientRect Lib "user32.dll" (ByVal hwnd As Long, ByRef lpRect As RECT) As Long
Private Declare Function GetParent Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function SetParent Lib "user32.dll" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
Private Declare Function GetDesktopWindow Lib "user32.dll" () As Long
Private Declare Function SystemParametersInfo Lib "user32.dll" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uParam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
Private Declare Function SHGetFolderLocation Lib "shell32.dll" (ByVal hwnd As Long, ByVal csidl As Long, ByVal hToken As Long, ByVal dwFlags As Long, ByRef ppidl As ITEMIDLIST) As Long
Private Declare Function OpenThreadToken Lib "advapi32.dll" (ByVal ThreadHandle As Long, ByVal DesiredAccess As Long, ByVal OpenAsSelf As Long, ByRef TokenHandle As Long) As Long
Private Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" (ByVal hwndOwner As Long, ByVal nFolder As Long, pidl As ITEMIDLIST) As Long
Private Declare Function SHGetPathFromIDList Lib "shell32.dll" Alias "SHGetPathFromIDListA" (ByVal pidl As Long, ByVal pszPath As String) As Long
Private Declare Function Shell_NotifyIconA Lib "shell32.dll" (ByVal dwMessage As Long, lpData As NOTIFYICONDATAA) As Long
Private Declare Function Shell_NotifyIconW Lib "shell32.dll" (ByVal dwMessage As Long, lpData As NOTIFYICONDATAW) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetTempPath Lib "kernel32.dll" (ByVal nBufferLength, ByRef lpBuffer As String) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function IsWindowVisible Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function ShowWindow Lib "user32.dll" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Private Declare Function RegisterWindowMessage Lib "user32.dll" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
Private Declare Function IsIconic Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function IsZoomed Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function DrawAnimatedRects Lib "user32.dll" (ByVal hwnd As Long, ByVal idAni As Long, ByRef lprcFrom As RECT, ByRef lprcTo As RECT) As Long
Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
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
Private Declare Function GetWindowRect Lib "user32.dll" (ByVal hwnd As Long, ByRef lpRect As RECT) As Long
Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long



Private Type POINT
    x                   As Long     ' Specifies the x-coordinate of the point.
    y                   As Long     ' Specifies the y-coordinate of the point.
End Type

Private Type DEV_BROADCAST_HDR
    dbch_size           As Long     ' Size of this structure, in bytes.
    dbch_devicetype     As Long     ' Device type, which determines the event-specific information that follows the first three members.
    dbch_reserved       As Long     ' Reserved; do not use.
End Type

Private Type DEV_BROADCAST_VOLUME
    dbch_size           As Long     ' Size of this structure, in bytes.
    dbch_devicetype     As Long     ' Set to DBT_DEVTYP_VOLUME.
    dbch_reserved       As Long     ' Reserved; do not use.
    dbcv_unitmask       As Long     ' Logical unit mask identifying one or more logical units. Each bit in the mask corresponds to one logical drive. Bit 0 represents drive A, bit 1 represents drive B, and so on.
    dbcv_flags          As Integer  ' This parameter can be DBTF_MEDIA or DBTF_NET
End Type

Private Type OSVERSIONINFO
    dwOSVersionInfoSize As Long         ' Size of this data structure, in bytes.
    dwMajorVersion      As Long         ' Major version number of the operating system.
    dwMinorVersion      As Long         ' Minor version number of the operating system.
    dwBuildNumber       As Long         ' Build number of the operating system.
    dwPlatformId        As Long         ' Operating system platform.
    szCSDVersion        As String * 128 ' Latest Service Pack installed on the system.
End Type

Private Type OSVERSIONINFOEX
    dwOSVersionInfoSize As Long         ' Size of this data structure, in bytes.
    dwMajorVersion      As Long         ' Major version number of the operating system.
    dwMinorVersion      As Long         ' Minor version number of the operating system.
    dwBuildNumber       As Long         ' Build number of the operating system.
    dwPlatformId        As Long         ' Operating system platform.
    szCSDVersion        As String * 128 ' Latest Service Pack installed on the system.
    wServicePackMajor   As Integer      ' Major version number of the latest Service Pack installed on the system.
    wServicePackMinor   As Integer      ' Minor version number of the latest Service Pack installed on the system.
    wSuiteMask          As Integer      ' Bit flags that identify the product suites available on the system.
    wProductType        As Byte         ' Additional information about the system.
    wReserved           As Byte         ' Reserved for future use.
    
    ' these are not part of the OSVERSIONINFOEX, i've added them to store additional os info
    eOSVersion          As OSConstants          ' 95/98/NT/XP...
    eOSEdition          As OSEditionConstants   ' home/pro/datacenter...
    sOSSignature        As String               ' full os signature
    sServicePack        As String               ' trimmed szCSDVersion
End Type

Private Type SYSTEM_INFO
    wProcessorArchitecture      As Integer  ' System's processor architecture.
    wReserved                   As Integer  ' Reserved for future use.
    dwPageSize                  As Long     ' Page size and the granularity of page protection and commitment.
    lpMinimumApplicationAddress As Long     ' Pointer to the lowest memory address accessible to applications and dynamic-link libraries (DLLs).
    lpMaximumApplicationAddress As Long     ' Pointer to the highest memory address accessible to applications and DLLs.
    dwActiveProcessorMask       As Long     ' Mask representing the set of processors configured into the system. Bit 0 is processor 0; bit 31 is processor 31.
    dwNumberOrfProcessors       As Long     ' Number of processors in the system.
    dwProcessorType             As Long     ' An obsolete member that is retained for compatibility with Windows NT 3.5 and earlier.
    dwAllocationGranularity     As Long     ' Granularity with which virtual memory is allocated.
    wProcessorLevel             As Integer  ' System's architecture-dependent processor level.
    wProcessorRevision          As Integer  ' Architecture-dependent processor revision.
End Type

Private Type MINMAXINFO
    Reserved        As POINT    ' Reserved; do not use.
    MaxSize         As POINT    ' Specifies the maximized width (POINT.x) and the maximized height (POINT.y) of the window. For systems with multiple monitors, this refers to the primary monitor.
    MaxPosition     As POINT    ' Specifies the position of the left side of the maximized window (POINT.x) and the position of the top of the maximized window (POINT.y). For systems with multiple monitors, this refers to the monitor on which the window maximizes.
    MinTrackSize    As POINT    ' Specifies the minimum tracking width (POINT.x) and the minimum tracking height (POINT.y) of the window. This is unchanged for systems with multiple monitors.
    MaxTrackSize    As POINT    ' Specifies the maximum tracking width (POINT.x) and the maximum tracking height (POINT.y) of the window. For systems with multiple monitors, this is the size for a window that is made as large as the virtual screen.
End Type

Private Type RECT
    Left            As Long     ' Specifies the x-coordinate of the upper-left corner of the rectangle.
    Top             As Long     ' Specifies the y-coordinate of the upper-left corner of the rectangle.
    Right           As Long     ' Specifies the x-coordinate of the lower-right corner of the rectangle.
    Bottom          As Long     ' Specifies the y-coordinate of the lower-right corner of the rectangle.
End Type

Private Type NOTIFYICONDATAA
   cbSize               As Long         ' Size of this structure, in bytes.
   hwnd                 As Long         ' Handle to the window that receives notification messages associated with an icon in the taskbar status area.
   uID                  As Long         ' Application-defined identifier of the taskbar icon.
   uFlags               As Long         ' Flags that indicate which of the other members contain valid data.
   uCallbackMessage     As Long         ' Application-defined message identifier.
   hIcon                As Long         ' Handle to the icon to be added, modified, or deleted.
   szTip                As String * 128 ' Pointer to a null-terminated string with the text for a standard ToolTip.
   dwState              As Long         ' State of the icon.
   dwStateMask          As Long         ' A value that specifies which bits of the state member are retrieved or modified.
   szInfo               As String * 256 ' Pointer to a null-terminated string with the text for a balloon ToolTip.
   uTimeOutOrVersion    As Long         ' The timeout value, in milliseconds, for a balloon ToolTip. / Specifies whether the Shell notify icon interface should use Windows 95 or Windows 2000 behavior.
   szInfoTitle          As String * 64  ' Pointer to a null-terminated string containing a title for a balloon ToolTip.
   dwInfoFlags          As Long         ' Flags that can be set to add an icon to a balloon ToolTip.
   guidItem             As Long         ' Reserved.
End Type

Private Type NOTIFYICONDATAW
   cbSize                   As Long     ' Size of this structure, in bytes.
   hwnd                     As Long     ' Handle to the window that receives notification messages associated with an icon in the taskbar status area.
   uID                      As Long     ' Application-defined identifier of the taskbar icon.
   uFlags                   As Long     ' Flags that indicate which of the other members contain valid data.
   uCallbackMessage         As Long     ' Application-defined message identifier.
   hIcon                    As Long     ' Handle to the icon to be added, modified, or deleted.
   szTip(0 To 255)          As Byte     ' Pointer to a null-terminated string with the text for a standard ToolTip.
   dwState                  As Long     ' State of the icon.
   dwStateMask              As Long     ' A value that specifies which bits of the state member are retrieved or modified.
   szInfo(0 To 511)         As Byte     ' Pointer to a null-terminated string with the text for a balloon ToolTip.
   uTimeOutOrVersion        As Long     ' The timeout value, in milliseconds, for a balloon ToolTip. / Specifies whether the Shell notify icon interface should use Windows 95 or Windows 2000 behavior.
   szInfoTitle(0 To 127)    As Byte     ' Pointer to a null-terminated string containing a title for a balloon ToolTip.
   dwInfoFlags              As Long     ' Flags that can be set to add an icon to a balloon ToolTip.
   guidItem                 As Long     ' Reserved.
End Type

Private Type SHITEMID
    cb              As Long             ' Size of identifier, in bytes, including cb itself.
    abID            As Byte             ' Variable-length item identifier.
End Type

Private Type ITEMIDLIST
    mkid            As SHITEMID         ' List of item identifiers.
End Type

Private Type TBBUTTONINFO
    cbSize          As Long             ' Size of the structure, in bytes.
    dwMask          As Long             ' Set of flags that indicate which members contain valid information.
    idCommand       As Long             ' Command identifier of the button.
    iImage          As Long             ' Image index of the button.
    fsState         As Byte             ' State flags of the button.
    fsStyle         As Byte             ' Style flags of the button.
    cx              As Integer          ' Width of the button, in pixels.
    lParam          As Long             ' Application-defined value associated with the button.
    pszText         As String           ' Address of a character buffer that contains or receives the button text.
    cchText         As Long             ' Size of the buffer at pszText.
End Type


Private m_form              As Form
Private m_hWnd              As Long
Attribute m_hWnd.VB_VarHelpID = -1
Private m_minmax            As MINMAXINFO
Private m_easyMove          As Boolean
Private m_osinfo            As OSVERSIONINFOEX
Private m_snap              As Boolean
Private m_snapDistance      As Long
Private m_parent            As Long
Private m_desktop           As Long
Private m_notifyIconA       As NOTIFYICONDATAA
Private m_notifyIconW       As NOTIFYICONDATAW
Private m_notifyNewVersion  As Boolean
Private m_notifyStructSize  As Long
Private m_unicode           As Boolean
Private m_showInTray        As Boolean
Private m_trayIcon          As Long
Private m_trayTip           As String
Private m_showInTaskbar     As Boolean
Private m_showInTaskbarLast As Boolean
Private m_minimizeToTray    As Boolean
Private m_closeToTray       As Boolean
Private m_trayHwnd          As Long
Private m_taskbarHwnd       As Long
Private m_rect              As RECT

Private WM_TASKBARCREATED   As Long


'-Selfsub declarations----------------------------------------------------------------------------
Private Enum eMsgWhen                                                       'When to callback
  MSG_BEFORE = 1                                                            'Callback before the original WndProc
  MSG_AFTER = 2                                                             'Callback after the original WndProc
  MSG_BEFORE_AFTER = MSG_BEFORE Or MSG_AFTER                                'Callback before and after the original WndProc
End Enum

Private Const ALL_MESSAGES  As Long = -1                                    'All messages callback
Private Const MSG_ENTRIES   As Long = 32                                    'Number of msg table entries
Private Const WNDPROC_OFF   As Long = &H38                                  'Thunk offset to the WndProc execution address
Private Const GWL_WNDPROC   As Long = -4                                    'SetWindowsLong WndProc index
Private Const IDX_SHUTDOWN  As Long = 1                                     'Thunk data index of the shutdown flag
Private Const IDX_HWND      As Long = 2                                     'Thunk data index of the subclassed hWnd
Private Const IDX_WNDPROC   As Long = 9                                     'Thunk data index of the original WndProc
Private Const IDX_BTABLE    As Long = 11                                    'Thunk data index of the Before table
Private Const IDX_ATABLE    As Long = 12                                    'Thunk data index of the After table
Private Const IDX_PARM_USER As Long = 13                                    'Thunk data index of the User-defined callback parameter data index

Private z_ScMem             As Long                                         'Thunk base address
Private z_Sc(64)            As Long                                         'Thunk machine-code initialised here
Private z_Funk              As Collection                                   'hWnd/thunk-address collection

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
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function IsBadCodePtr Lib "kernel32" (ByVal lpfn As Long) As Long
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetWindowLongA Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFree Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Sub RtlMoveMemory Lib "kernel32" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)
'-------------------------------------------------------------------------------------------------
'
'

Public Function Extend(ByRef frm As Form) As Boolean
Attribute Extend.VB_Description = "Call this to extend a Form."
    
    If m_hWnd <> 0 Then Exit Function ' return False
    
    m_hWnd = frm.hwnd
    Set m_form = frm

    If sc_Subclass(m_hWnd) Then
        
        WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated")
        
        sc_AddMsg m_hWnd, WM_GETMINMAXINFO, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_APPCOMMAND, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_DEVICECHANGE, MSG_AFTER
        sc_AddMsg m_hWnd, WM_MOUSEMOVE, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_MOVING, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_SIZING, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_NOTIFYICON, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_TASKBARCREATED, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_DESTROY, MSG_AFTER
        sc_AddMsg m_hWnd, WM_SYSCOMMAND, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_SHOWWINDOW, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_MOUSEWHEEL, MSG_BEFORE
        sc_AddMsg m_hWnd, WM_ACTIVATE, MSG_BEFORE

        m_minmax.MaxSize.x = -1: m_minmax.MaxSize.y = -1
        m_minmax.MaxTrackSize.x = -1: m_minmax.MaxTrackSize.y = -1
        m_minmax.MinTrackSize.x = -1: m_minmax.MinTrackSize.y = -1
        
        m_snapDistance = 10
        m_desktop = GetDesktopWindow
        m_parent = m_desktop
        
        Extend = True
        
        GatherOSInfo
        
        m_unicode = m_osinfo.dwPlatformId = 2
        
        m_notifyNewVersion = m_osinfo.eOSVersion >= WindowsMillenium
        
        If m_notifyNewVersion Then
            If m_unicode Then
                m_notifyStructSize = NOTIFYICONDATAA_V2_SIZE_U
            Else
                m_notifyStructSize = NOTIFYICONDATAA_V2_SIZE_A
            End If
        Else
            If m_unicode Then
                m_notifyStructSize = NOTIFYICONDATAA_V1_SIZE_U
            Else
                m_notifyStructSize = NOTIFYICONDATAA_V1_SIZE_A
            End If
        End If
        
        m_trayIcon = m_form.Icon.Handle
        m_trayTip = App.Title
        
        m_showInTaskbar = GetWindowLong(m_hWnd, GWL_EXTSTYLE) And WS_EX_APPWINDOW
        m_showInTaskbarLast = m_showInTaskbar
    
    End If
    
End Function
'

Public Property Get MinWidth() As Long
Attribute MinWidth.VB_Description = "Get/Set the minimum width of the Form. Setting this to -1 will disable minimum width."
    MinWidth = m_minmax.MinTrackSize.x
End Property
'

Public Property Let MinWidth(W As Long)
    m_minmax.MinTrackSize.x = W
End Property
'

Public Property Get MinHeight() As Long
Attribute MinHeight.VB_Description = "Get/Set the minimum height of the Form. Setting this to -1 will disable minimum height."
    MinWidth = m_minmax.MinTrackSize.y
End Property
'

Public Property Let MinHeight(H As Long)
    m_minmax.MinTrackSize.y = H
End Property
'

Public Property Get MaxWidth() As Long
Attribute MaxWidth.VB_Description = "Get/Set the maximum width of the Form. Setting this to -1 will disable maximum width."
    MinWidth = m_minmax.MaxTrackSize.x
End Property
'

Public Property Let MaxWidth(W As Long)
    m_minmax.MaxTrackSize.x = W
End Property
'

Public Property Get MaxHeight() As Long
Attribute MaxHeight.VB_Description = "Get/Set the maximum height of the Form. Setting this to -1 will disable maximum height."
    MinWidth = m_minmax.MaxTrackSize.y
End Property
'

Public Property Let MaxHeight(H As Long)
    m_minmax.MaxTrackSize.y = H
End Property
'

Public Property Get MaximizedLeft() As Long
Attribute MaximizedLeft.VB_Description = "Get/Set the left position of the Form in it's maximized state."
    MaximizedLeft = m_minmax.MaxPosition.x
End Property
'

Public Property Let MaximizedLeft(x As Long)
    m_minmax.MaxPosition.x = x
End Property
'

Public Property Get MaximizedTop() As Long
Attribute MaximizedTop.VB_Description = "Get/Set the top position of Form in it's maximized state."
    MaximizedTop = m_minmax.MaxPosition.y
End Property
'

Public Property Let MaximizedTop(y As Long)
    m_minmax.MaxPosition.y = y
End Property
'

Public Property Get EasyMove() As Boolean
    EasyMove = m_easyMove
End Property
'

Public Property Let EasyMove(bEasyMove As Boolean)
    m_easyMove = bEasyMove
End Property
'

Public Property Get Snap() As Boolean
    Snap = m_snap
End Property
'

Public Property Let Snap(bSnap As Boolean)
    m_snap = bSnap
End Property
'

Public Property Get SnapDistance() As Long
    SnapDistance = m_snapDistance
End Property
'

Public Property Let SnapDistance(lDistance As Long)
    m_snapDistance = lDistance
End Property
'

Public Property Get Parent() As Long
    Parent = m_parent
End Property
'

Public Property Let Parent(lParent As Long)
    m_parent = lParent
    SetParent m_hWnd, lParent
End Property
'

Public Property Get ShowInTray() As Boolean
    ShowInTray = m_showInTray
End Property
'

Public Property Let ShowInTray(bShow As Boolean)
    If bShow <> m_showInTray Then
        m_showInTray = bShow
        If m_unicode Then
            If m_showInTray Then
                m_notifyIconW.hwnd = m_hWnd
                m_notifyIconW.uID = App.hInstance
                m_notifyIconW.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
                m_notifyIconW.uCallbackMessage = WM_NOTIFYICON
                m_notifyIconW.hIcon = m_trayIcon
                stringToArray m_trayTip, m_notifyIconW.szTip, IIf(m_notifyNewVersion, 128, 64) * 2
                m_notifyIconW.cbSize = m_notifyStructSize
                Shell_NotifyIconW NIM_ADD, m_notifyIconW
            Else
                Shell_NotifyIconW NIM_DELETE, m_notifyIconW
            End If
        Else
            If m_showInTray Then
                m_notifyIconA.hwnd = m_hWnd
                m_notifyIconA.uID = App.hInstance
                m_notifyIconA.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
                m_notifyIconA.uCallbackMessage = WM_NOTIFYICON
                m_notifyIconA.hIcon = m_trayIcon
                m_notifyIconA.szInfo = m_trayTip & vbNullChar
                m_notifyIconA.cbSize = m_notifyStructSize
                Shell_NotifyIconA NIM_ADD, m_notifyIconA
            Else
                Shell_NotifyIconA NIM_DELETE, m_notifyIconA
            End If
        End If
    End If
End Property
'

Public Property Get TrayTooltip() As String
    TrayTooltip = m_trayTip
End Property
'

Public Property Let TrayTooltip(sTooltip As String)
    If sTooltip <> m_trayTip Then
        m_trayTip = sTooltip
        If m_showInTray Then
            If m_unicode Then
                stringToArray m_trayTip, m_notifyIconW.szTip, IIf(m_notifyNewVersion, 128, 64) * 2
                m_notifyIconW.uFlags = NIF_TIP
                Shell_NotifyIconW NIM_MODIFY, m_notifyIconW
            Else
                m_notifyIconA.szTip = m_trayTip & vbNullChar
                m_notifyIconA.uFlags = NIF_TIP
                Shell_NotifyIconA NIM_MODIFY, m_notifyIconA
            End If
        End If
    End If
End Property
'

Public Property Get TrayIcon() As Long
    TrayIcon = m_trayIcon
End Property
'

Public Property Let TrayIcon(ByVal lIcon As Long)
    If m_trayIcon <> lIcon Then
        m_trayIcon = lIcon
        If m_showInTray Then
            If m_unicode Then
                m_notifyIconW.hIcon = m_trayIcon
                m_notifyIconW.uFlags = NIF_ICON
                Shell_NotifyIconW NIM_MODIFY, m_notifyIconW
            Else
                m_notifyIconA.hIcon = m_trayIcon
                m_notifyIconA.uFlags = NIF_ICON
                Shell_NotifyIconA NIM_MODIFY, m_notifyIconA
            End If
        End If
    End If
End Property
'

Public Function ShowBaloon(ByVal sText As String, Optional ByVal sTitle As String = vbNullString, Optional eStyle As BalloonStyleConstants = bsDefault, Optional ByVal lTimeout As Long = 20) As Boolean
    If m_notifyNewVersion Then
        ShowBaloon = True
        If m_unicode Then
            stringToArray sText, m_notifyIconW.szInfo, 512
            stringToArray sTitle, m_notifyIconW.szInfoTitle, 128
            m_notifyIconW.uTimeOutOrVersion = lTimeout * 1000
            m_notifyIconW.dwInfoFlags = eStyle
            m_notifyIconW.uFlags = NIF_INFO
            Shell_NotifyIconW NIM_MODIFY, m_notifyIconW
        Else
            m_notifyIconA.szInfo = sText
            m_notifyIconA.szInfoTitle = sTitle
            m_notifyIconA.uTimeOutOrVersion = lTimeout * 1000
            m_notifyIconA.dwInfoFlags = eStyle
            m_notifyIconA.uFlags = NIF_INFO
            Shell_NotifyIconA NIM_MODIFY, m_notifyIconA
        End If
    End If
End Function
'

Public Property Get ShowInTaskbar() As Boolean
    ShowInTaskbar = m_showInTaskbar
End Property
'

Public Property Let ShowInTaskbar(ByVal bShow As Boolean)
    Dim lStyle  As Long
    m_showInTaskbar = bShow
    m_showInTaskbarLast = Not bShow
    If IsWindowVisible(m_hWnd) Then
        zWndProc1 False, False, 0, 0, WM_SHOWWINDOW, True, 0, 0
    End If
End Property
'

Public Property Get MinimizeToTray() As Boolean
    MinimizeToTray = m_minimizeToTray
End Property
'

Public Property Let MinimizeToTray(bMinimize As Boolean)
    m_minimizeToTray = bMinimize
End Property
'

Public Property Get CloseToTray() As Boolean
    CloseToTray = m_closeToTray
End Property
'

Public Property Let CloseToTray(bClose As Boolean)
    m_closeToTray = bClose
End Property
'

Public Sub Minimize(Optional Animate As Boolean = True)
    If Not IsIconic(m_hWnd) Then
        SendMessage m_hWnd, WM_SYSCOMMAND, SC_MINIMIZE, Abs(Not Animate)
    End If
End Sub
'

Public Sub Maximize()
    If Not IsZoomed(m_hWnd) Then
        SendMessage m_hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0
    End If
End Sub
'

Public Sub Restore(Optional Animate As Boolean = True)
    If IsIconic(m_hWnd) Or IsZoomed(m_hWnd) Then
        SendMessage m_hWnd, WM_SYSCOMMAND, SC_RESTORE, Abs(Not Animate)
    End If
End Sub
'

Public Function GetSpecialFolder(ByVal Folder As SpecialFolderConstants, Optional Create As Boolean = False) As String
    
    Dim lRet    As Long
    Dim IDL     As ITEMIDLIST
    Dim sBuffer As String
    Dim lToken  As Long
    
    GetSpecialFolder = vbNullString
    
    If Folder = sfTemp Then
        
        sBuffer = String(260, 0)
        lRet = GetTempPath(260, sBuffer)
        
        If lRet > 260 Then
            sBuffer = String(lRet + 1, 0)
            lRet = GetTempPath(lRet + 1, sBuffer)
        End If
        
        If lRet <> 0 Then GetSpecialFolder = Left$(sBuffer, lRet)
        
        Exit Function
        
    End If
    
    lRet = 1
    
    If Create And m_osinfo.eOSVersion >= WindowsMillenium Then Folder = CLng(Folder) Or CSIDL_FLAG_CREATE
    
    If m_osinfo.eOSVersion >= Windows2000 Then
        lRet = SHGetFolderLocation(m_hWnd, CLng(Folder), -1, 0, IDL)
    End If
    
    If lRet <> 0 Then
        If SHGetSpecialFolderLocation(m_hWnd, CLng(Folder), IDL) <> 0 Then Exit Function
    End If
    
    sBuffer = String(260, 0)
        
    SHGetPathFromIDList ByVal IDL.mkid.cb, ByVal sBuffer
    
    sBuffer = Left$(sBuffer, InStr(sBuffer, Chr$(0)) - 1)
    
    If Right$(sBuffer, 1) = "\" Then sBuffer = Left$(sBuffer, Len(sBuffer) - 1)
    
    GetSpecialFolder = sBuffer
    
End Function
'

Public Function GetOSInfo(ByVal info As OSInfoConstants, ByRef vReturn As Variant) As Boolean
    
    GetOSInfo = False
    
    If info >= osiVersion And info <= osiEdition Then
    
        GetOSInfo = True
        
        Select Case info
            
            Case osiVersion: vReturn = m_osinfo.eOSVersion
            
            Case osiMajorVersion: vReturn = m_osinfo.dwMajorVersion
            
            Case osiMinorVersion: vReturn = m_osinfo.dwMinorVersion
            
            Case osiBuildNumber: vReturn = m_osinfo.dwBuildNumber
            
            Case osiNTPlatform: vReturn = m_osinfo.dwPlatformId = 2
            
            Case osiServicePack: vReturn = IIf(LenB(m_osinfo.sServicePack) > 0, m_osinfo.sServicePack, m_osinfo.szCSDVersion)
            
            Case osiServicePackMajor: vReturn = m_osinfo.wServicePackMajor
            
            Case osiServicePackMinor: vReturn = m_osinfo.wServicePackMinor
            
            Case osiSignature: vReturn = m_osinfo.sOSSignature
            
            Case osiEdition: vReturn = m_osinfo.eOSEdition
            
        End Select
    
    End If
    
End Function
'

' Returns the high-order word from the given 32-bit value.
Public Function HiWord(ByVal dword As Long) As Long
Attribute HiWord.VB_Description = "Returns the high-order word from the given 32-bit value."
    RtlMoveMemory VarPtr(HiWord), VarPtr(dword) + 2, 2
End Function
'

' Returns the low-order word from the given 32-bit value.
Public Function LoWord(ByVal dword As Long) As Long
Attribute LoWord.VB_Description = "Returns the low-order word from the given 32-bit value."
    RtlMoveMemory VarPtr(LoWord), VarPtr(dword), 2
End Function
'


Private Function GatherOSInfo() As Boolean
        
    Dim bOsVersionInfoEx    As Boolean
    Dim lKey                As Long
    Dim lRet                As Long
    Dim sBuffer             As String
    Dim sysnfo              As SYSTEM_INFO
    
    Const SizeOf_OSVERSIONINFOEX    As Long = 156   ' without extra fields added
    Const SizeOf_OSVERSIONINFO      As Long = 148
    
    Const HKEY_LOCAL_MACHINE        As Long = &H80000002
    Const KEY_QUERY_VALUE           As Long = &H1
    
    bOsVersionInfoEx = True
    
    m_osinfo.dwOSVersionInfoSize = SizeOf_OSVERSIONINFOEX
    
    If GetVersionEx(VarPtr(m_osinfo)) = 0 Then
        bOsVersionInfoEx = False
        m_osinfo.dwOSVersionInfoSize = SizeOf_OSVERSIONINFO
        If GetVersionEx(VarPtr(m_osinfo)) = 0 Then
            GatherOSInfo = False
            Exit Function
        End If
    End If
    
    If Not GetNativeSystemInfo(sysnfo) Then
        GetSystemInfo sysnfo
    End If
    
    m_osinfo.eOSEdition = edStandard
    m_osinfo.szCSDVersion = StrConv(m_osinfo.szCSDVersion, vbUnicode)
    lRet = InStr(1, m_osinfo.szCSDVersion, vbNullChar)
    If lRet > 0 Then
        m_osinfo.sServicePack = Left$(m_osinfo.szCSDVersion, lRet - 1)
    End If
    
    Select Case m_osinfo.dwPlatformId
        
        Case 2 ' VER_PLATFORM_WIN32_NT
            
            If m_osinfo.dwMajorVersion = 6 And m_osinfo.dwMinorVersion = 0 Then
                If m_osinfo.wProductType = 0 Then ' VER_NT_WORKSTATION
                    m_osinfo.eOSVersion = WindowsVista
                    m_osinfo.sOSSignature = "Windows Vista"
                Else
                    m_osinfo.eOSVersion = Windows2008
                    m_osinfo.sOSSignature = "Windows Server ""Longhorn"""
                End If
            ElseIf m_osinfo.dwMajorVersion = 5 And m_osinfo.dwMinorVersion = 2 Then
                If GetSystemMetrics(89) Then ' SM_SERVERR2
                    m_osinfo.eOSVersion = Windows2003R2
                    m_osinfo.sOSSignature = "Microsoft Windows Server 2003 ""R2"""
                ElseIf m_osinfo.wProductType = 0 And sysnfo.wProcessorArchitecture = 9 Then ' VER_NT_WORKSTATION and PROCESSOR_ARCHITECTURE_AMD64
                    m_osinfo.eOSVersion = WindowsXP64
                    m_osinfo.sOSSignature = "Microsoft Windows XP Professional x64 Edition"
                Else
                    m_osinfo.eOSVersion = Windows2003
                    m_osinfo.sOSSignature = "Microsoft Windows Server 2003 family"
                End If
            ElseIf m_osinfo.dwMajorVersion = 5 And m_osinfo.dwMinorVersion = 1 Then
                m_osinfo.eOSVersion = WindowsXP
                m_osinfo.sOSSignature = "Microsoft Windows XP"
            ElseIf m_osinfo.dwMajorVersion = 5 And m_osinfo.dwMinorVersion = 0 Then
                m_osinfo.eOSVersion = Windows2000
                m_osinfo.sOSSignature = "Microsoft Windows 2000"
            ElseIf m_osinfo.dwMajorVersion <= 4 Then
                m_osinfo.eOSVersion = WindowsNT
                m_osinfo.sOSSignature = "Microsoft Windows NT"
            End If
            
            If bOsVersionInfoEx Then
                If m_osinfo.wProductType = &H1 Or m_osinfo.wProductType = 0 Then ' VER_NT_WORKSTATION
                    If m_osinfo.dwMajorVersion <= 4 Then
                        m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Workstation 4.0"
                        m_osinfo.eOSEdition = edWorkstation
                    ElseIf m_osinfo.wSuiteMask And &H200& Then ' VER_SUITE_PERSONAL
                        m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Home Edition"
                        m_osinfo.eOSEdition = edHome
                    Else
                        m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Professional"
                        m_osinfo.eOSEdition = edProfessional
                    End If
                ElseIf m_osinfo.wProductType = &H3 Or m_osinfo.wProductType = &H2 Then ' VER_NT_SERVER or VER_NT_DOMAIN_CONTROLLER
                    If m_osinfo.eOSVersion = Windows2003 Then
                        If sysnfo.wProcessorArchitecture = 6 Then ' PROCESSOR_ARCHITECTURE_IA64
                            If m_osinfo.wSuiteMask And &H80& Then ' VER_SUITE_DATACENTER
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Datacenter Edition for Itanium-based Systems"
                                m_osinfo.eOSEdition = edDatacenterItanium
                            ElseIf m_osinfo.wSuiteMask And &H2& Then ' VER_SUITE_ENTERPRISE
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Enterprise Edition for Itanium-based Systems"
                                m_osinfo.eOSEdition = edEnterpriseItanium
                            End If
                        ElseIf sysnfo.wProcessorArchitecture = 9 Then ' PROCESSOR_ARCHITECTURE_AMD64
                            If m_osinfo.wSuiteMask And &H80& Then ' VER_SUITE_DATACENTER
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Datacenter x64 Edition"
                                m_osinfo.eOSEdition = edDatacenter64
                            ElseIf m_osinfo.wSuiteMask And &H2& Then ' VER_SUITE_ENTERPRISE
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Enterprise x64 Edition"
                                m_osinfo.eOSEdition = edEnterprise64
                            Else
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Standard x64 Edition"
                                m_osinfo.eOSEdition = edStandard64
                            End If
                        Else
                            If m_osinfo.wSuiteMask And &H80& Then ' VER_SUITE_DATACENTER
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Datacenter Edition"
                                m_osinfo.eOSEdition = edDatacenter
                            ElseIf m_osinfo.wSuiteMask And &H2& Then ' VER_SUITE_ENTERPRISE
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Enterprise Edition"
                                m_osinfo.eOSEdition = edEnterprise
                            ElseIf m_osinfo.wSuiteMask And &H400& Then ' VER_SUITE_BLADE
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Web Edition"
                                m_osinfo.eOSEdition = edWeb
                            Else
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & ", Standard Edition"
                            End If
                        End If
                    ElseIf m_osinfo.eOSVersion = Windows2000 Then
                        If m_osinfo.wSuiteMask And &H80& Then ' VER_SUITE_DATACENTER
                            m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Datacenter Server"
                            m_osinfo.eOSEdition = edDatacenterServer
                        ElseIf m_osinfo.wSuiteMask And &H2& Then ' VER_SUITE_ENTERPRISE
                            m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Advanced Server"
                            m_osinfo.eOSEdition = edAdvancedServer
                        Else
                            m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Server"
                            m_osinfo.eOSEdition = edServer
                        End If
                    Else ' Windows NT 4.0
                        If m_osinfo.wSuiteMask And &H2& Then ' VER_SUITE_ENTERPRISE
                            m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Server 4.0, Enterprise Edition"
                            m_osinfo.eOSEdition = edEnterpriseServer
                        Else
                            m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Server 4.0"
                            m_osinfo.eOSEdition = edServer
                        End If
                    End If
                End If
            Else ' Test for specific product on Windows NT 4.0 SP5 and earlier
                sBuffer = String(128, 0)
                lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\ProductOptions", 0&, KEY_QUERY_VALUE, lKey)
                
                If lRet = 0 Then
                    lRet = RegQueryValueEx(lKey, "ProductType", 0&, 0&, sBuffer, 128)
                    If lRet = 0 And lRet < 128 Then
                        sBuffer = UCase$(Left$(sBuffer, lRet))
                        Select Case sBuffer
                            Case "WINNT"
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Workstation "
                                m_osinfo.eOSEdition = edWorkstation
                            Case "LANMANNT"
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Server "
                                m_osinfo.eOSEdition = edServer
                            Case "SERVERNT"
                                m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Advanced Server "
                                m_osinfo.eOSEdition = edAdvancedServer
                        End Select
                        m_osinfo.sOSSignature = m_osinfo.sOSSignature & m_osinfo.dwMajorVersion & "." & m_osinfo.dwMinorVersion
                    End If
                    RegCloseKey lKey
                End If
            End If
            
            If m_osinfo.dwMajorVersion = 4 And LCase$(m_osinfo.szCSDVersion) = "service pack 6" Then
                If m_osinfo.wServicePackMajor = 0 Then m_osinfo.wServicePackMajor = 6
                lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Hotfix\Q246009", 0, KEY_QUERY_VALUE, lKey)
                If lRet = 0 Then
                    m_osinfo.szCSDVersion = "Service Pack 6a"
                    If m_osinfo.wServicePackMinor = 0 Then m_osinfo.wServicePackMinor = 1
                    RegCloseKey lKey
                End If
            ElseIf m_osinfo.wServicePackMajor = 0 Then
                lRet = InStr(1, m_osinfo.szCSDVersion, "Service Pack ", vbTextCompare)
                If lRet > 0 Then
                    sBuffer = Mid$(m_osinfo.szCSDVersion, lRet + 13)
                    If InStr(1, sBuffer, ".") > 0 Then
                        m_osinfo.wServicePackMajor = Val(Split(sBuffer, ".")(0))
                        m_osinfo.wServicePackMinor = Val(Split(sBuffer, ".")(1))
                    Else
                        m_osinfo.wServicePackMajor = Val(sBuffer)
                    End If
                End If
            End If
        
        Case 1 ' VER_PLATFORM_WIN32_WINDOWS
            m_osinfo.dwBuildNumber = LoWord(m_osinfo.dwBuildNumber)
            If m_osinfo.dwMajorVersion = 4 And m_osinfo.dwMinorVersion = 0 Then
                m_osinfo.eOSVersion = Windows95
                m_osinfo.sOSSignature = "Microsoft Windows 95"
                If InStr(1, "BCbc", Mid$(m_osinfo.szCSDVersion, 2, 1)) > 0 Then
                    m_osinfo.sOSSignature = m_osinfo.sOSSignature & " OSR2"
                    m_osinfo.eOSEdition = edOSR2
                End If
                m_osinfo.szCSDVersion = vbNullString
            ElseIf m_osinfo.dwMajorVersion = 4 And m_osinfo.dwMinorVersion = 10 Then
                m_osinfo.eOSVersion = Windows98
                m_osinfo.sOSSignature = "Microsoft Windows 98"
                If UCase$(Mid$(m_osinfo.szCSDVersion, 2, 1)) = "A" Then
                    m_osinfo.sOSSignature = m_osinfo.sOSSignature & " SE"
                    m_osinfo.eOSEdition = edSecondEdition
                End If
                m_osinfo.szCSDVersion = vbNullString
            ElseIf m_osinfo.dwMajorVersion = 4 And m_osinfo.dwMinorVersion = 90 Then
                m_osinfo.eOSVersion = WindowsMillenium
                m_osinfo.sOSSignature = "Microsoft Windows Millenium Edition"
            End If
        
        Case 0 ' VER_PLATFORM_WIN32s
            
            m_osinfo.eOSVersion = Windows32s
            m_osinfo.sOSSignature = "Microsoft Win32s"
            
    End Select
    
    m_osinfo.sOSSignature = m_osinfo.sOSSignature & " (Version " & _
                            m_osinfo.dwMajorVersion & "." & m_osinfo.dwMinorVersion & _
                            " Build " & m_osinfo.dwBuildNumber
                            
    If bOsVersionInfoEx And m_osinfo.wServicePackMajor > 0 Then
        m_osinfo.sOSSignature = m_osinfo.sOSSignature & " Service Pack " & m_osinfo.wServicePackMajor & _
                                IIf(m_osinfo.wServicePackMinor > 0, "." & m_osinfo.wServicePackMinor, "")
    End If
    
    m_osinfo.sOSSignature = m_osinfo.sOSSignature & ")"
    
    GatherOSInfo = True
    
End Function
'

Private Function GetNativeSystemInfo(ByRef si As SYSTEM_INFO) As Boolean
    
    Dim lRet    As Long
    
    lRet = GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo")
    
    If lRet Then
        GetNativeSystemInfoAPI si
        GetNativeSystemInfo = True
    End If
    
End Function
'

Private Sub stringToArray(ByVal sString As String, ByRef bArray() As Byte, ByVal lMaxSize As Long)

    Dim b()     As Byte
    Dim i       As Long
    Dim j       As Long
    
    If Len(sString) > 0 Then
        b = sString
        For i = LBound(b) To UBound(b)
            bArray(i) = b(i)
            If (i = (lMaxSize - 2)) Then Exit For
        Next i
        For j = i To lMaxSize - 1
            bArray(j) = 0
        Next j
    End If
    
End Sub
'

Private Sub arrayToString(ByRef bString() As Byte, ByRef sString As String)
    
    Dim i As Long
    
    sString = vbNullString
    
    For i = LBound(bString) To UBound(bString)
        sString = sString & Chr$(bString(i))
    Next
    
End Sub
'


Private Function GetTrayHandle() As Boolean
    
    If IsWindow(m_trayHwnd) Then
        
        GetTrayHandle = True
        
    Else
        
        Dim taskbar As Long
        
        taskbar = FindWindow("Shell_TrayWnd", vbNullString)
        
        If taskbar = 0 Then taskbar = GetChild(ClassName:="Shell_TrayWnd")
        
        If taskbar <> 0 Then
            
            m_trayHwnd = FindWindowEx(taskbar, 0, "TrayNotifyWnd", vbNullString)
            
            If IsWindow(m_trayHwnd) Then
                
                GetTrayHandle = True
                
            Else
                
                m_trayHwnd = GetChild(Title:="Notification*", UseLike:=True)
                
                GetTrayHandle = IsWindow(m_trayHwnd) <> 0
                
            End If
            
        End If
        
    End If
    
End Function
'

Private Function GetTaskbarHandle() As Boolean
    
    If IsWindow(m_taskbarHwnd) Then
        
        GetTaskbarHandle = True
    
    Else
        
        Dim taskbar As Long
        
        taskbar = FindWindow("Shell_TrayWnd", vbNullString)
        
        If taskbar = 0 Then taskbar = GetChild(ClassName:="Shell_TrayWnd")
        
        If taskbar <> 0 Then
            
            m_taskbarHwnd = GetChild(taskbar, "Toolbar*", "Running Applications", True)
        
        Else
            
            m_taskbarHwnd = GetChild(0, "Toolbar*", "Running Applications", True)
        
        End If
        
        GetTaskbarHandle = IsWindow(m_taskbarHwnd) <> 0
    
    End If
    
End Function
'

Public Function LookupWindow(Optional ByVal ClassName As String, Optional ByVal Title As String, Optional UseLike As Boolean = False, Optional ByVal Parent As Long) As Long
    LookupWindow = GetChild(Parent, ClassName, Title, UseLike)
End Function
'

Private Function GetChild(Optional ByVal hwnd As Long, Optional ByVal ClassName As String, Optional ByVal Title As String, Optional UseLike As Boolean = False)
    
    Dim lHandle     As Long
    Dim lRet        As Long
    Dim sBuffer     As String
    Dim bClassMatch As Boolean
    Dim bTitleMatch As Boolean
    
    Do
        lHandle = FindWindowEx(hwnd, lHandle, vbNullString, vbNullString)
        
        bClassMatch = False
        bTitleMatch = False
        
        If lHandle <> 0 Then
            
            bClassMatch = LenB(ClassName) = 0
            bTitleMatch = LenB(Title) = 0
            
            If Not bClassMatch Then
                    
                sBuffer = String(160, 0)
                lRet = GetClassName(lHandle, sBuffer, 160)
                sBuffer = Left$(sBuffer, lRet)
                
                bClassMatch = (UseLike And (sBuffer Like ClassName)) Or (Not UseLike And (ClassName = sBuffer))
            
            End If
            
            If Not bTitleMatch Then
                
                sBuffer = String(160, 0)
                lRet = GetWindowText(lHandle, sBuffer, 160)
                sBuffer = Left$(sBuffer, lRet)
                
                bTitleMatch = (UseLike And (sBuffer Like Title)) Or (Not UseLike And (Title = sBuffer))
            
            End If
            
            If bClassMatch And bTitleMatch Then
                
                GetChild = lHandle
                Exit Function
            
            Else
                
                lRet = GetChild(lHandle, ClassName, Title, UseLike)
                
                If lRet <> 0 Then
                    
                    GetChild = lRet
                    Exit Function
                
                End If
                
            End If
        
        End If
    
    Loop While lHandle

End Function
'

Private Sub Class_Terminate()
  sc_Terminate                                                              'Terminate all subclassing
End Sub

'-SelfSub code------------------------------------------------------------------------------------
Private Function sc_Subclass(ByVal lng_hWnd As Long, _
                    Optional ByVal lParamUser As Long = 0, _
                    Optional ByVal nOrdinal As Long = 1, _
                    Optional ByVal oCallback As Object = Nothing, _
                    Optional ByVal bIdeSafety As Boolean = True) As Boolean 'Subclass the specified window handle
'*************************************************************************************************
'* lng_hWnd   - Handle of the window to subclass
'* lParamUser - Optional, user-defined callback parameter
'* nOrdinal   - Optional, ordinal index of the callback procedure. 1 = last private method, 2 = second last private method, etc.
'* oCallback  - Optional, the object that will receive the callback. If undefined, callbacks are sent to this object's instance
'* bIdeSafety - Optional, enable/disable IDE safety measures. NB: you should really only disable IDE safety in a UserControl for design-time subclassing
'*************************************************************************************************
Const CODE_LEN      As Long = 260                                           'Thunk length in bytes
Const MEM_LEN       As Long = CODE_LEN + (8 * (MSG_ENTRIES + 1))            'Bytes to allocate per thunk, data + code + msg tables
Const PAGE_RWX      As Long = &H40&                                         'Allocate executable memory
Const MEM_COMMIT    As Long = &H1000&                                       'Commit allocated memory
Const MEM_RELEASE   As Long = &H8000&                                       'Release allocated memory flag
Const IDX_EBMODE    As Long = 3                                             'Thunk data index of the EbMode function address
Const IDX_CWP       As Long = 4                                             'Thunk data index of the CallWindowProc function address
Const IDX_SWL       As Long = 5                                             'Thunk data index of the SetWindowsLong function address
Const IDX_FREE      As Long = 6                                             'Thunk data index of the VirtualFree function address
Const IDX_BADPTR    As Long = 7                                             'Thunk data index of the IsBadCodePtr function address
Const IDX_OWNER     As Long = 8                                             'Thunk data index of the Owner object's vTable address
Const IDX_CALLBACK  As Long = 10                                            'Thunk data index of the callback method address
Const IDX_EBX       As Long = 16                                            'Thunk code patch index of the thunk data
Const SUB_NAME      As String = "sc_Subclass"                               'This routine's name
  Dim nAddr         As Long
  Dim nID           As Long
  Dim nMyID         As Long

  If IsWindow(lng_hWnd) = 0 Then                                            'Ensure the window handle is valid
    zError SUB_NAME, "Invalid window handle"
    Exit Function
  End If

  nMyID = GetCurrentProcessId                                               'Get this process's ID
  GetWindowThreadProcessId lng_hWnd, nID                                    'Get the process ID associated with the window handle
  If nID <> nMyID Then                                                      'Ensure that the window handle doesn't belong to another process
    zError SUB_NAME, "Window handle belongs to another process"
    Exit Function
  End If

  If oCallback Is Nothing Then                                              'If the user hasn't specified the callback owner
    Set oCallback = Me                                                      'Then it is me
  End If

  nAddr = zAddressOf(oCallback, nOrdinal)                                   'Get the address of the specified ordinal method
  If nAddr = 0 Then                                                         'Ensure that we've found the ordinal method
    zError SUB_NAME, "Callback method not found"
    Exit Function
  End If

  If z_Funk Is Nothing Then                                                 'If this is the first time through, do the one-time initialization
    Set z_Funk = New Collection                                             'Create the hWnd/thunk-address collection
    z_Sc(14) = &HD231C031: z_Sc(15) = &HBBE58960: z_Sc(17) = &H4339F631: z_Sc(18) = &H4A21750C: z_Sc(19) = &HE82C7B8B: z_Sc(20) = &H74&: z_Sc(21) = &H75147539: z_Sc(22) = &H21E80F: z_Sc(23) = &HD2310000: z_Sc(24) = &HE8307B8B: z_Sc(25) = &H60&: z_Sc(26) = &H10C261: z_Sc(27) = &H830C53FF: z_Sc(28) = &HD77401F8: z_Sc(29) = &H2874C085: z_Sc(30) = &H2E8&: z_Sc(31) = &HFFE9EB00: z_Sc(32) = &H75FF3075: z_Sc(33) = &H2875FF2C: z_Sc(34) = &HFF2475FF: z_Sc(35) = &H3FF2473: z_Sc(36) = &H891053FF: z_Sc(37) = &HBFF1C45: z_Sc(38) = &H73396775: z_Sc(39) = &H58627404
    z_Sc(40) = &H6A2473FF: z_Sc(41) = &H873FFFC: z_Sc(42) = &H891453FF: z_Sc(43) = &H7589285D: z_Sc(44) = &H3045C72C: z_Sc(45) = &H8000&: z_Sc(46) = &H8920458B: z_Sc(47) = &H4589145D: z_Sc(48) = &HC4836124: z_Sc(49) = &H1862FF04: z_Sc(50) = &H35E30F8B: z_Sc(51) = &HA78C985: z_Sc(52) = &H8B04C783: z_Sc(53) = &HAFF22845: z_Sc(54) = &H73FF2775: z_Sc(55) = &H1C53FF28: z_Sc(56) = &H438D1F75: z_Sc(57) = &H144D8D34: z_Sc(58) = &H1C458D50: z_Sc(59) = &HFF3075FF: z_Sc(60) = &H75FF2C75: z_Sc(61) = &H873FF28: z_Sc(62) = &HFF525150: z_Sc(63) = &H53FF2073: z_Sc(64) = &HC328&

    z_Sc(IDX_CWP) = zFnAddr("user32", "CallWindowProcA")                    'Store CallWindowProc function address in the thunk data
    z_Sc(IDX_SWL) = zFnAddr("user32", "SetWindowLongA")                     'Store the SetWindowLong function address in the thunk data
    z_Sc(IDX_FREE) = zFnAddr("kernel32", "VirtualFree")                     'Store the VirtualFree function address in the thunk data
    z_Sc(IDX_BADPTR) = zFnAddr("kernel32", "IsBadCodePtr")                  'Store the IsBadCodePtr function address in the thunk data
  End If

  z_ScMem = VirtualAlloc(0, MEM_LEN, MEM_COMMIT, PAGE_RWX)                  'Allocate executable memory

  If z_ScMem <> 0 Then                                                      'Ensure the allocation succeeded
    On Error GoTo CatchDoubleSub                                            'Catch double subclassing
      z_Funk.Add z_ScMem, "h" & lng_hWnd                                    'Add the hWnd/thunk-address to the collection
    On Error GoTo 0

    If bIdeSafety Then                                                      'If the user wants IDE protection
      z_Sc(IDX_EBMODE) = zFnAddr("vba6", "EbMode")                          'Store the EbMode function address in the thunk data
    End If

    z_Sc(IDX_EBX) = z_ScMem                                                 'Patch the thunk data address
    z_Sc(IDX_HWND) = lng_hWnd                                               'Store the window handle in the thunk data
    z_Sc(IDX_BTABLE) = z_ScMem + CODE_LEN                                   'Store the address of the before table in the thunk data
    z_Sc(IDX_ATABLE) = z_ScMem + CODE_LEN + ((MSG_ENTRIES + 1) * 4)         'Store the address of the after table in the thunk data
    z_Sc(IDX_OWNER) = ObjPtr(oCallback)                                     'Store the callback owner's object address in the thunk data
    z_Sc(IDX_CALLBACK) = nAddr                                              'Store the callback address in the thunk data
    z_Sc(IDX_PARM_USER) = lParamUser                                        'Store the lParamUser callback parameter in the thunk data

    nAddr = SetWindowLongA(lng_hWnd, GWL_WNDPROC, z_ScMem + WNDPROC_OFF)    'Set the new WndProc, return the address of the original WndProc
    If nAddr = 0 Then                                                       'Ensure the new WndProc was set correctly
      zError SUB_NAME, "SetWindowLong failed, error #" & Err.LastDllError
      GoTo ReleaseMemory
    End If

    z_Sc(IDX_WNDPROC) = nAddr                                               'Store the original WndProc address in the thunk data
    RtlMoveMemory z_ScMem, VarPtr(z_Sc(0)), CODE_LEN                        'Copy the thunk code/data to the allocated memory
    sc_Subclass = True                                                      'Indicate success
  Else
    zError SUB_NAME, "VirtualAlloc failed, error: " & Err.LastDllError
  End If

  Exit Function                                                             'Exit sc_Subclass

CatchDoubleSub:
  zError SUB_NAME, "Window handle is already subclassed"

ReleaseMemory:
  VirtualFree z_ScMem, 0, MEM_RELEASE                                       'sc_Subclass has failed after memory allocation, so release the memory
End Function

'Terminate all subclassing
Private Sub sc_Terminate()
  Dim i As Long

  If Not (z_Funk Is Nothing) Then                                           'Ensure that subclassing has been started
    With z_Funk
      For i = .Count To 1 Step -1                                           'Loop through the collection of window handles in reverse order
        z_ScMem = .Item(i)                                                  'Get the thunk address
        If IsBadCodePtr(z_ScMem) = 0 Then                                   'Ensure that the thunk hasn't already released its memory
          sc_UnSubclass zData(IDX_HWND)                                     'UnSubclass
        End If
      Next i                                                                'Next member of the collection
    End With
    Set z_Funk = Nothing                                                    'Destroy the hWnd/thunk-address collection
  End If
End Sub

'UnSubclass the specified window handle
Private Sub sc_UnSubclass(ByVal lng_hWnd As Long)
  If z_Funk Is Nothing Then                                                 'Ensure that subclassing has been started
    zError "sc_UnSubclass", "Window handle isn't subclassed"
  Else
    If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                           'Ensure that the thunk hasn't already released its memory
      zData(IDX_SHUTDOWN) = -1                                              'Set the shutdown indicator
      zDelMsg ALL_MESSAGES, IDX_BTABLE                                      'Delete all before messages
      zDelMsg ALL_MESSAGES, IDX_ATABLE                                      'Delete all after messages
    End If
    z_Funk.Remove "h" & lng_hWnd                                            'Remove the specified window handle from the collection
  End If
End Sub

'Add the message value to the window handle's specified callback table
Private Sub sc_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = eMsgWhen.MSG_AFTER)
  If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                             'Ensure that the thunk hasn't already released its memory
    If When And MSG_BEFORE Then                                             'If the message is to be added to the before original WndProc table...
      zAddMsg uMsg, IDX_BTABLE                                              'Add the message to the before table
    End If
    If When And MSG_AFTER Then                                              'If message is to be added to the after original WndProc table...
      zAddMsg uMsg, IDX_ATABLE                                              'Add the message to the after table
    End If
  End If
End Sub

'Delete the message value from the window handle's specified callback table
Private Sub sc_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = eMsgWhen.MSG_AFTER)
  If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                             'Ensure that the thunk hasn't already released its memory
    If When And MSG_BEFORE Then                                             'If the message is to be deleted from the before original WndProc table...
      zDelMsg uMsg, IDX_BTABLE                                              'Delete the message from the before table
    End If
    If When And MSG_AFTER Then                                              'If the message is to be deleted from the after original WndProc table...
      zDelMsg uMsg, IDX_ATABLE                                              'Delete the message from the after table
    End If
  End If
End Sub

'Call the original WndProc
Private Function sc_CallOrigWndProc(ByVal lng_hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                             'Ensure that the thunk hasn't already released its memory
    sc_CallOrigWndProc = _
        CallWindowProcA(zData(IDX_WNDPROC), lng_hWnd, uMsg, wParam, lParam) 'Call the original WndProc of the passed window handle parameter
  End If
End Function

'Get the subclasser lParamUser callback parameter
Private Property Get sc_lParamUser(ByVal lng_hWnd As Long) As Long
  If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                             'Ensure that the thunk hasn't already released its memory
    sc_lParamUser = zData(IDX_PARM_USER)                                    'Get the lParamUser callback parameter
  End If
End Property

'Let the subclasser lParamUser callback parameter
Private Property Let sc_lParamUser(ByVal lng_hWnd As Long, ByVal NewValue As Long)
  If IsBadCodePtr(zMap_hWnd(lng_hWnd)) = 0 Then                             'Ensure that the thunk hasn't already released its memory
    zData(IDX_PARM_USER) = NewValue                                         'Set the lParamUser callback parameter
  End If
End Property

'-The following routines are exclusively for the sc_ subclass routines----------------------------

'Add the message to the specified table of the window handle
Private Sub zAddMsg(ByVal uMsg As Long, ByVal nTable As Long)
  Dim nCount As Long                                                        'Table entry count
  Dim nBase  As Long                                                        'Remember z_ScMem
  Dim i      As Long                                                        'Loop index

  nBase = z_ScMem                                                            'Remember z_ScMem so that we can restore its value on exit
  z_ScMem = zData(nTable)                                                    'Map zData() to the specified table

  If uMsg = ALL_MESSAGES Then                                               'If ALL_MESSAGES are being added to the table...
    nCount = ALL_MESSAGES                                                   'Set the table entry count to ALL_MESSAGES
  Else
    nCount = zData(0)                                                       'Get the current table entry count
    If nCount >= MSG_ENTRIES Then                                           'Check for message table overflow
      zError "zAddMsg", "Message table overflow. Either increase the value of Const MSG_ENTRIES or use ALL_MESSAGES instead of specific message values"
      GoTo Bail
    End If

    For i = 1 To nCount                                                     'Loop through the table entries
      If zData(i) = 0 Then                                                  'If the element is free...
        zData(i) = uMsg                                                     'Use this element
        GoTo Bail                                                           'Bail
      ElseIf zData(i) = uMsg Then                                           'If the message is already in the table...
        GoTo Bail                                                           'Bail
      End If
    Next i                                                                  'Next message table entry

    nCount = i                                                              'On drop through: i = nCount + 1, the new table entry count
    zData(nCount) = uMsg                                                    'Store the message in the appended table entry
  End If

  zData(0) = nCount                                                         'Store the new table entry count
Bail:
  z_ScMem = nBase                                                           'Restore the value of z_ScMem
End Sub

'Delete the message from the specified table of the window handle
Private Sub zDelMsg(ByVal uMsg As Long, ByVal nTable As Long)
  Dim nCount As Long                                                        'Table entry count
  Dim nBase  As Long                                                        'Remember z_ScMem
  Dim i      As Long                                                        'Loop index

  nBase = z_ScMem                                                           'Remember z_ScMem so that we can restore its value on exit
  z_ScMem = zData(nTable)                                                   'Map zData() to the specified table

  If uMsg = ALL_MESSAGES Then                                               'If ALL_MESSAGES are being deleted from the table...
    zData(0) = 0                                                            'Zero the table entry count
  Else
    nCount = zData(0)                                                       'Get the table entry count

    For i = 1 To nCount                                                     'Loop through the table entries
      If zData(i) = uMsg Then                                               'If the message is found...
        zData(i) = 0                                                        'Null the msg value -- also frees the element for re-use
        GoTo Bail                                                           'Bail
      End If
    Next i                                                                  'Next message table entry

    zError "zDelMsg", "Message &H" & Hex$(uMsg) & " not found in table"
  End If

Bail:
  z_ScMem = nBase                                                           'Restore the value of z_ScMem
End Sub

'Error handler
Private Sub zError(ByVal sRoutine As String, ByVal sMsg As String)
  App.LogEvent TypeName(Me) & "." & sRoutine & ": " & sMsg, vbLogEventTypeError
  MsgBox sMsg & ".", vbExclamation + vbApplicationModal, "Error in " & TypeName(Me) & "." & sRoutine
End Sub

'Return the address of the specified DLL/procedure
Private Function zFnAddr(ByVal sDLL As String, ByVal sProc As String) As Long
  zFnAddr = GetProcAddress(GetModuleHandleA(sDLL), sProc)                   'Get the specified procedure address
  Debug.Assert zFnAddr                                                      'In the IDE, validate that the procedure address was located
End Function

'Map zData() to the thunk address for the specified window handle
Private Function zMap_hWnd(ByVal lng_hWnd As Long) As Long
  If z_Funk Is Nothing Then                                                 'Ensure that subclassing has been started
    zError "zMap_hWnd", "Subclassing hasn't been started"
  Else
    On Error GoTo Catch                                                     'Catch unsubclassed window handles
    z_ScMem = z_Funk("h" & lng_hWnd)                                        'Get the thunk address
    zMap_hWnd = z_ScMem
  End If

  Exit Function                                                             'Exit returning the thunk address

Catch:
  zError "zMap_hWnd", "Window handle isn't subclassed"
End Function

'Return the address of the specified ordinal method on the oCallback object, 1 = last private method, 2 = second last private method, etc
Private Function zAddressOf(ByVal oCallback As Object, ByVal nOrdinal As Long) As Long
  Dim bSub  As Byte                                                         'Value we expect to find pointed at by a vTable method entry
  Dim bVal  As Byte
  Dim nAddr As Long                                                         'Address of the vTable
  Dim i     As Long                                                         'Loop index
  Dim j     As Long                                                         'Loop limit

  RtlMoveMemory VarPtr(nAddr), ObjPtr(oCallback), 4                         'Get the address of the callback object's instance
  If Not zProbe(nAddr + &H1C, i, bSub) Then                                 'Probe for a Class method
    If Not zProbe(nAddr + &H6F8, i, bSub) Then                              'Probe for a Form method
      If Not zProbe(nAddr + &H7A4, i, bSub) Then                            'Probe for a UserControl method
        Exit Function                                                       'Bail...
      End If
    End If
  End If

  i = i + 4                                                                 'Bump to the next entry
  j = i + 1024                                                              'Set a reasonable limit, scan 256 vTable entries
  Do While i < j
    RtlMoveMemory VarPtr(nAddr), i, 4                                       'Get the address stored in this vTable entry

    If IsBadCodePtr(nAddr) Then                                             'Is the entry an invalid code address?
      RtlMoveMemory VarPtr(zAddressOf), i - (nOrdinal * 4), 4               'Return the specified vTable entry address
      Exit Do                                                               'Bad method signature, quit loop
    End If

    RtlMoveMemory VarPtr(bVal), nAddr, 1                                    'Get the byte pointed to by the vTable entry
    If bVal <> bSub Then                                                    'If the byte doesn't match the expected value...
      RtlMoveMemory VarPtr(zAddressOf), i - (nOrdinal * 4), 4               'Return the specified vTable entry address
      Exit Do                                                               'Bad method signature, quit loop
    End If

    i = i + 4                                                             'Next vTable entry
  Loop
End Function

'Probe at the specified start address for a method signature
Private Function zProbe(ByVal nStart As Long, ByRef nMethod As Long, ByRef bSub As Byte) As Boolean
  Dim bVal    As Byte
  Dim nAddr   As Long
  Dim nLimit  As Long
  Dim nEntry  As Long

  nAddr = nStart                                                            'Start address
  nLimit = nAddr + 32                                                       'Probe eight entries
  Do While nAddr < nLimit                                                   'While we've not reached our probe depth
    RtlMoveMemory VarPtr(nEntry), nAddr, 4                                  'Get the vTable entry

    If nEntry <> 0 Then                                                     'If not an implemented interface
      RtlMoveMemory VarPtr(bVal), nEntry, 1                                 'Get the value pointed at by the vTable entry
      If bVal = &H33 Or bVal = &HE9 Then                                    'Check for a native or pcode method signature
        nMethod = nAddr                                                     'Store the vTable entry
        bSub = bVal                                                         'Store the found method signature
        zProbe = True                                                       'Indicate success
        Exit Function                                                       'Return
      End If
    End If

    nAddr = nAddr + 4                                                       'Next vTable entry
  Loop
End Function

Private Property Get zData(ByVal nIndex As Long) As Long
  RtlMoveMemory VarPtr(zData), z_ScMem + (nIndex * 4), 4
End Property

Private Property Let zData(ByVal nIndex As Long, ByVal nValue As Long)
  RtlMoveMemory z_ScMem + (nIndex * 4), VarPtr(nValue), 4
End Property

'-Subclass callback, usually ordinal #1, the last method in this source file----------------------
Private Sub zWndProc1(ByVal bBefore As Boolean, _
                      ByRef bHandled As Boolean, _
                      ByRef lReturn As Long, _
                      ByVal lng_hWnd As Long, _
                      ByVal uMsg As Long, _
                      ByVal wParam As Long, _
                      ByVal lParam As Long, _
                      ByRef lParamUser As Long)
'*************************************************************************************************
'* bBefore    - Indicates whether the callback is before or after the original WndProc. Usually
'*              you will know unless the callback for the uMsg value is specified as
'*              MSG_BEFORE_AFTER (both before and after the original WndProc).
'* bHandled   - In a before original WndProc callback, setting bHandled to True will prevent the
'*              message being passed to the original WndProc and (if set to do so) the after
'*              original WndProc callback.
'* lReturn    - WndProc return value. Set as per the MSDN documentation for the message value,
'*              and/or, in an after the original WndProc callback, act on the return value as set
'*              by the original WndProc.
'* lng_hWnd   - Window handle.
'* uMsg       - Message value.
'* wParam     - Message related data.
'* lParam     - Message related data.
'* lParamUser - User-defined callback parameter
'*************************************************************************************************

    Dim minmax          As MINMAXINFO
    Dim eCommand        As AppCommandConstants
    Dim bImplemented    As Boolean
    Dim lHandle         As Long
    Dim eDevice         As InputDeviceConstants
    Dim eVirtualKey     As VirtualKeyConstants
    Dim hdr             As DEV_BROADCAST_HDR
    Dim vol             As DEV_BROADCAST_VOLUME
    Dim drv             As String
    Dim b               As Byte
    Dim lRet            As Long
    Dim rc              As RECT
    Dim lWidth          As Long
    Dim lHeight         As Long
    Dim bChange         As Boolean
    Dim bBuffer()       As Byte
    Dim sBuffer         As String
    Dim lCount          As Long
    Dim lIndex          As Long
    Dim btnInfo         As TBBUTTONINFO
    

    Select Case uMsg

        ' sent to a window when the size or position of the window is about to change
        Case WM_GETMINMAXINFO

            RtlMoveMemory VarPtr(minmax), lParam, LenB(minmax)

            If m_minmax.MaxPosition.x <> 0 Then minmax.MaxPosition.x = m_minmax.MaxPosition.x
            If m_minmax.MaxPosition.y <> 0 Then minmax.MaxPosition.y = m_minmax.MaxPosition.y
            If m_minmax.MaxSize.x <> -1 Then minmax.MaxSize.x = m_minmax.MaxSize.x
            If m_minmax.MaxSize.y <> -1 Then minmax.MaxSize.y = m_minmax.MaxSize.y
            If m_minmax.MaxTrackSize.x <> -1 Then minmax.MaxTrackSize.x = m_minmax.MaxTrackSize.x
            If m_minmax.MaxTrackSize.y <> -1 Then minmax.MaxTrackSize.y = m_minmax.MaxTrackSize.y
            If m_minmax.MinTrackSize.x <> -1 Then minmax.MinTrackSize.x = m_minmax.MinTrackSize.x
            If m_minmax.MinTrackSize.y <> -1 Then minmax.MinTrackSize.y = m_minmax.MinTrackSize.y

            RtlMoveMemory lParam, VarPtr(minmax), LenB(minmax)

            lReturn = 0


        ' notifies an application with focus when one of new Internet or multimedia events occurs.
        Case WM_APPCOMMAND
            
            eCommand = HiWord(lParam) And Not FAPPCOMMAND_MASK
            bImplemented = False
            lHandle = wParam
            eDevice = HiWord(lParam) And FAPPCOMMAND_MASK
            eVirtualKey = LoWord(lParam)
            

            RaiseEvent onAppCommand(eCommand, bImplemented, lHandle, eDevice, eVirtualKey)

            If bImplemented Then

                bHandled = True
                lReturn = 1

            End If
        
        
        ' notifies an application of a change to the hardware configuration of a device or the computer.
        Case WM_DEVICECHANGE
            
            If lParam <> 0 Then
                
                RtlMoveMemory VarPtr(hdr), lParam, LenB(hdr)
                
                If wParam = DBT_DEVICEARRIVAL Or wParam = DBT_DEVICEREMOVECOMPLETE Then
                    
                    If hdr.dbch_devicetype = DBT_DEVTYP_VOLUME Then
                    
                        RtlMoveMemory VarPtr(vol), lParam, LenB(vol)
                        
                        For b = 0 To 25
        
                            If (vol.dbcv_unitmask And 2 ^ b) Then
                                
                                drv = Chr$(Asc("A") + b)
                                
                                Exit For
                                
                            End If
                        
                        Next
                        
                        If wParam = DBT_DEVICEARRIVAL Then
                        
                            lRet = GetFileAttributes(drv & ":\")
                            
                            If (lRet <> &HFFFFFFFF) And (lRet And FILE_ATTRIBUTE_DIRECTORY) Then
                                
                                RaiseEvent onMediaArrived(drv)
                            
                            End If
                            
                        ElseIf LenB(drv) = 2 Then
                            
                            RaiseEvent onMediaRemoved(drv)
                    
                        End If
                        
                    End If
                    
                End If
                
            End If
            
            
        ' posted to a window when the cursor moves.
        Case WM_MOUSEMOVE
            
            If m_easyMove And (wParam And vkLeftButton) Then
                
                ReleaseCapture
                SendMessage m_hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0
            
            End If
        
        
        ' sent to a window that the user is moving or sizing.
        Case WM_MOVING, WM_SIZING
            
            If m_snap Then
            
                If m_parent = m_desktop Then
                    If SystemParametersInfo(SPI_GETWORKAREA, 0&, rc, 0&) = 0 Then
                        GetClientRect m_parent, rc
                    End If
                Else
                    GetClientRect m_parent, rc
                End If
                
                RtlMoveMemory VarPtr(m_rect), lParam, LenB(m_rect)
                
                lWidth = m_rect.Right - m_rect.Left
                lHeight = m_rect.Bottom - m_rect.Top
                
                With m_rect
                    
                    bChange = False
                    
                    If .Left >= rc.Left - m_snapDistance / 2 And .Left <= rc.Left + m_snapDistance Then
                        .Left = rc.Left
                        If uMsg = WM_MOVING Then .Right = rc.Left + lWidth
                        bChange = True
                    ElseIf .Right >= rc.Right - m_snapDistance And .Right <= rc.Right + m_snapDistance / 2 Then
                        If uMsg = WM_MOVING Then .Left = rc.Right - lWidth
                        .Right = rc.Right
                        bChange = True
                    End If
                    
                    If .Top >= rc.Top - m_snapDistance / 2 And .Top <= rc.Top + m_snapDistance Then
                        .Top = rc.Top
                        If uMsg = WM_MOVING Then .Bottom = rc.Top + lHeight
                        bChange = True
                    ElseIf .Bottom >= rc.Bottom - m_snapDistance And .Bottom <= rc.Bottom + m_snapDistance / 2 Then
                        If uMsg = WM_MOVING Then .Top = rc.Bottom - lHeight
                        .Bottom = rc.Bottom
                        bChange = True
                    End If
                
                End With
                
                If bChange Then RtlMoveMemory lParam, VarPtr(m_rect), LenB(m_rect)
            
            End If
                
        ' sent when a window is being destroyed.
        Case WM_DESTROY
            
            Me.ShowInTray = False
        
        
        ' tray icon action / baloon state change
        Case WM_NOTIFYICON
                
                If Not m_showInTray Then Exit Sub
                
                If lParam >= WM_MOUSEMOVE And lParam <= WM_RBUTTONDBLCLK Then
                    
                    If lParam > WM_MOUSEMOVE Then SetForegroundWindow m_hWnd
                    
                    RaiseEvent onTrayAction(lParam)
                                
                ElseIf lParam >= NIN_BALLOONSHOW And lParam <= NIN_BALLOONUSERCLICK Then
                    
                    RaiseEvent onBaloonAction(lParam)
                    
                End If
        
        ' called when taskbar is recreated (probably due to explorer crash)
        Case WM_TASKBARCREATED
        
            If m_showInTray Then
                
                m_showInTray = False
                Me.ShowInTray = True
                
            End If
        
        
        ' window receives this message when the user chooses a command from the Window menu (formerly known as the system or control menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
        Case WM_SYSCOMMAND
            
            Select Case wParam
                
                Case SC_MINIMIZE, SC_CLOSE
                    
                    If wParam = SC_MINIMIZE And IsIconic(m_hWnd) Then Exit Sub
                    
                    If (m_minimizeToTray And wParam = SC_MINIMIZE) Or (m_closeToTray And wParam = SC_CLOSE) Then
                        
                        If GetTrayHandle Then
                            
                            bHandled = True
                            lReturn = 0
                            
                            If lParam <> 1 Then
                                GetWindowRect m_hWnd, m_rect
                                GetWindowRect m_trayHwnd, rc
                                If rc.Right > rc.Left Then
                                    rc.Left = (rc.Right - rc.Left - 4) \ 2 + rc.Left
                                    rc.Right = rc.Left + 2
                                    rc.Top = (rc.Bottom - rc.Top - 4) \ 2 + rc.Top
                                    rc.Bottom = rc.Top + 2
                                End If
                                DrawAnimatedRects m_hWnd, IDANI_CAPTION, m_rect, rc
                            End If
                            
                            ShowWindow m_hWnd, SW_HIDE
                            ShowWindow m_hWnd, SW_SHOWMINNOACTIVE
                            ShowWindow m_hWnd, SW_HIDE
                            
                        End If
                    End If
                
                
                Case SC_RESTORE
                    
                    If Not IsIconic(m_hWnd) And (m_minimizeToTray Or m_closeToTray) Then
                        
                        If GetTrayHandle Then
                            
                            bHandled = True
                            lReturn = 0
                            
                            If lParam <> 1 Then
                                GetWindowRect m_trayHwnd, rc
                                If rc.Right > rc.Left Then
                                    rc.Left = (rc.Right - rc.Left - 4) \ 2 + rc.Left
                                    rc.Right = rc.Left + 2
                                    rc.Top = (rc.Bottom - rc.Top - 4) \ 2 + rc.Top
                                    rc.Bottom = rc.Top + 2
                                End If
                                DrawAnimatedRects m_hWnd, IDANI_CAPTION, rc, m_rect
                            End If
                            
                            ShowWindow m_hWnd, SW_RESTORE
                            
                        End If
                    End If
            End Select
        
        
        ' sent to a window when the window is about to be hidden or shown.
        Case WM_SHOWWINDOW
                
            If (m_showInTaskbarLast <> m_showInTaskbar) And wParam Then
                m_showInTaskbarLast = m_showInTaskbar
                
                lRet = GetWindowLong(m_hWnd, GWL_EXTSTYLE)
                
                ShowWindow m_hWnd, SW_HIDE
                SetWindowLong m_hWnd, GWL_EXTSTYLE, IIf(m_showInTaskbar, lRet Or WS_EX_APPWINDOW And (Not WS_EX_TOOLWINDOW), lRet And (Not WS_EX_APPWINDOW))
                ShowWindow m_hWnd, SW_SHOW
            
            End If
            
        
        ' sent to the focus window when the mouse wheel is rotated.
        Case WM_MOUSEWHEEL
            
            Dim lLineNumbers    As Long
            
            SystemParametersInfo SPI_GETWHEELSCROLLLINES, 0, lLineNumbers, 0
            If lLineNumbers = 0 Then lLineNumbers = 1
            
            RaiseEvent onMouseWheel(m_form.ScaleX(LoWord(lParam), vbPixels, m_form.ScaleMode), _
                                    m_form.ScaleY(HiWord(lParam), vbPixels, m_form.ScaleMode), _
                                    wParam / 65536 / WHEEL_DELTA * lLineNumbers, LoWord(wParam))
            
            lReturn = 0
        
        
        
        ' sent to both the window being activated and the window being deactivated.
        Case WM_ACTIVATE
            
            Select Case LoWord(wParam)
                
                Case WA_INACTIVE
                    
                    RaiseEvent onDeactivate(HiWord(wParam) <> 0, lParam)
                
                
                Case WA_ACTIVE, WA_CLICKACTIVE
                
                    RaiseEvent onActivate(LoWord(wParam) = WA_CLICKACTIVE, HiWord(wParam) <> 0, lParam)
                    
'                    If m_showInTaskbar Then
'
'                        If GetTaskbarHandle Then
'
'                            lCount = SendMessage(m_taskbarHwnd, TB_BUTTONCOUNT, 0, 0)
'
'                            Const TB_GETBUTTONINFOW As Long = WM_USER + 63
'                            Const TB_SETBUTTONINFOW As Long = WM_USER + 64
'                            Const TB_GETBUTTONINFOA As Long = WM_USER + 65
'                            Const TB_SETBUTTONINFOA As Long = WM_USER + 66
'
'                            Const TBIF_STATE        As Long = &H4
'                            Const TBIF_COMMAND      As Long = &H20
'                            Const TBIF_BYINDEX      As Long = &H80000000
'
'                            btnInfo.cbSize = Len(btnInfo)
'                            btnInfo.dwMask = TBIF_BYINDEX Or TBIF_STATE
'
'                            For lIndex = 0 To lCount - 1
'                                If m_unicode Then
'                                    lRet = SendMessage(m_taskbarHwnd, TB_GETBUTTONINFOW, lIndex, VarPtr(btnInfo))
'                                Else
'                                    lRet = SendMessage(m_taskbarHwnd, TB_GETBUTTONINFOA, lIndex, VarPtr(btnInfo))
'                                End If
'                                Debug.Print lRet
'                            Next
'
'                        End If
'                    End If
            
            End Select
            
    End Select

End Sub
