Introduction
This article was prompted by a question in Quick Answers about how to write a VB.NET application that started in the tray. There were a number of examples here at CodeProject -- this article[^] by member [ICR] in particular -- but I could not find anything in VB. This article is meant to remedy that situation.
The Theory
As C# programmers know, applications start with a call to System.Windows.Forms.Application.Run
. This call is normally hidden from VB.NET programmers, with the compiler supplying the required code behind the scenes. We can call this explicitly, however, which allows us to write applications that do not rely on start-up forms.
AppContext Class
The first step is to create a class that inherits from System.Windows.Forms.ApplicationContext
. This provides the information needed by the Operating System to manage your application. This is also where you instantiate your NotifyIcon
for the system tray, a menu to interact with the icon and any other essentials.
Public Class AppContext
Inherits ApplicationContext
#Region " Storage "
Private WithEvents Tray As NotifyIcon
Private WithEvents MainMenu As ContextMenuStrip
Private WithEvents mnuDisplayForm As ToolStripMenuItem
Private WithEvents mnuSep1 As ToolStripSeparator
Private WithEvents mnuExit As ToolStripMenuItem
#End Region
#Region " Constructor "
Public Sub New()
mnuDisplayForm = New ToolStripMenuItem("Display form")
mnuSep1 = New ToolStripSeparator()
mnuExit = New ToolStripMenuItem("Exit")
MainMenu = New ContextMenuStrip
MainMenu.Items.AddRange(New ToolStripItem() {mnuDisplayForm, mnuSep1, mnuExit})
Tray = New NotifyIcon
Tray.Icon = My.Resources.TrayIcon
Tray.ContextMenuStrip = MainMenu
Tray.Text = "Formless tray application"
Tray.Visible = True
End Sub
#End Region
#Region " Event handlers "
Private Sub AppContext_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.ThreadExit
Tray.Visible = False
End Sub
Private Sub mnuDisplayForm_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuDisplayForm.Click
ShowDialog()
End Sub
Private Sub mnuExit_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuExit.Click
ExitApplication()
End Sub
Private Sub Tray_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Tray.DoubleClick
ShowDialog()
End Sub
#End Region
End Class
This is pretty straightforward. The icon and menus are initialized in the class' Sub New
, and the icon is made visible. The ThreadExit
is where you clean up objects created within the class. The class also handles the Click
event for mnuDisplay
, which displays a dialog form, and mnuExit
, which closes the application. Double clicking on the icon has the same effect as clicking mnuDisplay
.
OtherMethods Module
I put the methods ExitApplication
and ShowDialog
in a separate code file.
Friend Module OtherMethods
Private PF As PopupForm
Public Sub ExitApplication()
Application.Exit()
End Sub
Public Sub ShowDialog()
If PF IsNot Nothing AndAlso Not PF.IsDisposed Then Exit Sub
Dim CloseApp As Boolean = False
PF = New PopupForm
PF.ShowDialog()
CloseApp = (PF.DialogResult = DialogResult.Abort)
PF = Nothing
If CloseApp Then ExitApplication
End Sub
End Module
The module is tagged as Friend
to prevent it from being exported. ExitApplication
is where you would clean up any objects that are external to AppContext
. Your application shuts down with the call to Application.Exit
, which triggers AppContext
's ThreadExit
event. If you have any open forms, Application.Exit
will close those down, exactly as you would expect.
ShowDialog
displays a simple dialog box that allows the user to either cancel the form or close the application. I had to do a bit of extra work to guarantee that one and only one dialog box gets displayed: clicking mnuDisplayForm
will happily spawn multiple forms otherwise.
Main Method
The next step is to write the code that launches your application. In a public module, declare one of four variations of the Main
method.
Public Module LaunchApp
Public Sub Main()
Application.EnableVisualStyles()
Application.Run(New AppContext)
End Sub
End Module
In most cases, version 1 will be sufficient. If you need access to the command line, then you will want to use version 2. Versions 3 and 4 allow you to return an integer value to the Operating System when your application closes, which is useful only in rare situations.
The call to Application.Run
is the key here. There are three overloads: instead of an ApplicationContext
, you can also pass a Form
object or no parameter. The no parameter version creates a default context which is useful for console applications. The Form version is what VB normally uses internally. You can conditionally call different versions of Application.Run
, like so:
Public Sub Main(ByVal cmdArgs() As String)
Application.EnableVisualStyles()
Dim UseTray As Boolean = False
For Each Cmd As String In cmdArgs
If Cmd.ToLower = "-tray" Then
UseTray = True
Exit For
End If
Next
If UseTray Then
Application.Run(New AppContext)
Else
Application.Run(New MainForm)
End If
End Sub
With this, you can use a command line switch to decide whether your app will launch with a main form or into the notification area.
Note that the first line of code is Application.EnableVisualStyles
. For reasons explained below, it is necessary to turn visual styles off when configuring your project; this is where you turn them back on. You don't have to do this, of course, but if your app has any interface elements, they will look nicer if you do.
My Project
The last step is to configure your project to use the Main
method. Start by pulling up the My Project interface. On the Application tab, uncheck the "Enable application framework" box. This disables various application framework properties, including the XP visual styles (which is why we need to turn them back on). Now, go to the "Startup object" drop-down and select Sub Main
. Be aware that you will select Sub Main
even if you are using one of the Function Main
methods.
When you run your application, the bootstrap will now call Main
. Your custom context class will be launched, which sets up an interactive icon in the notification area.
Conclusion
I hope you found this article useful; if so, please vote it up. And if you find any bugs, please let me know below and I will try to get them fixed.
History
- Version 4 - May 4, 2010 - Clarified a few points, fixed some grammar, and changed references to "tray" into either "notification area" (the official name) or "system tray" (which Microsoft says is wrong, but still uses in its own documentation). My apologies for the naming confusion.
- Version 2, 3 - April 26, 2010 - Initial release.