|
|
![]() |
|
||
|
|
|||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||
| Visual Basic Subclassing Routines Shell_NotifyIcon: Respond to Systray Balloon Tip Clicks |
||
| Posted: | Tuesday January 07, 2003 | |
| Updated: | Monday December 26, 2011 | |
| Applies to: | VB5, VB6 | |
| Developed with: | VB6, Windows XP | |
| OS restrictions: | Windows 2000 or Windows XP | |
| Author: | VBnet - Randy Birch | |
|
Related: |
CreateWindowEx: 21st Century ToolTips for VB - The Basics |
|
| Prerequisites |
|
Windows 2000 or XP (Shell version 5 or better) This demo contains new code added January 2003 to properly determine the Shell32.dll version and use the appropriately-sized NOTIFYICONDATA structure. Although this will handle the display of the systray icon across Windows versions, application designers targeting Windows 2000 and XP should nonetheless take appropriate steps to ensure their app degrades gracefully to utilize only the functionality provided in earlier system's shell versions. For information concerning using the systray across all Windows versions it is strongly recommended you refer to Shell_NotifyIcon: Windows Systray NOTIFYICONDATA Overview. |
|
|
Shell_NotifyIcon: Display Systray Balloon Tips
shows how easy it is to use balloon tips on 2000 or XP. This demo adds
subclassing to enable detection of the user's clicking on the balloon
tip to respond to any message displayed. In addition, the code detects when the balloon tip is shown and if it
was closed
by the timeout expiring or the X being pressed. It also shows how to use and change a
conventional tool tip for the icon, as well as respond to a systray popup
menu.
Knowing when the balloon tip is shown is critical to applications counting on the tip to draw attention to a particular issue. Because Windows allows no more than one taskbar balloon tip to be displayed at any given moment, a queuing mechanism is used when a command to show a balloon tips is made. If an application attempts to display a balloon tip when one is already being displayed, the balloon tip will not appear until the existing balloon tip has been visible for at least the system-minimum timeout value (typically 10 seconds). For example, a balloon tip from another application having a uTimeout value of 30 seconds has been visible for seven seconds when your application attempts to display its balloon tip. If the system-minimum timeout is ten seconds, the first balloon tip displays for an additional three seconds before being replaced by your balloon tip. If your application expected your balloon tip to have been on-screen for 3 seconds before it is actually shown, you may invoke code to close the tip too quickly, etc. or make incorrect assumptions about the user's interest in the message. Therefore, to accommodate this balloon tips raise a message when they are actually shown, and, in response to this your app can begin to take whatever action is appropriate. Similarly, to assist in determining a course of action balloon tips also send a message when it times out without user interaction (NIN_BALLOONTIMEOUT). This message is also sent when the user dismisses the balloon tip using its X button. The NIN_BALLOONHIDE message, despite the action its name implies, is not returned to the application when the balloon tip is hidden or closed, but rather when the systray icon is removed during the display of a balloon tip. Thus it really doesn't appear to serve much purpose to me. A complete discussion of the structure and parameters for balloon tips can be found on the Shell_NotifyIcon: Windows Systray NOTIFYICONDATA Overview page. |
| BAS Module |
|
|
| Add the following code to a BAS module: |
|
|
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Variable to hold the ID of the
'default window message processing
'procedure. Returned by SetWindowLong
Public defWindowProc As Long
'Get/SetWindowLong messages
Public Const GWL_WNDPROC As Long = (-4)
Private Const GWL_HWNDPARENT As Long = (-8)
Private Const GWL_ID As Long = (-12)
Private Const GWL_STYLE As Long = (-16)
Private Const GWL_EXSTYLE As Long = (-20)
Private Const GWL_USERDATA As Long = (-21)
'general windows messages
Private Const WM_USER As Long = &H400
Private Const WM_NOTIFY As Long = &H4E
Private Const WM_COMMAND As Long = &H111
Public Const WM_CLOSE As Long = &H10
'our app-specific message to trap
'in the WindowProc routine
Private Const WM_APP As Long = &H8000&
Public Const WM_MYHOOK As Long = WM_APP + &H15
'mouse constants for the callback
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const WM_LBUTTONUP As Long = &H202
Private Const WM_LBUTTONDBLCLK As Long = &H203
Private Const WM_MBUTTONDOWN As Long = &H207
Private Const WM_MBUTTONUP As Long = &H208
Private Const WM_MBUTTONDBLCLK As Long = &H209
Private Const WM_RBUTTONDOWN As Long = &H204
Private Const WM_RBUTTONUP As Long = &H205
Private Const WM_RBUTTONDBLCLK As Long = &H206
'this identifies this icon, allowing an app
'to display more than one (each with unique IDs)
'and respond to each individually.
Public Const APP_SYSTRAY_ID = 999
Public Const NIN_BALLOONSHOW = (WM_USER + 2)
Public Const NIN_BALLOONHIDE = (WM_USER + 3)
Public Const NIN_BALLOONTIMEOUT = (WM_USER + 4)
Public Const NIN_BALLOONUSERCLICK = (WM_USER + 5)
Private Declare Function SetForegroundWindow Lib "user32" _
(ByVal hwnd As Long) As Long
Public Declare Function PostMessage Lib "user32" _
Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Public Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Any) As Long
Public 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 WindowProc(ByVal hwnd As Long, _
ByVal uMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
'If the handle returned is to our form,
'call a message handler to deal with
'tray notifications. If it is a general
'system message, pass it on to
'the default window procedure.
'
'If destined for the form and equal to
'our custom hook message (WM_MYHOOK),
'examining lParam reveals the message
'generated, to which we react appropriately.
On Error Resume Next
Select Case hwnd
'form-specific handler
Case Form1.hwnd
Select Case uMsg
'check uMsg for the application-defined
'identifier (NID.uID) assigned to the
'systray icon in NOTIFYICONDATA (NID).
'WM_MYHOOK was defined as the message sent
'as the .uCallbackMessage member of
'NOTIFYICONDATA the systray icon
Case WM_MYHOOK
'lParam is the value of the message
'that generated the tray notification.
Select Case lParam
Case WM_RBUTTONUP:
'This assures that focus is restored to
'the form when the menu is closed. If the
'form is hidden, it (correctly) has no effect.
Call SetForegroundWindow(Form1.hwnd)
Form1.PopupMenu Form1.zmnuSysTrayDemo
Case NIN_BALLOONSHOW
Debug.Print "The balloon tip has just appeared"
Case NIN_BALLOONHIDE
Debug.Print "The systray icon was removed when" & _
"the balloon tip was displayed"
Case NIN_BALLOONUSERCLICK
Debug.Print "The user clicked on the balloon tip"
Case NIN_BALLOONTIMEOUT
Debug.Print "The balloon tip timed-out without " & _
"the user clicking it, or the user " & _
"clicked the balloon tip's close button"
End Select
'handle any other form messages by
'passing to the default message proc
Case Else
WindowProc = CallWindowProc(defWindowProc, _
hwnd, _
uMsg, _
wParam, _
lParam)
Exit Function
End Select
'this takes care of messages when the
'handle specified is not that of the form
Case Else
WindowProc = CallWindowProc(defWindowProc, _
hwnd, _
uMsg, _
wParam, _
lParam)
End Select
End Function
|
|
|
| Form Code |
|
|
| To a form add a text box (Text1, multiline), four option buttons in a control array (Option1(0) through Option1(3)), and two command buttons (Command1, Command2). Also add a top-level menu named zmnuSysTrayDemo with four menu items in a menu array - mnuFile(0) through mnuFile(3). mnuFile(2) is a separator. Add the following code to the form: |
|
|
Option Explicit Private Const NOTIFYICON_VERSION = &H3
Private Const NIF_MESSAGE = &H1
Private Const NIF_ICON = &H2
Private Const NIF_TIP = &H4
Private Const NIF_STATE = &H8
Private Const NIF_INFO = &H10
Private Const NIM_ADD = &H0
Private Const NIM_MODIFY = &H1
Private Const NIM_DELETE = &H2
Private Const NIM_SETFOCUS = &H3
Private Const NIM_SETVERSION = &H4
Private Const NIM_VERSION = &H5
Private Const NIS_HIDDEN = &H1
Private Const NIS_SHAREDICON = &H2
'icon flags
Private Const NIIF_NONE = &H0
Private Const NIIF_INFO = &H1
Private Const NIIF_WARNING = &H2
Private Const NIIF_ERROR = &H3
Private Const NIIF_GUID = &H5
Private Const NIIF_ICON_MASK = &HF
Private Const NIIF_NOSOUND = &H10
'shell version / NOTIFIYICONDATA struct size constants
Private Const NOTIFYICONDATA_V1_SIZE As Long = 88 'pre-5.0 structure size
Private Const NOTIFYICONDATA_V2_SIZE As Long = 488 'pre-6.0 structure size
Private Const NOTIFYICONDATA_V3_SIZE As Long = 504 '6.0+ structure size
Private NOTIFYICONDATA_SIZE As Long
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type NOTIFYICONDATA
cbSize As Long
hwnd As Long
uID As Long
uFlags As Long
uCallbackMessage As Long
hIcon As Long
szTip As String * 128
dwState As Long
dwStateMask As Long
szInfo As String * 256
uTimeoutAndVersion As Long
szInfoTitle As String * 64
dwInfoFlags As Long
guidItem As GUID
End Type
Private Declare Function Shell_NotifyIcon Lib "shell32.dll" _
Alias "Shell_NotifyIconA" _
(ByVal dwMessage As Long, _
lpData As NOTIFYICONDATA) As Long
Private Declare Function GetFileVersionInfoSize Lib "version.dll" _
Alias "GetFileVersionInfoSizeA" _
(ByVal lptstrFilename As String, _
lpdwHandle As Long) As Long
Private Declare Function GetFileVersionInfo Lib "version.dll" _
Alias "GetFileVersionInfoA" _
(ByVal lptstrFilename As String, _
ByVal dwHandle As Long, _
ByVal dwLen As Long, _
lpData As Any) As Long
Private Declare Function VerQueryValue Lib "version.dll" _
Alias "VerQueryValueA" _
(pBlock As Any, _
ByVal lpSubBlock As String, _
lpBuffer As Any, _
nVerSize As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
Private Sub Form_Load()
Text1.Text = "A newer version of MyApp.exe is " & _
"now available for download from " & _
"http://www.somplace.com/myapp/update/." & _
vbCrLf & vbCrLf & _
"Click here to download now!"
Command1.Caption = "Add Systray Icon"
Command2.Caption = "Show Balloon Tip"
Command2.Enabled = False
Option1(0).Caption = "no icon"
Option1(1).Caption = "information icon"
Option1(2).Caption = "warning icon"
Option1(3).Caption = "error icon"
Option1(1).Value = True
End Sub
Private Sub Form_Unload(Cancel As Integer)
'Remove the icon added to the taskbar
ShellTrayRemove
'remove subclassing
UnSubClass Me.hwnd
'ensure unloading proceeds
Cancel = False
End Sub
Private Sub Command1_Click()
Call ShellTrayAdd
Command2.Enabled = True
End Sub
Private Sub Command2_Click()
ShellTrayModifyTip GetSelectedOptionIndex()
End Sub
Private Sub ShellTrayAdd()
Dim nid As NOTIFYICONDATA
If NOTIFYICONDATA_SIZE = 0 Then SetShellVersion
With nid
.cbSize = NOTIFYICONDATA_SIZE
.hwnd = Me.hwnd
.uID = APP_SYSTRAY_ID
.uFlags = NIF_MESSAGE Or NIF_ICON Or NIF_TIP
.dwState = NIS_SHAREDICON
.hIcon = Form1.Icon
.szTip = "New Download Watcher" & vbNullChar
.uTimeoutAndVersion = NOTIFYICON_VERSION
.uCallbackMessage = WM_MYHOOK
End With
'add the icon ...
If Shell_NotifyIcon(NIM_ADD, nid) = 1 Then
'... and inform the system of the
'NOTIFYICON version in use
Call Shell_NotifyIcon(NIM_SETVERSION, nid)
'prepare to receive the systray messages
SubClass Me.hwnd
End If
End Sub
Private Sub ShellTrayRemove()
Dim nid As NOTIFYICONDATA
If NOTIFYICONDATA_SIZE = 0 Then SetShellVersion
With nid
.cbSize = NOTIFYICONDATA_SIZE
.hwnd = Form1.hwnd
.uID = APP_SYSTRAY_ID
End With
Call Shell_NotifyIcon(NIM_DELETE, nid)
End Sub
Private Sub ShellTrayModifyTip(nIconIndex As Long)
Dim nid As NOTIFYICONDATA
If NOTIFYICONDATA_SIZE = 0 Then SetShellVersion
With nid
.cbSize = NOTIFYICONDATA_SIZE
.hwnd = Form1.hwnd
.uID = APP_SYSTRAY_ID
.uFlags = NIF_INFO
.dwInfoFlags = nIconIndex
.szInfoTitle = "New Download Available!" & vbNullChar
.szInfo = Text1.Text & vbNullChar
End With
Call Shell_NotifyIcon(NIM_MODIFY, nid)
End Sub
Private Sub UnSubClass(hwnd As Long)
'restore the default message handling
'before exiting
If defWindowProc <> 0 Then
SetWindowLong hwnd, GWL_WNDPROC, defWindowProc
defWindowProc = 0
End If
End Sub
Private Sub SubClass(hwnd As Long)
'assign our own window message
'procedure (WindowProc)
On Error Resume Next
defWindowProc = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
End Sub
Private Sub SetShellVersion()
Select Case True
Case IsShellVersion(6)
NOTIFYICONDATA_SIZE = NOTIFYICONDATA_V3_SIZE '6.0 structure size
Case IsShellVersion(5)
NOTIFYICONDATA_SIZE = NOTIFYICONDATA_V2_SIZE 'pre-6.0 structure size
Case Else
NOTIFYICONDATA_SIZE = NOTIFYICONDATA_V1_SIZE 'pre-5.0 structure size
End Select
End Sub
Private Function IsShellVersion(ByVal version As Long) As Boolean
'returns True if the Shell version
'(shell32.dll) is equal or later than
'the value passed as 'version'
Dim nBufferSize As Long
Dim nUnused As Long
Dim lpBuffer As Long
Dim nVerMajor As Integer
Dim bBuffer() As Byte
Const sDLLFile As String = "shell32.dll"
nBufferSize = GetFileVersionInfoSize(sDLLFile, nUnused)
If nBufferSize > 0 Then
ReDim bBuffer(nBufferSize - 1) As Byte
Call GetFileVersionInfo(sDLLFile, 0&, nBufferSize, bBuffer(0))
If VerQueryValue(bBuffer(0), "\", lpBuffer, nUnused) = 1 Then
CopyMemory nVerMajor, ByVal lpBuffer + 10, 2
IsShellVersion = nVerMajor >= version
End If 'VerQueryValue
End If 'nBufferSize
End Function
Private Function GetSelectedOptionIndex() As Long
'returns the selected item index from
'an option button array. Use in place
'of multiple If...Then statements!
'If your array contains more elements,
'just append them to the test condition,
'setting the multiplier to the button's
'negative -index.
GetSelectedOptionIndex = Option1(0).Value * 0 Or _
Option1(1).Value * -1 Or _
Option1(2).Value * -2 Or _
Option1(3).Value * -3
End Function
Private Sub mnuFile_Click(Index As Integer)
'code simulating reaction
'to the menu clicks
Select Case Index
Case 0, 1:
MsgBox "Called from File " & mnuFile(Index).Caption
Case 3:
'Executing 'Unload Me' from within a
'menu event invoked from a systray icon
'will cause a GPF. The proper way to
'terminate under these circumstances
'is to send a WM_CLOSE message to the
'form. The form will process the
'message as though the user had selected
'Close from the sysmenu, invoking the
'normal chain of shutdown events, removing
'the tray icon, terminating the subclassing
'cleanly and ultimately preventing the GPF.
'
'This code can also be called directly from
'the form's menu as well, so no special coding
'is required to differentiate between an end
'command from a popup systray menu, or from
'a normal form menu.
'
'The UnloadMode of QueryUnload/UnloadMode
'will equal vbFormControlMenu when this
'close method is used.
Call PostMessage(Me.hwnd, WM_CLOSE, 0&, ByVal 0&)
Case Else
End Select
End Sub |
| Comments |
The
following comments regarding the behaviour of a balloon tip and its
messages under Windows 2000 should be noted. The condensed version of
the following is that under Windows 2000, only a click on the
Remember too that this is a subclassed app, so don't hit VB's 'End' button. |
|
|
|
|
|
|||||
|
|||||
|
|
|||||
|
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |
![]() |