In this tip, you will learn to make a program whose main graphic interface consists of an icon in the system tray.
Introduction
Sometimes, when you do a desktop program, you want to display an icon in the system tray to be able to display some information to the user. Luckily, Microsoft has made this very easy for us developers by supplying us with the NotifyIcon
component. You can simply drop an instance of that on your form and presto! You've got easy access to the system tray right away - no coding needed!
But what happens if you want to do a program whose only user interface consists of the tray icon? You don't want to have a form to put it on!
In "the old days" (before VS.NET) when I worked a lot with Visual Basic 3 through 6, I would have created the form anyway, put the component on it, and have tried to hide the form. The way you would normally do that was to move it outside of the visual desktop area by setting the forms StartPosition
to Manual
and then setting the Top
and Left
properties to some ridiculously large negative number in the forms Load
routine.
I have always thought that doing that seemed like an unnecessary hack, and I still do that.
Here's How!
So how do you do it correctly in .NET? The "old way" still works of course, but why would I want to do that when I don't like the approach? And there is a better way of doing it!
If you look at the Program.cs file in your WinForms project, you will see that it looks something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TrayIconTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
When the application is run, it creates an instance of your form and runs it. And that is exactly what you don't want in this specific case.
You could try to put all your code in the Main
method of the Program
class and skip the call to the Application.Run
method, but that is not a good idea, and it doesn't work either.
But here's the thing: the Application.Run
method doesn't need to have a Form
parameter. It will accept an ApplicationContext
object instead. And that is what we need to do here.
We create a class and name it as we like (I call it MyApplicationContext
), and we make sure to derive it from the .NET ApplicationContext
object. Then we use that in our Run
method call instead of Form1
like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TrayIconTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyApplicationContext());
}
}
}
So now we can put our NotifyIcon
code in our MyAccplicationContext
class instead and not have to worry about hiding a Form
.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TrayIconTest
{
class MyApplicationContext : ApplicationContext
{
private NotifyIcon TrayIcon;
private ContextMenuStrip TrayIconContextMenu;
private ToolStripMenuItem CloseMenuItem;
public MyApplicationContext()
{
Application.ApplicationExit += new EventHandler(this.OnApplicationExit);
InitializeComponent();
TrayIcon.Visible = true;
}
private void InitializeComponent()
{
TrayIcon = new NotifyIcon();
TrayIcon.BalloonTipIcon = ToolTipIcon.Info;
TrayIcon.BalloonTipText =
"I noticed that you double-clicked me! What can I do for you?";
TrayIcon.BalloonTipTitle = "You called Master?";
TrayIcon.Text = "My fabulous tray icon demo application";
TrayIcon.Icon = Properties.Resources.TrayIcon;
TrayIcon.DoubleClick += TrayIcon_DoubleClick;
TrayIconContextMenu = new ContextMenuStrip();
CloseMenuItem = new ToolStripMenuItem();
TrayIconContextMenu.SuspendLayout();
this.TrayIconContextMenu.Items.AddRange(new ToolStripItem[] {
this.CloseMenuItem});
this.TrayIconContextMenu.Name = "TrayIconContextMenu";
this.TrayIconContextMenu.Size = new Size(153, 70);
this.CloseMenuItem.Name = "CloseMenuItem";
this.CloseMenuItem.Size = new Size(152, 22);
this.CloseMenuItem.Text = "Close the tray icon program";
this.CloseMenuItem.Click += new EventHandler(this.CloseMenuItem_Click);
TrayIconContextMenu.ResumeLayout(false);
TrayIcon.ContextMenuStrip = TrayIconContextMenu;
}
private void OnApplicationExit(object sender, EventArgs e)
{
TrayIcon.Visible = false;
}
private void TrayIcon_DoubleClick(object sender, EventArgs e)
{
TrayIcon.ShowBalloonTip(10000);
}
private void CloseMenuItem_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Do you really want to close me?",
"Are you sure?", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button2) == DialogResult.Yes)
{
Application.Exit();
}
}
}
}
Normally, when you drop a NotifyIcon
component on a Form
, you don't need to worry any more about that, the designer handles all the coding and puts it in the designer file. In this case, you don't have a designer file, so you have to create the controls manually in your code. You could (if you wanted to) create a designer file yourself as a partial file and move the InitializeComponent
method to that, but for this example, that would be overkill in my opinion.
Voila! That's all there is to it. A WinForms application without any WinForms... ;-)
You can of course create forms in your projects and show them, if you e.g., need a settings form or the like. That part works just as usual.
An alternative would of course be to create the application as a Windows Service instead, but that adds a lot more complexity to the project and requires that you also do an installer project. In some cases, that is way too much work for such a small task.
History
- 29th July, 2013: Version 1.00 - Initial release