How to Receive Shell Change Notifications

How to Receive Shell Change Notifications

shchange.gif (9052 bytes) FindFirstChange/FindNextChange notification APIs can be easily used to determine when a file system object has changed. However, this method is best used by an application where polling the physical file system is desired (for example, in creating a Watched Folder as used in Adobe's Acrobat Distiller). Once a change has been detected, the application can proceed to process the change then return to its idle/polling state.

But how does Explorer get updated when a new printer is added in the Printers folder, or a connection is deleted in the Dial-Up Networking folder? When folders or files are moved, deleted or renamed? When registered files are opened and items are added/removed from the recent (Documents) menu? When the recycled bin is added to or emptied, needing an update in the icon displayed? When a scheduled task is added, deleted or modified? When such changes occur system-wide to both physical and virtual folders implemented by namespace extensions, Visual Basic developers must resort to the undocumented API functions SHChangeNotifyRegister and SHChangeNotifyDeregister.

This nifty demo, created by Brad Martinez (VB Programs and Examples), shows how an application can use these undocumented functions to register itself with the shell allowing the application to be notified of any specified changes that were made in any specified part of the namespace, from the desktop folder on down. The demo also provides a rather interesting study of what is actually happening in the namespace as well.

The code, as is Brad's norm, is fully commented. Due to the length of the file, and its extremely descriptive examples, this page may display wider than most on VBnet. I have tried to limit its display width without affecting the commenting, but some I left alone. Therefore, this page will be most easily recreated by performing a Select All on the page, pasting the contents into Notepad, and using the code from there.

As usual, the normal caveat for undocumented APIs apply. Use with care. Although they work now (on both Brad's and my development systems) there's no telling when a new release of Windows may render them ineffective.

BAS Module 1 Code Three BAS modules will be created. The first contains the definitions for the shell API definitions. The second will be for the subclassing, and the third the will handle the notification setup.

Place the following API declare code into the general declarations area of a bas module 1, shell API definitions.


Option Explicit

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright 1996-2001 VBnet, Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' You are free to use this code within your own applications,
' but you are expressly forbidden from selling or otherwise
' distributing this source code without prior written consent.
' This includes both posting free demo projects made from this
' code as well as reproducing the code in text or html format.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'Brought to you by Brad Martinez
'  http://www.mvps.org/btmtz/
'  http://www.mvps.org/ccrp/
'
'Demonstrates how to receive shell change
'notifications (ala "what happens when the
'SHChangeNotify API is called?")
'
'Interpretation of the shell's undocumented
'functions SHChangeNotifyRegister (ordinal 2)
'and SHChangeNotifyDeregister (ordinal 4) would
'not have been possible without the assistance of
'James Holderness. For a complete (and probably
'more accurate) overview of shell change notifications,
'please refer to James'"Shell Notifications" page at
'http://www.geocities.com/SiliconValley/4942/
'------------------------------------------------------

Public Const MAX_PATH As Long = 260

'Defined as an HRESULT that corresponds
'to S_OK.
Public Const ERROR_SUCCESS As Long = 0

Public Type SHFILEINFO   'shfi
  hIcon As Long
  iIcon As Long
  dwAttributes As Long
  szDisplayName As String * MAX_PATH
  szTypeName As String * 80
End Type

'If pidl is invalid, SHGetFileInfoPidl can
'very easily blow up when filling the
'szDisplayName and szTypeName string members
'of the SHFILEINFO struct
Public Type SHFILEINFOBYTE   'sfib
  hIcon As Long
  iIcon As Long
  dwAttributes As Long
  szDisplayName(1 To MAX_PATH) As Byte
  szTypeName(1 To 80) As Byte
End Type

'Special folder values for
'SHGetSpecialFolderLocation and
'SHGetSpecialFolderPath (Shell32.dll v4.71)
Public Enum SHSpecialFolderIDs
  CSIDL_DESKTOP = &H0
  CSIDL_INTERNET = &H1
  CSIDL_PROGRAMS = &H2
  CSIDL_CONTROLS = &H3
  CSIDL_PRINTERS = &H4
  CSIDL_PERSONAL = &H5
  CSIDL_FAVORITES = &H6
  CSIDL_STARTUP = &H7
  CSIDL_RECENT = &H8
  CSIDL_SENDTO = &H9
  CSIDL_BITBUCKET = &HA
  CSIDL_STARTMENU = &HB
  CSIDL_DESKTOPDIRECTORY = &H10
  CSIDL_DRIVES = &H11
  CSIDL_NETWORK = &H12
  CSIDL_NETHOOD = &H13
  CSIDL_FONTS = &H14
  CSIDL_TEMPLATES = &H15
  CSIDL_COMMON_STARTMENU = &H16
  CSIDL_COMMON_PROGRAMS = &H17
  CSIDL_COMMON_STARTUP = &H18
  CSIDL_COMMON_DESKTOPDIRECTORY = &H19
  CSIDL_APPDATA = &H1A
  CSIDL_PRINTHOOD = &H1B
  CSIDL_ALTSTARTUP = &H1D        ''DBCS
  CSIDL_COMMON_ALTSTARTUP = &H1E ''DBCS
  CSIDL_COMMON_FAVORITES = &H1F
  CSIDL_INTERNET_CACHE = &H20
  CSIDL_COOKIES = &H21
  CSIDL_HISTORY = &H22
End Enum

Enum SHGFI_FLAGS
  SHGFI_LARGEICON = &H0           'sfi.hIcon is large icon
  SHGFI_SMALLICON = &H1           'sfi.hIcon is small icon
  SHGFI_OPENICON = &H2            'sfi.hIcon is open icon
  SHGFI_SHELLICONSIZE = &H4       'sfi.hIcon is shell size (not system size), rtns BOOL
  SHGFI_PIDL = &H8                'pszPath is pidl, rtns BOOL
  SHGFI_USEFILEATTRIBUTES = &H10  'parent pszPath exists, rtns BOOL
  SHGFI_ICON = &H100              'fills sfi.hIcon, rtns BOOL, use DestroyIcon
  SHGFI_DISPLAYNAME = &H200       'isf.szDisplayName is filled, rtns BOOL
  SHGFI_TYPENAME = &H400          'isf.szTypeName is filled, rtns BOOL
  SHGFI_ATTRIBUTES = &H800        'rtns IShellFolder::GetAttributesOf  SFGAO_* flags
  SHGFI_ICONLOCATION = &H1000     'fills sfi.szDisplayName with filename
                                  '   containing the icon, rtns BOOL
  SHGFI_EXETYPE = &H2000          'rtns two ASCII chars of exe type
  SHGFI_SYSICONINDEX = &H4000     'sfi.iIcon is sys il icon index, rtns hImagelist
  SHGFI_LINKOVERLAY = &H8000&     'add shortcut overlay to sfi.hIcon
  SHGFI_SELECTED = &H10000        'sfi.hIcon is selected icon
End Enum

Declare Function FlashWindow Lib "user32" _
   (ByVal hWnd As Long, _
    ByVal bInvert As Long) As Long
    
Declare Sub CopyMemory Lib "kernel32" _
    Alias "RtlMoveMemory" _
   (pDest As Any, _
    pSource As Any, _
    ByVal dwLength As Long)

'Frees memory allocated by the shell (pidls)
Declare Sub CoTaskMemFree Lib "ole32.dll" _
   (ByVal pv As Long)

'Retrieves the location of a special
'(system) folder. Returns ERROR_SUCCESS if
'successful or an OLE-defined error
'result otherwise.
Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" _
   (ByVal hwndOwner As Long, _
    ByVal nFolder As SHSpecialFolderIDs, _
    pidl As Long) As Long

'Converts an item identifier list to a
'file system path. Returns TRUE if successful
'or FALSE if an error occurs, for example,
'if the location specified by the pidl
'parameter is not part of the file system.
Declare Function SHGetPathFromIDList Lib "shell32.dll" _
    Alias "SHGetPathFromIDListA" _
   (ByVal pidl As Long, _
    ByVal pszPath As String) As Long

'Retrieves information about an object
'in the file system, such as a file,
'a folder, a directory, or a drive root.
Declare Function SHGetFileInfoPidl Lib "shell32" _
    Alias "SHGetFileInfoA" _
   (ByVal pidl As Long, _
    ByVal dwFileAttributes As Long, _
    psfib As SHFILEINFOBYTE, _
    ByVal cbFileInfo As Long, _
    ByVal uFlags As SHGFI_FLAGS) As Long

Declare Function SHGetFileInfo Lib "shell32" _
    Alias "SHGetFileInfoA" _
   (ByVal pszPath As String, _
    ByVal dwFileAttributes As Long, _
    psfi As SHFILEINFO, _
    ByVal cbFileInfo As Long, _
    ByVal uFlags As SHGFI_FLAGS) As Long


Public Function GetPIDLFromFolderID(hOwner As Long, _
                                    nFolder As SHSpecialFolderIDs) As Long
                                    
  'Returns an absolute pidl (relative to
  'the desktop) from a special folder's ID.
  '(Calling proc is responsible for freeing
  'the pidl)
  'hOwner - handle of window that will
  '         own any displayed msg boxes
  'nFolder  - special folder ID
 
   Dim pidl As Long
   
   If SHGetSpecialFolderLocation(hOwner, _
                                  nFolder, _
                                  pidl) = ERROR_SUCCESS Then
      GetPIDLFromFolderID = pidl
   End If
   
End Function


Public Function GetDisplayNameFromPIDL(pidl As Long) As String

  'If successful returns the specified
  'absolute pidl's displayname, returns
  'an empty string otherwise.

   Dim sfib As SHFILEINFOBYTE
   
   If SHGetFileInfoPidl(pidl, 0, sfib, Len(sfib), _
                        SHGFI_PIDL Or SHGFI_DISPLAYNAME) Then
                        
      GetDisplayNameFromPIDL = _
         GetStrFromBufferA(StrConv(sfib.szDisplayName, vbUnicode))
      
   End If
   
End Function


Public Function GetPathFromPIDL(pidl As Long) As String

  'Returns a path from only an absolute pidl
  '(relative to the desktop).

   Dim sPath As String * MAX_PATH
   
  'SHGetPathFromIDList rtns TRUE (1),
  'if successful, FALSE (0) if not
   If SHGetPathFromIDList(pidl, sPath) Then
      GetPathFromPIDL = GetStrFromBufferA(sPath)
   End If
   
End Function


Public Function GetStrFromBufferA(sz As String) As String

   'Return the string before first null
   'char encountered (if any) from an
   'ANSII string. If no null, return the
   'string passed
  
   If InStr(sz, vbNullChar) Then
         GetStrFromBufferA = Left$(sz, InStr(sz, vbNullChar) - 1)
   Else: GetStrFromBufferA = sz
   End If
   
End Function

'--end block--'

BAS Module 2 Code Place the following API declare code into the general declarations area of a bas module 2, shell subclassing.
Option Explicit
Private Const WM_NCDESTROY As Long = &H82
Private Const GWL_WNDPROC As Long = (-4)
Private Const OLDWNDPROC As String = "OldWndProc"

Private Declare Function GetProp Lib "user32" _
    Alias "GetPropA" _
   (ByVal hWnd As Long, _
    ByVal lpString As String) As Long
    
Private Declare Function SetProp Lib "user32" _
    Alias "SetPropA" _
    (ByVal hWnd As Long, _
    ByVal lpString As String, _
    ByVal hData As Long) As Long
    
Private Declare Function RemoveProp Lib "user32" _
    Alias "RemovePropA" _
    (ByVal hWnd As Long, _
    ByVal lpString As String) 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 CallWindowProc Lib "user32" _
    Alias "CallWindowProcA" _
    (ByVal lpPrevWndFunc As Long, _
    ByVal hWnd As Long, _
    ByVal uMsg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long) As Long


Public Function SubClass(hWnd As Long) As Boolean

   Dim lpfnOld As Long
   Dim fSuccess As Boolean
  
   If (GetProp(hWnd, OLDWNDPROC) = 0) Then
   
      lpfnOld = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
    
      If lpfnOld Then
         fSuccess = SetProp(hWnd, OLDWNDPROC, lpfnOld)
      End If
      
   End If
  
   If fSuccess Then
      SubClass = True
   Else
      If lpfnOld Then Call UnSubClass(hWnd)
      MsgBox "Unable to successfully subclass &H" & Hex(hWnd), vbCritical
   End If
  
End Function


Public Function UnSubClass(hWnd As Long) As Boolean
  
   Dim lpfnOld As Long
  
   lpfnOld = GetProp(hWnd, OLDWNDPROC)
   
   If lpfnOld Then
      If RemoveProp(hWnd, OLDWNDPROC) Then
         UnSubClass = SetWindowLong(hWnd, GWL_WNDPROC, lpfnOld)
      End If
   End If

End Function


Public Function WndProc(ByVal hWnd As Long, _
                        ByVal uMsg As Long, _
                        ByVal wParam As Long, _
                        ByVal lParam As Long) As Long
  
   Select Case uMsg
   
      Case WM_SHNOTIFY
         Call Form1.NotificationReceipt(wParam, lParam)
      
      Case WM_NCDESTROY
         Call UnSubClass(hWnd)
         MsgBox "Unsubclassed &H" & Hex(hWnd), vbCritical, "WndProc Error"
   
   End Select
   
   WndProc = CallWindowProc(GetProp(hWnd, OLDWNDPROC), hWnd, uMsg, wParam, lParam)
  
End Function

'--end block--'

BAS Module 3 Code Place the following API declare code into the general declarations area of a bas module 3, shell notifications.
Option Explicit
'the one and only shell change notification
'handle for the desktop folder
Private m_hSHNotify As Long

'the desktop's pidl
Private m_pidlDesktop As Long

'User defined notification message sent
'to the specified window's window proc.
Public Const WM_SHNOTIFY = &H401

'------------------------------------------------------
Public Type PIDLSTRUCT
  'Fully qualified pidl (relative to
  'the desktop folder) of the folder
  'to monitor changes in. 0 can also
  'be specified for the desktop folder.
   pidl As Long
   
  'Value specifying whether changes in
  'the folder's subfolders trigger a
  'change notification event (it's actually
  'a Boolean, but we'll go Long because
  'of VB's DWORD struct alignment).
   bWatchSubFolders As Long
End Type

Declare Function SHChangeNotifyRegister Lib "shell32" Alias "#2" _
   (ByVal hWnd As Long, _
    ByVal uFlags As SHCN_ItemFlags, _
    ByVal dwEventID As SHCN_EventIDs, _
    ByVal uMsg As Long, _
    ByVal cItems As Long, _
    lpps As PIDLSTRUCT) As Long

'hWnd     - Handle of the window to receive
'          the window message specified in uMsg.
'
'uFlags   - Flag that indicates the meaning of
'           the dwItem1 and dwItem2 members of
'           the SHNOTIFYSTRUCT (which is pointed
'           to by the window procedure's wParam
'           value when the specified window message
'           is received). This parameter can
'           be one of the SHCN_ItemFlags enum
'           values below. This interpretation may
'           be inaccurate as it appears pidls are
'           almost always returned in the SHNOTIFYSTRUCT.
'           See James' site for more info...
'
'dwEventId- Combination of SHCN_EventIDs enum
'           values that specifies what events the
'           specified window will be notified of.
'           See below.
'
'uMsg     - Window message to be used to identify
'           receipt of a shell change notification.
'           The message should *not* be a value that
'           lies within the specified window's
'           message range ( i.e. BM_ messages for
'           a button window) or that window may
'           not receive all (if not any) notifications
'           sent by the shell!!!
'
'cItems   - Count of PIDLSTRUCT structures in the array
'           pointed to by the lpps param.
'
'lpps     - Pointer to an array of PIDLSTRUCT structures
'           indicating what folder(s) to monitor changes in,
'           and whether to watch the specified folder's subfolder.

'If successful, SHChangeNotifyRegister returns a notification
'handle which must be passed to SHChangeNotifyDeregister
'when no longer used. Returns 0 otherwise.

'Once the specified message is registered with SHChangeNotifyRegister,
'the specified window's function proc will be notified by the shell
'of the specified event in (and under) the folder(s) specified in a pidl.
'On message receipt, wParam points to a SHNOTIFYSTRUCT and lParam
'contains the event's ID value.

'The values in dwItem1 and dwItem2 are event specific. See the
'description of the values for the wEventId parameter of the
'documented SHChangeNotify API function.
Type SHNOTIFYSTRUCT
  dwItem1 As Long
  dwItem2 As Long
End Type

'...?
'Declare Function SHChangeNotifyUpdateEntryList Lib "shell32" Alias "#5" _
'                             (ByVal hNotify As Long, _
'                             ByVal Unknown As Long, _
'                             ByVal cItem As Long, _
'                             lpps As PIDLSTRUCT) As Boolean
'
'Declare Function SHChangeNotifyReceive Lib "shell32" Alias "#5" _
'                             (ByVal hNotify As Long, _
'                             ByVal uFlags As SHCN_ItemFlags, _
'                             ByVal dwItem1 As Long, _
'                             ByVal dwItem2 As Long) As Long

'Closes the notification handle returned from a call
'to SHChangeNotifyRegister. Returns True if successful,
'False otherwise.
Declare Function SHChangeNotifyDeregister Lib "shell32" _
    Alias "#4" _
   (ByVal hNotify As Long) As Boolean

'------------------------------------------------------
'This function should be called by any app that
'changes anything in the shell. The shell will then
'notify each "notification registered" window of this action.
Declare Sub SHChangeNotify Lib "shell32" _
   (ByVal wEventId As SHCN_EventIDs, _
    ByVal uFlags As SHCN_ItemFlags, _
    ByVal dwItem1 As Long, _
    ByVal dwItem2 As Long)

'Shell notification event IDs
Public Enum SHCN_EventIDs
   SHCNE_RENAMEITEM = &H1          '(D) A non-folder item has been renamed.
   SHCNE_CREATE = &H2              '(D) A non-folder item has been created.
   SHCNE_DELETE = &H4              '(D) A non-folder item has been deleted.
   SHCNE_MKDIR = &H8               '(D) A folder item has been created.
   SHCNE_RMDIR = &H10              '(D) A folder item has been removed.
   SHCNE_MEDIAINSERTED = &H20      '(G) Storage media has been inserted into a drive.
   SHCNE_MEDIAREMOVED = &H40       '(G) Storage media has been removed from a drive.
   SHCNE_DRIVEREMOVED = &H80       '(G) A drive has been removed.
   SHCNE_DRIVEADD = &H100          '(G) A drive has been added.
   SHCNE_NETSHARE = &H200          'A folder on the local computer is being
                                   '    shared via the network.
   SHCNE_NETUNSHARE = &H400        'A folder on the local computer is no longer
                                   '    being shared via the network.
   SHCNE_ATTRIBUTES = &H800        '(D) The attributes of an item or folder have changed.
   SHCNE_UPDATEDIR = &H1000        '(D) The contents of an existing folder have changed,
                                   '    but the folder still exists and has not been renamed.
   SHCNE_UPDATEITEM = &H2000       '(D) An existing non-folder item has changed, but the
                                   '    item still exists and has not been renamed.
   SHCNE_SERVERDISCONNECT = &H4000 'The computer has disconnected from a server.
   SHCNE_UPDATEIMAGE = &H8000&     '(G) An image in the system image list has changed.
   SHCNE_DRIVEADDGUI = &H10000     '(G) A drive has been added and the shell should
                                   '    create a new window for the drive.
   SHCNE_RENAMEFOLDER = &H20000    '(D) The name of a folder has changed.
   SHCNE_FREESPACE = &H40000       '(G) The amount of free space on a drive has changed.

#If (WIN32_IE >= &H400) Then
   SHCNE_EXTENDED_EVENT = &H4000000 '(G) Not currently used.
#End If

  SHCNE_ASSOCCHANGED = &H8000000   '(G) A file type association has changed.
  SHCNE_DISKEVENTS = &H2381F       '(D) Specifies a combination of all of the disk
                                   '    event identifiers.
  SHCNE_GLOBALEVENTS = &HC0581E0   '(G) Specifies a combination of all of the global
                                   '    event identifiers.
  SHCNE_ALLEVENTS = &H7FFFFFFF
  SHCNE_INTERRUPT = &H80000000     'The specified event occurred as a result of a system
                                   'interrupt. It is stripped out before the clients
                                   'of SHCNNotify_ see it.
End Enum

#If (WIN32_IE >= &H400) Then
   Public Const SHCNEE_ORDERCHANGED = &H2 'dwItem2 is the pidl of the changed folder
#End If

'Notification flags
'uFlags & SHCNF_TYPE is an ID which indicates
'what dwItem1 and dwItem2 mean
Public Enum SHCN_ItemFlags
   SHCNF_IDLIST = &H0         'LPITEMIDLIST
   SHCNF_PATHA = &H1          'path name
   SHCNF_PRINTERA = &H2       'printer friendly name
   SHCNF_DWORD = &H3          'DWORD
   SHCNF_PATHW = &H5          'path name
   SHCNF_PRINTERW = &H6       'printer friendly name
   SHCNF_TYPE = &HFF
  'Flushes the system event buffer. The
  'function does not return until the system
  'is finished processing the given event.
   SHCNF_FLUSH = &H1000
  'Flushes the system event buffer. The function
  'returns immediately regardless of whether
  'the system is finished processing the given event.
   SHCNF_FLUSHNOWAIT = &H2000

#If UNICODE Then
  SHCNF_PATH = SHCNF_PATHW
  SHCNF_PRINTER = SHCNF_PRINTERW
#Else
  SHCNF_PATH = SHCNF_PATHA
  SHCNF_PRINTER = SHCNF_PRINTERA
#End If

End Enum


Public Function SHNotify_Register(hWnd As Long) As Boolean
  
  'Registers the one and only shell change notification.

   Dim ps As PIDLSTRUCT
  
  'If we don't already have a notification going...
   If (m_hSHNotify = 0) Then
  
     'Get the pidl for the desktop folder.
      m_pidlDesktop = GetPIDLFromFolderID(0, CSIDL_DESKTOP)
      
      If m_pidlDesktop Then
      
        'Fill the one and only PIDLSTRUCT, we're
        'watching desktop and all of the it's
        'subfolders, everything...
         ps.pidl = m_pidlDesktop
         ps.bWatchSubFolders = True
         
        'Register the notification, specifying that
        'we want the dwItem1 and dwItem2 members of
        'the SHNOTIFYSTRUCT to be pidls. We're
        'watching all events.
         m_hSHNotify = SHChangeNotifyRegister(hWnd, _
                                              SHCNF_TYPE Or SHCNF_IDLIST, _
                                              SHCNE_ALLEVENTS Or SHCNE_INTERRUPT, _
                                              WM_SHNOTIFY, _
                                              1, _
                                              ps)
                                              
         SHNotify_Register = CBool(m_hSHNotify)
    
    Else
    
        'If something went wrong...
         Call CoTaskMemFree(m_pidlDesktop)
    
    End If
    
  End If
  
End Function


Public Function SHNotify_Unregister() As Boolean
  
  'Unregisters the one and only shell change notification.
  
  'If we have a registered notification handle.
   If m_hSHNotify Then
   
     'Unregister it. If the call is successful,
     'zero the handle's variable, free and zero
     'the the desktop's pidl.
      If SHChangeNotifyDeregister(m_hSHNotify) Then
      
         m_hSHNotify = 0
         Call CoTaskMemFree(m_pidlDesktop)
         m_pidlDesktop = 0
         SHNotify_Unregister = True
         
      End If
      
   End If

End Function


Public Function SHNotify_GetEventStr(dwEventID As Long) As String

  'Returns the event string associated
  'with the specified event ID value.
  
   Dim sEvent As String
   
   Select Case dwEventID
      Case SHCNE_RENAMEITEM:       sEvent = "SHCNE_RENAMEITEM"       '&H1
      Case SHCNE_CREATE:           sEvent = "SHCNE_CREATE"           '&H2
      Case SHCNE_DELETE:           sEvent = "SHCNE_DELETE"           '&H4
      Case SHCNE_MKDIR:            sEvent = "SHCNE_MKDIR"            '&H8
      Case SHCNE_RMDIR:            sEvent = "SHCNE_RMDIR"            '&H10
      Case SHCNE_MEDIAINSERTED:    sEvent = "SHCNE_MEDIAINSERTED"    '&H20
      Case SHCNE_MEDIAREMOVED:     sEvent = "SHCNE_MEDIAREMOVED"     '&H40
      Case SHCNE_DRIVEREMOVED:     sEvent = "SHCNE_DRIVEREMOVED"     '&H80
      Case SHCNE_DRIVEADD:         sEvent = "SHCNE_DRIVEADD"         '&H100
      Case SHCNE_NETSHARE:         sEvent = "SHCNE_NETSHARE"         '&H200
      Case SHCNE_NETUNSHARE:       sEvent = "SHCNE_NETUNSHARE"       '&H400
      Case SHCNE_ATTRIBUTES:       sEvent = "SHCNE_ATTRIBUTES"       '&H800
      Case SHCNE_UPDATEDIR:        sEvent = "SHCNE_UPDATEDIR"        '&H1000
      Case SHCNE_UPDATEITEM:       sEvent = "SHCNE_UPDATEITEM"       '&H2000
      Case SHCNE_SERVERDISCONNECT: sEvent = "SHCNE_SERVERDISCONNECT" '&H4000
      Case SHCNE_UPDATEIMAGE:      sEvent = "SHCNE_UPDATEIMAGE"      '&H8000&
      Case SHCNE_DRIVEADDGUI:      sEvent = "SHCNE_DRIVEADDGUI"      '&H10000
      Case SHCNE_RENAMEFOLDER:     sEvent = "SHCNE_RENAMEFOLDER"     '&H20000
      Case SHCNE_FREESPACE:        sEvent = "SHCNE_FREESPACE"        '&H40000
    
#If (WIN32_IE >= &H400) Then
      Case SHCNE_EXTENDED_EVENT:   sEvent = "SHCNE_EXTENDED_EVENT"   '&H4000000
#End If
    
      Case SHCNE_ASSOCCHANGED:     sEvent = "SHCNE_ASSOCCHANGED"     '&H8000000
    
      Case SHCNE_DISKEVENTS:       sEvent = "SHCNE_DISKEVENTS"       '&H2381F
      Case SHCNE_GLOBALEVENTS:     sEvent = "SHCNE_GLOBALEVENTS"     '&HC0581E0
      Case SHCNE_ALLEVENTS:        sEvent = "SHCNE_ALLEVENTS"        '&H7FFFFFFF
      Case SHCNE_INTERRUPT:        sEvent = "SHCNE_INTERRUPT"        '&H80000000
   End Select
  
   SHNotify_GetEventStr = sEvent

End Function

'--end block--'

Form Code To a new form, add a timer (tmrFlashMe) control (used to flash the window in the demo), and a textbox (Text1) set to multiline.

Open the project's Properties (Project > Project1 Properties), and on the Make tab, enter "WIN32_IE = 256" (no quotes) as conditional compile arguments. Then add the following to the form:


Option Explicit

Private Sub Form_Load()

   If SubClass(hWnd) Then
   
      If IsIDE Then
      
      Text1.Text = "**IMPORTANT**" & vbCrLf & _
              "This window is subclassed. Do not close it from" & vbCrLf & _
              "either VB's End button or End menu command," & vbCrLf & _
              "or VB will blow up. Close this window only from" & vbCrLf & _
              "the system menu above!" & vbCrLf & vbCrLf & Text1
      End If
      
      Call SHNotify_Register(hWnd)
      
   Else: Text1.Text = "Well, it's supposed to work."
   End If
   
  'position the window in the bottom corner
   Me.Move Screen.Width - Width, Screen.Height - Height
  
End Sub


Private Sub Form_Resize()

  On Error GoTo Out
  Text1.Move 0, 0, ScaleWidth, ScaleHeight
  
Out:
End Sub


Private Sub Form_Unload(Cancel As Integer)
  
   Call SHNotify_Unregister
   Call UnSubClass(hWnd)
  
End Sub


Private Function IsIDE() As Boolean

   On Error GoTo Out
   Debug.Print 1 / 0
  
Out:
   IsIDE = Err
End Function


Public Sub NotificationReceipt(wParam As Long, lParam As Long)

   Dim sOut As String
   Dim shns As SHNOTIFYSTRUCT
   
   sOut = SHNotify_GetEventStr(lParam) & vbCrLf
   
  'Fill the SHNOTIFYSTRUCT from it's pointer.
   CopyMemory shns, ByVal wParam, Len(shns)
       
  'lParam is the ID of the notification event,
  'one of the SHCN_EventIDs.
   Select Case lParam
      
     '----------------------------------------------------
     'For the SHCNE_FREESPACE event, dwItem1 points
     'to what looks like a 10 byte struct. The first
     'two bytes are the size of the struct, and the
     'next two members equate to SHChangeNotify's
     'dwItem1 and dwItem2 params.
    
     'The dwItem1 member is a bitfield indicating which
     'drive(s) had it's (their) free space changed.
     'The bitfield is identical to the bitfield returned
     'from a GetLogicalDrives call, i.e, bit 0 = A:\, bit
     '1 = B:\, 2, = C:\, etc. Since VB does DWORD alignment
     'when CopyMemory'ing to a struct, we'll extract the
     'bitfield directly from it's memory location.
    
      Case SHCNE_FREESPACE
      
         Dim dwDriveBits As Long
         Dim wHighBit As Integer
         Dim wBit As Integer
         
         CopyMemory dwDriveBits, ByVal shns.dwItem1 + 2, 4
   
        'Get the zero based position of the highest
        'bit set in the bitmask (essentially determining
        'the value's highest complete power of 2).
        'Use floating point division (we want the exact
        'values from the Logs) and remove the fractional
        'value (the fraction indicates the value of
        'the last incomplete power of 2, which means the
        'bit isn't set).
        
         wHighBit = Int(Log(dwDriveBits) / Log(2))
         
         For wBit = 0 To wHighBit
           
          'If the bit is set...
           If (2 ^ wBit) And dwDriveBits Then
             
            '... get it's drive string
             sOut = sOut & Chr$(vbKeyA + wBit) & ":\" & vbCrLf
   
           End If
         Next
      
     '----------------------------------------------------
     'shns.dwItem1 also points to a 10 byte struct. The
     'struct's second member (after the struct's first
     'WORD size member) points to the system imagelist
     'index of the image that was updated.
      Case SHCNE_UPDATEIMAGE
      
         Dim iImage As Long
      
         CopyMemory iImage, ByVal shns.dwItem1 + 2, 4
         sOut = sOut & "Index of image in system imagelist: " & iImage & vbCrLf
    
     '----------------------------------------------------
     'Everything else except SHCNE_ATTRIBUTES is the
     'pidl(s) of the changed item(s). For SHCNE_ATTRIBUTES,
     'neither item is used. See the description of the
     'values for the wEventId parameter of the
     'SHChangeNotify API function for more info.
      Case Else
         Dim sDisplayname As String
         
         If shns.dwItem1 Then
         
            sDisplayname = GetDisplayNameFromPIDL(shns.dwItem1)
            
            If Len(sDisplayname) Then
             sOut = sOut & "first item displayname: " & sDisplayname & vbCrLf
             sOut = sOut & "first item path: " & GetPathFromPIDL(shns.dwItem1) & vbCrLf
            Else
             sOut = sOut & "first item is invalid" & vbCrLf
            End If
            
         End If
         
         If shns.dwItem2 Then
         
            sDisplayname = GetDisplayNameFromPIDL(shns.dwItem2)
           
            If Len(sDisplayname) Then
               sOut = sOut & "second item displayname: " & sDisplayname & vbCrLf
               sOut = sOut & "second item path: " & GetPathFromPIDL(shns.dwItem2) & vbCrLf
            Else
               sOut = sOut & "second item is invalid" & vbCrLf
            End If
         End If
  
  End Select
  
 'update the text window and flash
 'the window title
  Text1.Text = Text1.Text & sOut & vbCrLf
  Text1.SelStart = Len(Text1.Text)
  tmrFlashMe = True

End Sub


Private Sub tmrFlashMe_Timer()

  'initial settings: Interval = 1, Enabled = False
  
   Static nCount As Integer
   
   If nCount = 0 Then tmrFlashMe.Interval = 200
   
   nCount = nCount + 1
   Call FlashWindow(hWnd, True)
   
  'Reset everything after 3 flash cycles
   If nCount = 6 Then
      nCount = 0
      tmrFlashMe.Interval = 1
      tmrFlashMe = False
   End If

End Sub

'--end block--'

Comments As with all subclassed, save the project first. And never used VB's End button or menu End command to stop the program; use the application's system menu Close instead.

Run the project and perform any Explorer-based actions, including adding/removing/renaming printers, dropping/connecting to shared resources, opening/moving/renaming files and folders etc. As each action occurs, the text box will reflect the system notification messages sent.

Solutions
 
 
Directions Business Systems Development Business Development Systems