This content has moved - please find it at https://devblog.cyotek.com.

Although these pages remain accessible, some content may not display correctly in future as the new blog evolves.

Visit https://devblog.cyotek.com.

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:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());

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.

public abstract class TrayIconApplicationContext : ApplicationContext
{
  protected TrayIconApplicationContext()
  {
    Application.ApplicationExit += this.ApplicationExitHandler;
  }

  protected virtual void OnApplicationExit(EventArgs e)
  {

  }

  private void ApplicationExitHandler(object sender, EventArgs e)
  {
    this.OnApplicationExit(e);
  }
}

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.

private readonly NotifyIcon _notifyIcon;

protected TrayIconApplicationContext()
{
  Application.ApplicationExit += this.ApplicationExitHandler;

  _notifyIcon = new NotifyIcon
  {
    Text = Application.ProductName,
    Visible = true
  };
  this.TrayIcon.MouseDoubleClick += this.TrayIconDoubleClickHandler;
  this.TrayIcon.MouseClick += this.TrayIconClickHandler;
}

protected NotifyIcon TrayIcon
{
  get { return _notifyIcon; }
}
  
protected virtual void OnTrayIconClick(MouseEventArgs e)
{ }

protected virtual void OnTrayIconDoubleClick(MouseEventArgs e)
{ }

private void TrayIconClickHandler(object sender, MouseEventArgs e)
{
  this.OnTrayIconClick(e);
}

private void TrayIconDoubleClickHandler(object sender, MouseEventArgs e)
{
  this.OnTrayIconDoubleClick(e);
}

We'll also update OnApplicationExit to clear up the icon:

if (_notifyIcon != null)
{
  _notifyIcon.Visible = false;
  _notifyIcon.Dispose();
}

Even though we are setting the icon's Visible property to true, nothing will happen as you need to assign the Icon 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.

private readonly ContextMenuStrip _contextMenu;

protected TrayIconApplicationContext()
{
  _contextMenu = new ContextMenuStrip();

  Application.ApplicationExit += this.ApplicationExitHandler;

  _notifyIcon = new NotifyIcon
  {
    ContextMenuStrip = _contextMenu,
    Text = Application.ProductName,
    Visible = true
  };
  this.TrayIcon.DoubleClick += this.TrayIconDoubleClickHandler;
  this.TrayIcon.Click += this.TrayIconClickHandler;
}

protected ContextMenuStrip ContextMenu
{
  get { return _contextMenu; }
}

Again, we'll update the exit handler to dispose of the menu:

if (_contextMenu != null)
  _contextMenu.Dispose();

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:

class CustomApplicationContext : TrayIconApplicationContext
{
  public ApplicationContext()
  {
    this.TrayIcon.Icon = Resources.SmallIcon;

    this.ContextMenu.Items.Add("&Settings...", null, this.SettingsContextMenuClickHandler).Font = new Font(this.ContextMenu.Font, FontStyle.Bold);
    this.ContextMenu.Items.Add("-");
    this.ContextMenu.Items.Add("&About...", null, this.AboutContextMenuClickHandler);
    this.ContextMenu.Items.Add("-");
    this.ContextMenu.Items.Add("E&xit", null, this.ExitContextMenuClickHandler);
  }

  protected override void OnTrayIconDoubleClick(MouseEventArgs e)
  {
    this.ShowSettings();

    base.OnTrayIconDoubleClick(e);
  }

  private void AboutContextMenuClickHandler(object sender, EventArgs eventArgs)
  {
    using (Form dialog = new AboutDialog())
      dialog.ShowDialog();
  }

  private void ExitContextMenuClickHandler(object sender, EventArgs eventArgs)
  {
    this.ExitThread();
  }

  private void SettingsContextMenuClickHandler(object sender, EventArgs eventArgs)
  {
    this.ShowSettings();
  }

  private void ShowSettings()
  {
    using (Form dialog = new SettingsDialog())
      dialog.ShowDialog();
  }
}

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.

[STAThread]
private static void Main()
{
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);
  Application.Run(new CustomApplicationContext());
}

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 and MouseDoubleClick events to show windows doesn't really work as shown in the example project. Perhaps this can be worked around by using the MouseUp 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 and ShowInTaskbar 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 the Shown event
protected override void OnShown(EventArgs e)
{
  base.OnShown(e);

  this.Activate();
}

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

Downloads

Filename Description Version Release Date
ApplicationContextExample.zip
  • md5: 672e52b457e869f33e5ad810cdf69182

Sample project for the creating long running Windows Forms applications without a start-up form blog post.

26/08/2013 Download

About The Author

Gravatar

The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game. Until that time, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.

Leave a Comment

While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?

Styling with Markdown is supported

Comments

Rene

# Reply

Good example. Thank you for saving my day. :-)

Gravatar

Richard Moss

# Reply

You're welcome, glad you found it of use.

Regards; Richard Moss

shawtza

# Reply

thanks Richard!

Kiran Patel

# Reply

You are life saver! Missed core thing in my implementation was always visible Status Window form as given in your code.

I have to schedule the app through schedule task manager with only system tray icon visible but nothing was shown when executing it through schedule task run. After adding always visible Status Window form; it works like a charm!!!

Gravatar

Richard Moss

# Reply

You're welcome, I'm glad you found it useful!

Regards;
Richard Moss

Gravatar

Darin

# Reply

Thank you for your work. I found this very useful.

Also, your note "Opened dialogs were frequently displayed behind existing windows of other applications." is an easily fixable issue. Google [C# messagebox.show "new form"], replacing "messagebox.show" with the type of dialog you want, and then search within each result's page for "new form". You will find it is hit and miss but you will find some people writing code like this (This example is from code I'm actively using..): var OFD = new OpenFileDialog() { Title = Title, Filter = Filter, FilterIndex = 1, RestoreDirectory = true, CheckFileExists = CheckExist, ValidateNames = true, Multiselect = false, InitialDirectory = FolderPath, }; if (OFD.ShowDialog(new Form() { TopMost = true, TopLevel = true }) == DialogResult.OK) { <<Code to process the user's selected file>> }

This works because ShowDialog takes on the properties, or one might say persona, of its owner ("new Form()" in this case). I believe the "new form()" is short lived and its memory is recycled. The new form does not show on the screen and has worked flawlessly every time I've used it.

Gravatar

Richard Moss

# Reply

Darin,

Thanks for the comments. Interesting approach for solving the modal issue, not a solution I would have thought of trying given it's slightly hackish nature. Useful to know, "just in case"!

Thanks again;
Richard Moss

Gravatar

Eugene Mayevski

# Reply

Thank you very much, you've saved us from hours of experiments and looking for a black cat where it's absent!

Gravatar

NetMage

# Reply

Note that the code for adding the TrayIcon click events when you add the context menu in the constructor is incorrect - it uses DoubleClick and Click instead of MouseDoubleClick and MouseClick like earlier.

Gravatar

NetMage

# Reply

Also, your CustomApplicationContext constructor has the wrong name - it should not be ApplicationContext.

Gravatar

Richard Moss

# Reply

Hello,

Thanks for the corrections - that's what happens I suppose when you write the article in tandem with the demo! I tend to do the demo's first now and then go back and write an article but I'd be willing to be there's other errors elsewhere. In most cases though, the accompanying download or Git repo is the source of truth, unless it is a simple article the complete source code generally isn't part of the article text.

Regards;
Richard Moss