Creating long running Windows Forms applications without a start-up form
Sometimes you may wish to create an application that sits running in the background but doesn't actually display an initial user interface. However, the user can interact with the application and so therefore its not appropriate to be a service. Often such applications are accessible from a system tray icon. Another viable requirement might be for multiple top level windows, for example recent versions of Microsoft Word, where each document has its own application window.
By default however, a normal Windows Form application displays a
single start-up form which definitely isn't desirable when you
want to have a hidden UI, especially as hiding this form isn't
as straightforward as you might expect. Fortunately however, the
framework provides us with the ApplicationContext
class that
we can use to create a different approach to managing the
application.
Getting Started
If you look in Program.Main
, you'll see code similar to the
following:
The Application.Run
statement is the critical aspect, and it
operates by creating a new instance of your start-up form which,
when closed, causes the application to end. The Run
method
also allows you to pass in a custom ApplicationContext
class
instead which can be used for more flexibility. In order to exit
the application when using this class, you can either call the
static Application.ExitThread
or the ExitThread
method of
the ApplicationContext
class. In additional,
Application.Exit
seems to work just as well.
To start with, we're going to create a basic class that inherits
from ApplicationContent
and provides system tray icon and
context menu support. To that end, we'll create a class named
TrayIconApplicationContext
.
The first thing we need to do is hook into the ApplicationExit
event. We'll use this for clean-up purposes no matter if the
application is shut down via our new class, or other code
calling ExitThread
.
When the event handler is triggered, it calls the
OnApplicationExit
virtual method. This makes it easier for
inheritors of this class to provide their own clean up behaviour
without hooking into events. It seems a shame there isn't an
existing method to override in the first place without the the
initial hooking of events, but it's a minor thing.
Adding the tray icon
Now that we have the basic infrastructure in place, we can add
our tray icon. To do this we'll create an instance of the
NotifyIcon
component, accessible via an protected property.
We'll also automatically hook into the Click
and DoubleClick
events of the icon and provide virtual methods for inheritors.
We'll also update OnApplicationExit
to clear up the icon:
Even though we are setting the icon's
Visible
property totrue
, nothing will happen as you need to assign theIcon
property first.
Adding a context menu
Having a tray icon is very nice, but if the only interaction
possible with our application is double clicking the icon, it's
a bit of a limited application! We'll solve this by adding a
ContextMenuStrip
to the class, which will be bound to the
icon. Inheritors can then populate the menu according to their
requirements.
Again, we'll update the exit handler to dispose of the menu:
Creating the application
With our reusable application context class ready, we can now create a custom application specific version. Of course, you don't have to do this, you could just make the changes directly to the original class, but it's better to promote resuse where you can.
For example, a basic application which had a settings dialog and an about dialog could look something like this:
This sample creates a context menu with 3 items; two dialogs and a way to exit the program. Double clicking the icon also displays a dialog. Convention usually suggests that for the context menu, you display the primary item in bold - so in this example the bold item opens the settings dialog, matching the double click action.
Finally, we need to modify the entry point of our application to use the new class.
And that's how simple it is to set up an application with no start up form!
Notes
While writing a real utility program that made use of this technique for an application accessed via the system I had the following observations:
- Dialogs opened from this class are not modal. You can see this
by double clicking the tray icon several times - if you call
ShowDialog
, they aren't modal and you can can therefore open multiple dialogs by accessing the menu again etc. It's probably better to have instance variables for such forms, and then create them on first use, and activate the existing instance on subsequent calls. The full source code download available below shows examples of this. - Mixing the
MouseClick
andMouseDoubleClick
events to show windows doesn't really work as shown in the example project. Perhaps this can be worked around by using theMouseUp
event instead and theSystemInformation.DoubleClickSize
/SystemInformation.DoubleClickTime
properties but that's beyond the scope of this article. - As there is no top level main window to appear in the taskbar,
you should probably ensure any window that can be opened
directly from the tray icon has its
Icon
,ShowIcon
andShowInTaskbar
properties set. - Opened dialogs were frequently displayed behind existing
windows of other applications. I didn't observe this while
debugging the project, but only when running the program
outside the IDE. The simplest way I found to work around this
issue was to call
this.Activate()
from theShown
event
As usual an example project is available from the link below containing a demonstration of this technique.
Update History
- 2013-08-26 - First published
- 2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?
# Rene
# Richard Moss
# shawtza
# Kiran Patel
# Richard Moss
# Darin
# Richard Moss
# Eugene Mayevski
# NetMage
# NetMage
# Richard Moss