Introduction
This article expands on an earlier one of mine, Create a System Tray Application in VB.NET[^]. If you are unfamiliar with how to write applications for the notification area, that might be a good place to start.
Background
I recently got a BitTorrent downloader that does something very interesting: when the application is closed, it switches into a background mode and minimizes to an icon in the notification area. Right clicking the icon gives a menu that shows me how many torrents are still downloading and lets me restore to a regular window or exit the app completely. That seemed like a spiffy piece of functionality, so I set out to learn how I could write applications that do the same thing. This article is the result.
As usual, I wrote this in VB.NET, because that is the language I am most familiar with. It should be pretty easy to translate into C#: just keep in mind that some of the implementation is VB specific.
The code and demo were tested using Visual Studio 2008 and the 3.5 Framework; it should work with .NET 4.0, and probably with 2.0 as well. If not, please leave a comment.
Context is Everything
Effectively, this technique creates a notification area application that may or may not have a visible user interface. We start by creating a derivative of System.Windows.Forms.ApplicationContext
:
Public Class AppContext
Inherits ApplicationContext
#Region " Constructor "
Public Sub New(ByVal StartClosed As Boolean)
InitializeApp()
If StartClosed Then
Notify.Visible = True
Else
AppForm.Show()
End If
End Sub
#End Region
#Region " Event handlers "
Private Sub AppContext_ThreadExit(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Me.ThreadExit
Notify.Visible = False
End Sub
#End Region
End Class
When AppContext
is created, it takes a parameter indicating whether the app will start already closed to the notification area. After initializing everything, the application thread launches by either displaying the icon in the system tray or by showing the application's form. When the application thread ends -- it was exited by the user, cancelled in the Program Manager, Windows is shutting down, whatever -- the ThreadExit
event gets called, which allows you to do whatever cleanup is necessary, such as closing files and saving the application state. All I am doing here is making sure that the icon has been removed from the notification area.
The Code Behind the Curtain
The next step is to create MainModule
with the infrastructure that actually manages the app. Declare the components with the WithEvents
keyword so you can intercept the components' events.
#Region " Global storage "
Public WithEvents AppForm As MainForm
Public WithEvents Notify As NotifyIcon
Public WithEvents MainMenu As ContextMenuStrip
Public WithEvents mnuShow As ToolStripMenuItem
Public WithEvents mnuSep1 As ToolStripSeparator
Public WithEvents mnuExit As ToolStripMenuItem
Public TimerMenu As ToolStripMenuItem
#End Region
Then create the Sub Main
method, which will be the entry point for your application.
#Region " Sub Main "
Public Sub Main(ByVal cmdArgs() As String)
Application.EnableVisualStyles()
Dim StartClosed As Boolean = False
For Each Cmd As String In cmdArgs
If Cmd.ToLower = "/c" Then
StartClosed = True
Exit For
End If
Next
Application.Run(New AppContext(StartClosed))
End Sub
#End Region
I used the variant that passes command line arguments. In this demo, the application will normally start as a regular window; a command argument of /c tells the application to start closed. The method turns visual styles back on (you will see later why it was disabled; I think this is a VB-only step that C# programmers can ignore), determines how the application will start, then launches the app by calling Application.Run
with a new instance of AppContext
.
When AppContext
is created, the method InitializeApp
gets called.
Public Sub InitializeApp()
TimerMenu = New ToolStripMenuItem
mnuShow = New ToolStripMenuItem("Show application")
mnuSep1 = New ToolStripSeparator()
mnuExit = New ToolStripMenuItem("Exit application")
MainMenu = New ContextMenuStrip
MainMenu.Items.AddRange(New ToolStripItem() _
{TimerMenu, mnuShow, mnuSep1, mnuExit})
Notify = New NotifyIcon
Notify.Icon = My.Resources.MainIcon
Notify.ContextMenuStrip = MainMenu
Notify.Text = "Formless tray application"
AppForm = New MainForm
AppForm.Icon = My.Resources.MainIcon
End Sub
The context menu for the icon gets constructed, the icon itself is instantiated and initialized, and the application form is created and set up.
The rest of MainModule
is the code that changes the application state and handles the click events on the icon's context menu.
Public Sub CloseApp()
Notify.Visible = True
AppForm.Visible = False
End Sub
Public Sub ExitApp()
Application.Exit()
End Sub
Public Sub MinimizeApp()
AppForm.WindowState = FormWindowState.Minimized
End Sub
Public Sub RestoreApp()
Notify.Visible = False
AppForm.Visible = True
End Sub
Private Sub mnuExit_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuExit.Click
ExitApp()
End Sub
Private Sub mnuShow_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuShow.Click
RestoreApp()
End Sub
When the application is closed, the icon will be in the notification area. Right clicking on it pulls up a menu showing application feedback (in this demo, the current date and time) and options to either show the application form or exit the application completely.
You will see that RestoreApp
does not change AppForm.WindowState
. If the form is maximized or minimized when it is closed, it will be restored as maximized or minimized.
Note that setting AppForm.Visible = False
only hides the form; it does not halt execution of its code. Your application will continue to run unobtrusively, without any clutter in the taskbar. If your app needs to make a distinction between "active" and "background" modes, you can use CloseApp
and RestoreApp
to switch states.
The Form
The form for the demo is very basic. It has a MenuStrip
with options to minimize, close and exit the application, a Timer
, a Label
named TimeLabel
and a TextBox
. Clicking the menu items will call the MinimizeApp
, CloseApp
or ExitApp
method from MainModule
. In the form's constructor, TimeLabel
is set to display the current date and time, and the timer is set to go off every second. When the timer rings, both TimeLabel
and TimerMenu
are updated to the current date and time.
Public Class MainForm
#Region " Constructors "
Public Sub New()
InitializeComponent()
TimeLabel.Text = DateTime.Now.ToString
TimerMenu.Text = TimeLabel.Text
Timer1.Interval = 1000
Timer1.Start()
End Sub
#End Region
#Region " Event handlers "
Private Sub MainForm_FormClosing(ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles Me.FormClosing
If e.CloseReason = CloseReason.UserClosing Then
e.Cancel = True
CloseApp()
End If
End Sub
Private Sub mnuFormClose_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles mnuFormClose.Click
CloseApp()
End Sub
Private Sub mnuFormExit_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles mnuFormExit.Click
ExitApp()
End Sub
Private Sub mnuFormMinimize_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles mnuFormMinimize.Click
MinimizeApp()
End Sub
#End Region
Private Sub Timer1_Tick(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Timer1.Tick
TimeLabel.Text = DateTime.Now.ToString
TimerMenu.Text = TimeLabel.Text
End Sub
End Class
What makes this all work is the FormClosing
event. The FormClosingEventArgs
parameter allows us to see why the form is being closed. If it is closing because of user intervention -- that is to say, the user clicked the Close Form button in the upper right corner or selected Close in the app's taskbar context menu -- we can cancel the close and instead call CloseApp
.
Project Settings
Because of the way Visual Basic code gets compiled, VB programmers have a bit more work to do before this will work as expected. Go into the Project Settings form (Project in the Visual Studio main menu, then project name Properties at the bottom) and select the Application tab. Uncheck Enable application framework; this tells the compiler that you will be managing the infrastructure. One side effect of this is that visual styles get disabled, which is why we turn it back on manually in Sub Main
. In the Startup object drop-down, select Sub Main
.
Final Note
I hope you found this article useful. As always, I welcome comments about bugs and other problems in the code, and feedback is always welcome. If some enterprising soul would like to translate this code into C# and post it as an article, please give me credit.
Article history
- Version 1 - August 25, 2010 - Initial release