Click here to Skip to main content
15,867,835 members
Articles / Desktop Programming / Windows Forms

Close Your Application to the Notification Area

Rate me:
Please Sign up or sign in to vote.
4.61/5 (12 votes)
25 Aug 2010CPOL5 min read 48K   1.8K   38   13
How to write an app that will go to the system tray when the user closes it
Tray.png

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:

VB.NET
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
        'Guarantees that the icon will not linger.
        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.

VB.NET
#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.

VB
#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.

VB.NET
Public Sub InitializeApp()
    'Initialize the menus
    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})

    'Initialize the notification icon
    Notify = New NotifyIcon
    Notify.Icon = My.Resources.MainIcon
    Notify.ContextMenuStrip = MainMenu
    Notify.Text = "Formless tray application"

    'Create the application form
    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.

VB.NET
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

TrayMenu.png

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.

FormMenu.png
VB.NET
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Gregory Gadow recently graduated from Central Washington University with a B.S. that combined economics and statistical analysis, and currently works for the Washington Department of Fish & Wildlife as an IT developer. He has been writing code for 30 years in more than a dozen programming languages, including Visual Basic, VB.Net, C++, C#, ASP, HTML, XML, SQL, and R.

Comments and Discussions

 
QuestionFormless... Pin
R. Giskard Reventlov23-Aug-13 10:53
R. Giskard Reventlov23-Aug-13 10:53 
QuestionSettings Pin
Vet Ralph28-Apr-12 7:50
Vet Ralph28-Apr-12 7:50 
AnswerRe: Settings Pin
Gregory Gadow28-Apr-12 10:56
Gregory Gadow28-Apr-12 10:56 
QuestionRe: Settings Pin
Vet Ralph29-Apr-12 1:55
Vet Ralph29-Apr-12 1:55 
QuestionWHAT I NEEDED Pin
JDJTsg16-Jan-12 8:13
JDJTsg16-Jan-12 8:13 
GeneralMy vote of 5 Pin
JDJTsg16-Jan-12 8:08
JDJTsg16-Jan-12 8:08 
GeneralMy vote of 4 Pin
Md. Marufuzzaman15-Sep-10 22:11
professionalMd. Marufuzzaman15-Sep-10 22:11 
QuestionWhy mess with ApplicationContext? Pin
Pamodh9-Sep-10 4:25
Pamodh9-Sep-10 4:25 
AnswerRe: Why mess with ApplicationContext? Pin
Gregory Gadow9-Sep-10 7:39
Gregory Gadow9-Sep-10 7:39 
Flexibility.

1. When you launch your app as a form, which is the VB default, it will always launch as a visible form. You can immediately close it to the notification area, but you will still briefly have a visible form, which may not be what you want.

2. You can conditionally select which form will be your application's interface. Perhaps you validate your user on launch: for general users, AppForm is initialized to one form, power users get a different form and admins get a third. You could write a single form with three varying layouts, but different forms are more flexible and certainly easier to maintain.

3. You can have your app thread run without any kind of form, and create ad hoc user interfaces that exist only as long as needed. This would greatly reduce the resource overhead of your application, especially if your app will usually be running from the tray and not as a form.

4. You could do the above things by just using Sub Main, but then you are depending on the user to manually exit the app if you need to close files, clean up handles and save the application state. Short of a catastrophic reboot -- like the machine blowing up -- the ThreadExit event of ApplicationContext will always be called, which means your exit code will be executed even if Windows is shutting down or the user cancelled the application thread through Task Manager.

For a simple app like the demo, ApplicationContext is probably overkill, yes. But using it does not really add to the complexity of your code, and will make it easier to scale future versions of your app.
GeneralRe: Why mess with ApplicationContext? Pin
Pamodh10-Sep-10 2:15
Pamodh10-Sep-10 2:15 
QuestionRe: Why mess with ApplicationContext? Pin
MaximilianReisch9-Oct-10 4:24
MaximilianReisch9-Oct-10 4:24 
GeneralBrilliant :) Pin
Anthony Daly1-Sep-10 9:06
Anthony Daly1-Sep-10 9:06 
GeneralRe: Brilliant :) Pin
Gregory Gadow1-Sep-10 11:34
Gregory Gadow1-Sep-10 11:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.