The solution attached to this post allows for basic system tray functionality. You can create your systray icon (and change it on the fly) from a form icon or by loading/drawing an image on a 32x32 pixel picturebox. There is easy support for a popup menu, as well as showing standard balloon tips. That's all there is to basic systray icons.
If that's all you need, the attached demo project is for you. No dlls or ocxs; it's all pure native VB6, meaning fewer headaches for the developer. The logic is contained in a class, and is exposed to the developer in a form.
On to the demo:
Last edited by Ellis Dee; Dec 16th, 2009 at 04:57 AM.
As we can see from basDemo.bas, firing up the system tray couldn't be simpler:
vb Code:
Option Explicit
Private Sub Main()
If App.PrevInstance Then Exit Sub
Load frmSysTray
End Sub
You pretty much never want two instances of a tray app running, so we use Previnstance to cancel multiple instances. If you decide to abandon the Sub Main approach and just make frmSysTray your startup form, add the following line of code to the very beginning of the Form_Load event:
If App.PrevInstance Then Unload Me
Events
The systray class fires messages directly to frmSysTray, so when you want to capture feedback from the tray icon, add event handlers to the form as if you were working with any regular control. Select "SysTray" from the left code dropdown -- where it typically starts with "(General)" -- and then choose any of the events you like from the code dropdown on the right. Supported events are:
The RightClick() event is where you typically open a popup menu, which is exactly what the demo does. Define your tray icon's popup menu in frmSysTray and handle the events from it there as well, just as with any regular menu.
Note that LeftClick() can supercede DoubleClick(), so basically figure you can support one or the other.
Methods / Properties
In addition to the tray icon talking to your app, your app will want to talk to the tray icon. From within frmSysTray, you can access the SysTray class using the following methods/properties:
SysTray.Init
Call this once in frmSysTray's Form_Load event to connect the class to the form. Optionally send a tooltip to display if the user hovers their mouse over the tray icon.
SysTray.MouseMove
This is an internal method you don't need to (and shouldn't) mess with. It is how the class captures messages from Windows, which in turn get interpreted and sent back to the form for you to use.
SysTray.ShowBalloonTip
Works the same as a msgbox. Send it a message, icon and title, plus optionally send the number of milliseconds it should stay on the screen before closing itself. Default is 30 seconds.
SysTray.SetIcon
Send this method a form and the tray icon will use that form's icon.
SysTray.DrawIcon
Send this method a picturebox and the tray icon will convert the picture into an icon. Optionally send a color to be used for transparency. This lets you draw the icon yourself using Line() commands if you need fine control, like say an activity indicator. Or you can use LoadPicture to work with image files. Note that the picturebox is 32x32 and the tray icon is typically only 16x16, so your images will likely get squished.
SysTray.TooltipText
Simple property to set the tooltip text if you need to change it during runtime.
As written, the SysTray object is only available from within frmSysTray. If you want to access the tray in a different form or from a module or class, change the declaration at the top of frmSysTray to public:
Public WithEvents SysTray As clsSysTray
Now you can access it directly from anywhere in your project by first specifying the form. eg:
frmSysTray.SysTray.ShowBalloonTip "Hello, world!"
Last edited by Ellis Dee; Dec 16th, 2009 at 02:26 AM.
If you want some tips on Vista/Win7 compatibility, here are a few. I've been doing some research on this topic recently.
1. Vista offers users option to display a popup window instead of a tooltip. In Vista, hover over your Sound icon and the new "tip" should show. In order to know that the window should be displayed, new msgs were added: NIN_POPUPOPEN and NIN_POPUPCLOSE. Additionally, Vista can send the mouse coordinates back with the message.
:: The above requires at least two modifications
a. There is now a NOTIFYICON_VERSION_4 = 4 constant vs just version 3
b: Subclassing is required now, because one can no longer use VB's MouseMove events to trap the messages. With v4, wParam contains both icon id and message, lParam contains the X,Y coords
2. Vista offers a couple of other balloon options too. One can select a 32x32 icon or 16x16 icon for the balloon. In fact, the balloon icon can now be any icon you want and is no longer limited to those in WinXP and earlier. Some new constants for Vista
NIIF_LARGEICON = &H20
NIIF_RESPECT_QUIET_TIME = &H80
NIF_REALTIME = &H40
NIF_SHOWTIP = &H80
3. The NOTIFYICONDATA structure grew by 4 bytes in Vista. The balloon timeout is no longer honored/used
Some comments on your project, if you don't mind.
1. There are more messages that can be received
WM_CONTEXTMENU, all buttonclicks for the MiddleButton and XButton, NIN_KEYSELECT & NIN_SELECT
2. NIIF_USER is not defined in you BalloonIconEnum. If this option is chosen (XP and higher), the app icon is used.
3. FYI: You can offer a HideBalloon method if desired. Simply show a balloon with a null message and it will kill an existing balloon for the same icon.
4. Though not commonly used, it could be easy for you to offer multiple icons (i.e., 2 icons for same app displayed at same time) if you chose to do so.
Last edited by LaVolpe; Dec 16th, 2009 at 03:25 PM.
Reason: clarified last point
Insomnia is just a byproduct of, "It can't be done"
if i want to change the icon in the system tray where should i go and what should i edit?
Set the form icon in the form frmSysTray, and then from inside the form call SysTray.SetIcon Me.
Originally Posted by LaVolpe
Some comments on your project, if you don't mind.
1. There are more messages that can be received
WM_CONTEXTMENU, all buttonclicks for the MiddleButton and XButton, NIN_KEYSELECT & NIN_SELECT
2. NIIF_USER is not defined in you BalloonIconEnum. If this option is chosen (XP and higher), the app icon is used.
3. FYI: You can offer a HideBalloon method if desired. Simply show a balloon with a null message and it will kill an existing balloon for the same icon.
4. Though not commonly used, it could be easy for you to offer multiple icons (i.e., 2 icons for same app displayed at same time) if you chose to do so.
By all means. I just discovered this technique a couple days ago, so I'm far from an expert or anything.
1. What is ContextMenu, and how does it differ from RightClick? KeySelect? How do you use the keyboard with the system tray? I don't really understand any of the constants from this point; a little help?
3. Sweet, I looked around for how to do that. Clever. It won't disappear until it's been open at least 10 seconds, but still better than nothing.
4. What's the thinking behind having two icons display at the same time? I don't follow.
1. What is ContextMenu, and how does it differ from RightClick? KeySelect? How do you use the keyboard with the system tray? I don't really understand any of the constants from this point; a little help?
With WinXP+, WM_ContextMenu was added & simply means rightclick. You still get the right down/up, but get WM_ContextMenu also.
KeySelect is odd. Here is how you can test it in XP.
:: Click that tray icon/circle that shows/hides tray icons. Leave mouse over the circle/icon
:: Use right arrow and move over the various icons. Pressing Space or Enter will trigger a KeySelect. Honestly, why would someone navigate the system tray that way?
Originally Posted by Ellis Dee
3. Sweet, I looked around for how to do that. Clever. It won't disappear until it's been open at least 10 seconds, but still better than nothing.
With Vista, it is immediate. Maybe setting both the balloon title & message may make it immediate in XP and below? Curiosity.. will test this myself later.
Originally Posted by Ellis Dee
4. What's the thinking behind having two icons display at the same time? I don't follow.
Not many apps do this. Avast anti-virus app does. One is used primarily for database notifications/actions while the other is used for app settings. Avast was smart enough to allow user to combine both icons to a single icon. Personally, I don't want an app adding multiple icons to my tray, but IMO should be a user's choice. I think in your project, just creating another systray form would do the trick. In my classes, I use the .UID member of the tray structure to identify each tray icon created.
With Vista, it is immediate. Maybe setting both the balloon title & message may make it immediate in XP and below? Curiosity.. will test this myself later.
On second thought, I don't actually know that it isn't immediately cleared on XP if you send a blank balloon. I was reacting to the one-balloon-at-a-time functionality of the system tray, where if you stack up a bunch of messages they will only appear one at a time, waiting the minimum time (10 seconds) before closing each. If you send a blank balloon it may well simply close the currently open one. I'll test it and update the project with a new method if it does.
I really appreciate the feedback, especially those MSDN links. I wanted to create a good codebank thread for simple system tray functionality. The ones I found were all super-advanced (animated? Messenger?) and not practical for those who just want a little icon to show up and be clickable. heh. This thread should serve that need well, as a nice jumping off point for all things systray.
:: Use right arrow and move over the various icons. Pressing Space or Enter will trigger a KeySelect. Honestly, why would someone navigate the system tray that way?
Accessibility for the disabled. Microsoft was probably required by law to add that functionality.
Is it possible to show the balloon without that exit button in the top-right corner?
In one of my apps the balloon only pops up to notify the user, clicking on the balloon doesn't do anything except close the balloon and set focus back on the form that was in use.
The problem is setting focus to a form after receiving the balloon "exit" click message, setfocus calls are ignored. If the user clicks the balloon setfocus works fine. What gives?
EDIT:
I tried this but when the balloon exit button is clicked the forms titlebar just flashes.
Code:
Case BalloonClick, BalloonExit
If Form1.WindowState <> 1 Then SetForegroundWindow Form1.hwnd
Last edited by Edgemeal; Dec 19th, 2009 at 05:31 AM.
Not sure offhand, but my gut reaction is the standard tried-and-true work-around: timers. Kludgy I know, but for a temporary solution, consider using a timer with an interval of around 100. Have the BalloonExit event enable the timer, and have the timer do the setfocus and then turn itself off. Whatever it is about the event procedure that ignores the setfocus call will get purged as that event is allowed to end naturally. The timer then does its own independent thing, meaning it should be able to setfocus without issue.
Hopefully that will work well enough to tide you over until a real solution can be found.
Is it possible to show the balloon without that exit button in the top-right corner?
After playing around with it for a bit for an app I'm writing, yes, just don't send a title. When you don't send a title the icon is ignored too and the only thing displayed is the message text.
On an unrelated note, the timeout setting seems to be ignored and it just stays open forever. I've added a second timer to frmSysTray that sends a blank message, immediately clearing the balloon tip. I then added three lines to the end of clsSysTray.ShowBalloonTip that allows leaving the balloontip open for any amount of time you like up to a minute:
After playing around with it for a bit for an app I'm writing, yes, just don't send a title. When you don't send a title the icon is ignored too and the only thing displayed is the message text.
I tried that with my balloon code and it does get rid of the exit button and the balloon auto closes after about 5-10 seconds if I do something, otherwise it seems to just stay open forever like you say which is actually how I'd want it to work anyway. I never even tried no title, good catch!
i tried practicing it at home.
when i right-click on the icon at the taskbar after running the project, nothing happen. the only thing that keeps working is the tooltip n nothing more.
am using VISTA version of windows and i don't know if the windows has got something to do with it.
any help?
There's only one thing that makes a dream impossible to achieve: THE FEAR OF FAILURE
when i right-click on the icon at the taskbar after running the project, nothing happen. the only thing that keeps working is the tooltip n nothing more.
am using VISTA version of windows and i don't know if the windows has got something to do with it.
any help?
Don't have Vista here to test but one possible problem I see is the code expects the form to be set to pixel scalemode, if not then you need to convert X for the SysTray.MouseMove calls, maybe something like,..
Code:
Dim msg As Single
msg = ScaleX(X, ScaleMode, vbPixels)
SysTray.MouseMove Button, msg, Me
Upthread LaVolpe says that subclassing is required in Vista. Subclassing makes me wet myself, so I'm afraid I won't be much help with Vista support. If I can manage to get my hands on a Vista machine I'll try to take a look.
I think LaVolpe is saying subclass is needed if you want to use a popup window instead of a tooltip in Vista. ?
Might want to try using a pic box for the tray handle and mouse_move events instead of the form, years ago I ran into unexpected results using the form for the tray, been using a pic box ever since and haven't had a problem/complaint.
Might want to try using a pic box for the tray handle and mouse_move events instead of the form, years ago I ran into unexpected results using the form for the tray, been using a pic box ever since and haven't had a problem/complaint.
jeffrey, to try this, replace the Init() function in clsSysTray with this:
Code:
Public Function Init(ppic As PictureBox, pstrTooltip As String) As Boolean
Const NIF_MESSAGE = &H1
Const WM_MOUSEMOVE = &H200
Const NIM_ADD = &H0
With mtypIcon
.cbSize = Len(mtypIcon)
.hwnd = ppic.hwnd
.hIcon = ppic.Parent.Icon
.uID = vbNull
.uFlags = NIF_MESSAGE
.uCallbackMessage = WM_MOUSEMOVE
End With
Shell_NotifyIcon NIM_ADD, mtypIcon
SetIcon pfrm
Me.TooltipText = pstrTooltip
End Function
Then change Form_Load() in frmSysTray to this:
Code:
Private Sub Form_Load()
Set SysTray = New clsSysTray
Me.WindowState = vbMinimized
DoEvents
Me.Hide
SysTray.Init Me.pic, "System Tray demo"
End Sub
Ellis, you'll get an error since you are no longer passing the form to the Init func., maybe like this?...
Code:
Public Sub SetIcon(hIcon As Long)
mtypIcon.hIcon = hIcon
RefreshIcon
End Sub
Public Function Init(ppic As PictureBox, pstrTooltip As String) As Boolean
Const NIF_MESSAGE = &H1
Const WM_MOUSEMOVE = &H200
Const NIM_ADD = &H0
With mtypIcon
.cbSize = Len(mtypIcon)
.hwnd = ppic.hwnd
.hIcon = ppic.Parent.Icon
.uID = vbNull
.uFlags = NIF_MESSAGE
.uCallbackMessage = WM_MOUSEMOVE
End With
Shell_NotifyIcon NIM_ADD, mtypIcon
SetIcon ppic.Parent.Icon 'pfrm
Me.TooltipText = pstrTooltip
End Function
Ah, yeah, happily that's an easy fix. In the form code, add a single API call to the Right_Click() event, plus of course the API declaration.
Code:
Option Explicit
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
[...]
Private Sub SysTray_RightClick()
SetForegroundWindow Me.pic.hwnd ' Auto close menu if user clicks away
PopupMenu Me.mnuSysTray
End Sub
Last edited by Ellis Dee; Feb 27th, 2013 at 09:33 PM.
Oh dear why ya're messing around with a picturebox?
What about the VB 6 - Ressource-Editor to include icon's to the exe?
If the menu item 'Tools\Ressource editor' is not visible in ya VB6-IDE - you may need to enable this via 'Add-ins\Add-in-Manager'
<Moderator note: Removed links to EXE files.>
Okay well incase you are using 'Visual Basic 6 Portable' here is how to add the Ressource editor
Get these files from somewhere (sorry binaries are not allowed here)
c:\Program Files\Visual Basic 6 Portable\commtb32.dll
c:\Program Files\Visual Basic 6 Portable\wizards\rc.exe
c:\Program Files\Visual Basic 6 Portable\wizards\rcdll.dll
c:\Program Files\Visual Basic 6 Portable\wizards\resedit.dll
(c:\Program Files\Visual Basic 6 Portable\wizards\rsedtdeu.dll)
c:\Program Files\Visual Basic 6 Portable\wizards\rsedtenu.dll
regsvr32.exe resedit.dll
(regsvr32.exe rsedtdeu.dll)
regsvr32.exe rsedtenu.dll
Well extent the clsSysTray.cls by this:
Code:
Public Sub SetIconFromRes(ResID As Variant)
Me.Icon = LoadResPicture(ResID, vbResIcon)
End Sub
Public Property Get Icon() As Long
Icon = mtypIcon.hIcon
End Property
Public Property Let Icon(ByVal vNewValue As Long)
mtypIcon.hIcon = vNewValue
RefreshIcon
End Property
Now here are some inspiration for the use
vb Code:
SysTray.Icon = LoadPicture("WRENCH.ICO")
SysTray.SetIconFromRes 101
SysTray.SetIconFromRes "Pause"
and maybe now uncomment/delete these Function/subs
DrawIcon, BitmapToIcon, BitmapToIconTransparent, GetColMask
and also
' Bitmap to Icon
Private Type * and
Private Declare Function *
... and yes on more thing Attention:
When using clsSysTray.cls with an own form,
Set the form properties Scale\ScaleMode = 3 - Pixel !
If there is something else selected (like for ex. 1-Twips) the code ...
vb Code:
Public Sub MouseMove(Button As Integer, ByVal X As Long, pfrm As Form)
Select Case X
Case WM_LBUTTONDBLCLK: RaiseEvent DoubleClick
Case WM_LBUTTONUP: RaiseEvent LeftClick
Case WM_RBUTTONUP: RaiseEvent RightClick
...
...will not match the the constant 'WM_LBUTTONDBLCLK' due to other scaling
...and so stuff like the rightclick menu will not work!
... and you can delete 'Set frmSysTray = Nothing' in
vb Code:
Private Sub Class_Terminate()
...
Set frmSysTray = Nothing
End Sub
it is unnecessary.
Last edited by public; Apr 8th, 2013 at 07:11 AM.
Reason: Removed links to compiled programs
There may very well be more (and better) ways to skin this cat.
But the attached appears to do what you asked.
There is only one change in the class (making the terminate Public)
There is one change in the IDE to add the minimize button to the form
The other changes are in the Form's code.
Rob
PS I notice the form when arising from the sys tray, tends to be lower in the pecking order (you have to minimize other running apps, to see this form).
If you find that annoying, you could try Method 3 on this web page - http://www.vbforums.com/showthread.p...-top-of-others
Thanks for the info & .zip all working really well
If anyone else trips over this thread I have found from feedback that you really need to be using Global Hotkey to get the app. into the tray & out of the tray, this is much easier than a 'Restore' from the tray & option in the app. to go to the tray !!!