System-tray enable your Visual Basic apps

In this article, Lamont Adams explains how to add an icon to the system tray. He also tries to illustrate some features of Windows' Version 5 system tray but admits he's stumped. Help him out, if you think you can.

Q:"I'm a VB newbie, [and I’m] using the Packaging and Distribution Wizard that came with Visual Studio 6.0. It works just fine, but is there a way I can have my program automatically place the icon in the system tray when my program is loaded? There is no option for this in PDW.”—Gene

A:The question of how to add an icon to the system tray, or “Taskbar Notification Area,” in Microsoft-speak, comes up often, and it has a surprisingly simple solution. Icons are added to the system tray using the shell32 API function Shell_NotifyIcon(). The declaration for this function is as follows:
Declare Function Shell_NotifyIcon Lib "shell32" _
Alias "Shell_NotifyIconA" _
ByVal dwMessage As Long, pnid As NOTIFYICONDATA)_
As Boolean

The NOTIFYICONDATA structure contains information that specifies what icon to use, the tooltip to display, and how to notify the icon’s owner application of events involving the icon, such as mouse clicks. NOTIFYICONDATA looks like this:
    cbSize As Long
    hwnd As Long
    uId As Long
   uFlags As Long
    uCallBackMessage As Long
    hIcon As Long
    szTip As String * 64
End Type

Let’s look at what each of those items means:
  • ·        cbSize is the size of the structure in bytes (use VB’s len() function).
  • ·        hwnd is the window handle of the form that “owns” the icon and should receive notification of its events. Use the hwnd property of the VB form that you want to receive messages from the icon.
  • ·        uId is a unique identifier of the icon. Multiple icons in the tray can belong to the same application. The combination of hwnd and uId identifies the icon.
  • ·        uFlags are special information codes.
  • ·        uCallBackMessage is the window message Shell_NotifyIcon sends to the icon’s owner. It’s simplest to use the mouse movement message with VB applications. This will cause the icon’s form’s MouseMove event to fire when a user interacts with the icon. See the below discussion of messages to find out why.
  • ·        hIcon is the icon handle of the icon to add to the tray. Use the Icon property of the form you used for hwnd.
  • ·        szTip is a null-terminated string containing a tooltip to display when the user moves his or her mouse over the icon.

For a simple example showing the use Shell_NotifyIcon, check out the code in Listing A. The results of running the code are shown in Figure A.

Figure A
Shell_NotifyIcon sample application

“There is a message for you”
Windows applications communicate with the system by sending and receiving messages, which are essentially just special numbers that are meaningful to Windows. These standard messages are known as “window messages,” and each of them has a corresponding constant value and meaning. The WM_MOUSEMOVE constant defined in Listing A is an example of a window message sent by Windows indicating that the mouse has moved over an application’s window.

For better or worse, VB hides a lot of this from the programmer, receiving messages from Windows behind-the-scenes and instead raising events for our code to handle. Extending the above example, VB would intercept the WM_MOUSEMOVE message sent to a form and raise the form’s MouseMove event instead. Pretty simple, huh?

Unfortunately, VB doesn’t implement all possible window messages as events; it ignores the ones it doesn’t know or care about. Ordinarily, this isn’t a problem, but occasionally, like dealing with Shell_NotifyIcon, it complicates our professional lives a little.

Shell_NotifyIconlets us specify which message it should send to our application when something happens with the tray icon. It attaches a parameter to that message to tell us what the user did to the icon. Looking at the available form-level events, there aren’t many that accept arguments, and the situation is complicated by the fact that VB will often mangle message parameters into a different type before passing them to the event handlers that do accept arguments.

Actually, it’s possible to handle messages that VB would ordinarily simply discard, through an advanced technique called subclassing.

Also, some VB events are actually a combination of messages. For example, a mouse click is actually two different messages, a button down message and a button up message.

Mouse, move!
I decided that the MouseMove event was the easiest to use for this example (although it’s certainly possible to use others). The message parameter sent by Shell_NotifyIcon will be received by the event handler as the X argument and may need a little manipulation to translate into the appropriate window message (just divide it by the horizontal scale factor of the display, Screen.TwipsPerPixelX).

One common feature of tray icons is displaying a menu of options when they are clicked. This is simple to do. Suppose you want to display the contents of your form’s File menu, mnuFile, when the user right-clicks on the tray icon. Just add the popupMenu method call to the WM_RBUTTONUP case statement in the form’s MouseMove event handler:
Me.popupMenu Me.mnuFile

Stump the guru
Until Microsoft introduced Windows 2000 and Internet Explorer 5, tray icon functionality remained essentially unchanged from that in Windows 95. However, Win2K introduced some new UI wrinkles with its new version of the Windows shell, referred to by Microsoft as “Version 5.” One of these new features is the ability to display “balloon-style” tooltips over tray icons, as shown in Figure B:

Figure B
Doh! Stupid dongle!

I had originally intended to illustrate the use of some of these features for this column, but unfortunately, I hit a snag: The Version 5 implementation of NOTIFYICONDATA contains a C/C++ union data-type as part of the structure. One of the parts of this union happens to be the parameter that switches on the new tray features. After several unsuccessful attempts at initializing Shell_NotifyIcon with the Version 5 switch set, I reached the end of my rope (and my deadline) and was forced to give up.

So, here’s your chance to one-up’s resident VB guru: Fix the code in Listing B so that the indicated function call succeeds. If you figure it out, e-mail the fix to me. Good luck.



Editor's Picks