This article is like an addition of "This Old House Classics" with Bob Vila. While we are eagerly anticipating the gold release of Visual Studio .Net - Bill Gates said Windows XP will be launched (in the next couple of days) on Regis and Kelly and hopefully Visual Studio .Net will be released shortly thereafter - I thought I would write an article about a Visual Basic 6 classic, Using the System Tray. Another reason is that some of you wrote and asked about the System Tray. The System Tray as you might recall is that lower-right corner region containing application icons, on the startup bar.
This week's article demonstrates how to add, remove and respond to user input in the System Tray. (For those of you that asked, I hope this helps.) What else this will remind us of is part of the motivation for .Net. As you will quickly see VB6 allows you to interact with the System Tray but only through heavy use of the Windows API. This has always been a complaint about VB6: anytime we want to do something advanced we have to go to the API. Well, in this article I will demonstrate how to manipulate the System Tray in VB6, but keep in mind, that the Common Language Runtime (CLR) in Visual Studio .Net integrates these kinds of capabilities more closely with .Net languages. Huzzah!
Interacting with the System Tray requires the definition of a type, the declaration of a method defined in the shell32.dll API, and interacting to user feedback via mouse inputs. Additionally, you will have to know a little bit about Windows messages and some arbitrary, disparate constant values related to working with the System Tray. We will cover each of these aspects in turn, followed by the complete code listing for the sample program.
Defining the NotifyIconData Type
The NotifyIconData structure is a simple type that is used to package all of the necessary information up in order to send that information to the shell32.dll API. (The fact that we have to declare this in our VB6 applications every time we want to use it, or schlep a module around with it pre-defined, is a drag.) The following user-defined Type mirrors the structure needed to interact with the System Tray.
Private Type NotifyIconData Size As Long Handle As Long ID As Long Flags As Long CallBackMessage As Long Icon As Long Tip As String * 64 End Type
Size will be used to represent the size of NotifyIconData variables. Handle represents the Windows handle (hWnd) of the calling application. ID is an application-defined identifier. Flags is a bit-field indicating which of CallBackMessage, Icon, and Tip will contain valid data. (One of the problems Visual Basic.Net addresses is transmogrifying the generic nature of working with the Windows API to working with strong-named entities and types.)
Each time we call Shell_NotifyIcon we will be passing an initialized NotifyIconData variable to that method.
The reason NotifyIconData is defined as Private is because VB6 does not support Public types in private modules, forms. This is another limitation of VB6. Access modifiers should be up to the discretion of the programmer, not a limitation of the language.
Declaring the Shell_NotifyIcon API Method
Shell_NotifyIcon data is an API method. We need to know the signature and API DLL containing the method we want to use to properly add the method declaration. More than likely you have some experience with this, so I will just provide the signature for the method we need.
Private Declare Function Shell_NotifyIcon _ Lib "shell32" Alias "Shell_NotifyIconA" ( _ ByVal Message As Long, Data As NotifyIconData) As Boolean
As a reminder the Lib "shell32" clause indicates the DLL containing this method. The Alias clause, Alias "Shell_NotifyIconA", indicates that we want to use the ASCII rather than the Unicode version of this method. The API method is a Function that takes a long and a NotifyIconData variable, returning a Boolean.
A Declare statement is the implicit equivalent of the call to LoadLibrary, another API method. LoadLibrary allows you to load an API DLL on demand. When you use the Declare idiom in VB6, Windows manages loading and unloading the library.
Adding and Deleting Icons from the System Tray
When the user-defined type has been added and the API method declared, we can invoke that method in our application as if it were just another method in our application.
The following subroutine demonstrates initializing a NotifyIconData variable and calling the Shell_NotifyIcon API method. The subroutine is appropriately named AddIconToTray.
Private Data As NotifyIconData Private Sub AddIconToTray() Data.Size = Len(Data) Data.Handle = hWnd Data.ID = vbNull Data.Flags = IconFlag Or TipFlag Or MessageFlag Data.CallBackMessage = WM_MOUSEMOVE Data.Icon = Icon Data.Tip = "System Tray Demo - codeguru.com" & vbNullChar Call Shell_NotifyIcon(AddIcon, Data) End Sub
From the listing you can easily determine that the length of Data is stored in the Size field. Data.Handle is initialized to the hWnd property of the Form containing this code. No application identifier is used; thus Data.ID is initialized to vbNull. Data.Flags indicates that CallBackMessage, Icon, and Tip will all contain data. This is indicated by Or'ing the three constants suitable for the Flags field.
Data.Icon is initialized to the Form's Icon property. The tip is initialized to a gratuitous plug for www.codeguru.com. Because Tip is an ASCIIZ string-a string of characters ending in 0-we have to append the vbNullChar to the end of the Tip string. Finally, the API method is called passing the constant indicating we want to perform an add-icon operation and the NotifyIconData variable.
To delete the Icon we pass the same instance of NotifyIconData, Data, to the same message, passing a constant representing the delete operation. That subroutine is listed next.
Private Sub DeleteIconFromTray() Call Shell_NotifyIcon(DeleteIcon, Data) End Sub
Responding to Mouse Inputs in the System Tray
Our Data.CallbackMessage indicates that we want to respond to MouseMove messages. In order for this to work we will need to implement a Form_MouseMove event handler. (The event handler is defined in the form whose hWnd property we used to initialize the Data.Handle field. You can implement the Form_MouseMove event in the usual way in the code editor, or you can type the code manually and VB6 will automatically make the connection.
Private Sub Form_MouseMove(Button As Integer, _ Shift As Integer, X As Single, Y As Single) Dim Message As Long Message = X / Screen.TwipsPerPixelX Select Case Message Case WM_LBUTTONDBLCLK Visible = Not Visible ' False = 0 = Minimized; Abs(True) = 1 = Normal. Bad code! WindowState = Abs(Not Visible) End Select End Sub
The statement Message = X / Screen.TwipsPerPixelX yields the mouse message. This is pure unnecessary obfuscation. There is no way someone would figure that out unless they were told or implemented this bizarre behavior. Why this was done probably has to do with assembly language, the ROM BIOS, and limitations on earlier computers. This kind of code will hopefully disappear altogether in the future.
When the user double-clicks over the System Tray icon the form visibility is toggled, and my own obscure code that treats a Boolean like a Windows state toggles the form between minimized and Normal states. (WindowState = Abs(Not Visible) is another example of frivolous obfuscation. This can be resolved with the Refactoring Extract Method. We'll save that discussion for another day.)
Listing 1 contains the complete code listing for the example program. The Windows messages and constants can only be discovered by digging around in the help files. Fortunately, Intellisense and the improved framework of Visual basic.Net will make performing these tasks easier in the future.
Listing 1: System Tray Sample Application
' FormMain.frm - Add an icon to the system tray. ' Copyright (c) 2001. All Rights Reserved. ' By Paul Kimmel. email@example.com Option Explicit ' Type passed to Shell_NotifyIcon Private Type NotifyIconData Size As Long Handle As Long ID As Long Flags As Long CallBackMessage As Long Icon As Long Tip As String * 64 End Type ' Constants for managing System Tray tasks, foudn in shellapi.h Private Const AddIcon = &H0 Private Const ModifyIcon = &H1 Private Const DeleteIcon = &H2 Private Const WM_MOUSEMOVE = &H200 Private Const WM_LBUTTONDBLCLK = &H203 Private Const WM_LBUTTONDOWN = &H201 Private Const WM_LBUTTONUP = &H202 Private Const WM_RBUTTONDBLCLK = &H206 Private Const WM_RBUTTONDOWN = &H204 Private Const WM_RBUTTONUP = &H205 Private Const MessageFlag = &H1 Private Const IconFlag = &H2 Private Const TipFlag = &H4 Private Declare Function Shell_NotifyIcon _ Lib "shell32" Alias "Shell_NotifyIconA" ( _ ByVal Message As Long, Data As NotifyIconData) As Boolean Private Data As NotifyIconData Private Sub Form_Load() AddIconToTray Visible = False End Sub Private Sub Form_Terminate() DeleteIconFromTray End Sub Private Sub AddIconToTray() Data.Size = Len(Data) Data.Handle = hWnd Data.ID = vbNull Data.Flags = IconFlag Or TipFlag Or MessageFlag Data.CallBackMessage = WM_MOUSEMOVE Data.Icon = Icon Data.Tip = "System Tray Demo - codeguru.com" & vbNullChar Call Shell_NotifyIcon(AddIcon, Data) End Sub Private Sub DeleteIconFromTray() Call Shell_NotifyIcon(DeleteIcon, Data) End Sub Private Sub Form_MouseMove(Button As Integer, _ Shift As Integer, X As Single, Y As Single) Dim Message As Long Message = X / Screen.TwipsPerPixelX Select Case Message Case WM_LBUTTONDBLCLK Visible = Not Visible WindowState = Abs(Not Visible) End Select End Sub
This article demonstrates how to invoke methods in the Windows API as well as add an application's icon to the System Tray. These applications are especially useful in some instances; one such instance is the Volume control. I can't tell you how many times I have had to mute David Gray, Beethoven, Creed, or Johnny Cash when I was jamming and the phone rang.
Another message I hope you got is that there are deficits in VB6. (Clearly Microsoft's .Net initiative is an indicator of this fact.) Perhaps after reading this article you will have a little better understanding of the everyday problems VB.NET resolves.
If you're like some of the programmers I know, you program in more than one language. If you are like all of the programmers I know, you know a lot and forget stuff you used to know. To this end I have begin creating a Code Database, allowing me to quickly access solutions I have used in the past. If things go as planned, this code database will be available as an ASP.NET application available to the development community at www.softconcepts.com. In the interim I will shortly begin posting the code from these articles on that website.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for cool Visual Basic .Net topics in his upcoming book Visual Basic .Net Unleashed. Paul founded Software Conceptions, Inc. in 1990. Contact Paul Kimmel at firstname.lastname@example.org for help building VB.NET applications or migrating VB6 applications to .NET.