Introduction
I’m currently working on a small utility called Hotwire, which should be released in the not too distant future. Part of the application consists of a “launcher” application that sits in the notification area (the bit by the clock :-)) and fires up various other parts of the app when the user needs them.
Originally I put it together as a WinForms app, which seemed logical as the NotifyIcon class lives in System.Windows.Forms anyway, but once I’d started putting the configuration dialog together, I realised how much I missed the Binding magic of WPF. Although shifting the app across to WPF was a relatively simple task, there were a couple of “gotchas” along the way.
The WinForms Way – Without a Window!
A lot of the samples you’ll see for using NotifyIcon in WinForms will create a Window, put the NotifyIcon class on the form, and hide the form on startup so all you’re left with is the icon by the clock. While this approach works, as this application doesn’t have a “main window” as such, it seemed a bit naff to have a hidden Window lurking in the background that I’d never use.
To do things slightly more “elegantly” we can “bootstrap” our application manually with our own Main() method, and put together a class that creates all of the controls required for our NotifyIcon and it’s various menus. Our Main() method is very simple; we just set a few properties, instantiate our TaskTray class and call Application.Run() to give us our Windows message pump (if you don’t do the last part, your application will quit immediately!)
static class Program
{
private static TaskTray t;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
t = new TaskTray();
Application.Run();
}
}
Now our TaskTray.cs file will intentionally look spookily similar to the code behind for a normal WinForms window. All we need to do in our constructor is create a Container, a NotifyIcon, a ContextMenuStrip to attach to it, and an icon for us to display. To maintain parity with a normal codebehind class, we will wrap all of this up into a method called InitialiseComponent:
public void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.SysTrayIcon = new System.Windows.Forms.NotifyIcon(this.components);
this.MainMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.configToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.MainMenu.SuspendLayout();
this.SysTrayIcon.ContextMenuStrip = this.MainMenu;
this.SysTrayIcon.Icon = new System.Drawing.Icon(this.GetType(), "myapp.ico");
this.SysTrayIcon.Text = "MyApp";
this.SysTrayIcon.Visible = true;
this.MainMenu.Name = "MainMenu";
this.MainMenu.Size = new System.Drawing.Size(114, 82);
this.MainMenu.Items.Clear();
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(113, 22);
this.aboutToolStripMenuItem.Text = "&About";
this.MainMenu.Items.Add(aboutToolStripMenuItem);
this.configToolStripMenuItem.Size = new System.Drawing.Size(113, 22);
this.configToolStripMenuItem.Text = "&Configuration";
this.MainMenu.Items.Add(configToolStripMenuItem);
this.MainMenu.Items.Add(new System.Windows.Forms.ToolStripSeparator());
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
this.exitToolStripMenuItem.Size = new System.Drawing.Size(113, 22);
this.exitToolStripMenuItem.Text = "E&xit";
this.MainMenu.Items.Add(this.exitToolStripMenuItem);
this.MainMenu.ResumeLayout(false);
}
We can then hook some events up in the normal manner and we’re all done, and no Window classes in sight!
The WPF Way – Still Without a Window!
To port this to WPF we first need to add references to System.Windows.Forms and System.Drawing (for our Icon), once that’s done we need to roll our own “bootstrapper” so we don’t create a Window on startup.
By default, when you create a new WPF application, you will get Window1.xaml and App.xaml. The latter contains a StartupUri property that the generated code behind uses to bootstrap the application and display the main window. If we nuke App.Xaml from the project (we’ll get a duplication Main() error if we don’t), we can add our own bootstrapper which looks very similar to the WinForms one above:
class BootStrap
{
static TaskTray _taskTray;
[STAThread]
static void Main()
{
Application app = new Application();
_taskTray = new TaskTray();
app.Run();
}
}
If you add our TaskTray class into this new project (making sure to add a Using statement for System.Windows), and fire it up we will get our NotifyIcon as before and it looks like we’re all done! Or are we… 🙂
What? Where Did My App Go?
If you decide you want to show a Window from one of your menu items, such as a configuration window, you’ll immediately notice that as soon as the user or application closes that window then your application will immediately quit!
The reason for this is down to the default behaviour of the WPF Application object. The Application object keeps track of all of the Windows opened (as well as a “special” MainWindow which is automatically set to the first Window that is created inside the Application) and when the last window closes, it terminates the application. This default behaviour makes sense for a “normal” application, because most of the time we WANT it to shutdown properly when all the windows are closed; but in our scenario it’s a huge pain in the ****!
The Fix
Our immediate reaction might be to create a “dummy” Window in our bootstrapper and keep it hidden somehow; but this is the same “hack” as the one I disliked from the WinForms version, so we really want to avoid doing that.
Luckily, we can override this behaviour using the ShutdownMode property on our Application object. We can tell it to either terminate only when the MainWindow window closes, or only shutdown when we explicitly tell it to (which is what we want for our application). This changes our bootstrap class to the following:
class BootStrap
{
static TaskTray _taskTray;
[STAThread]
static void Main()
{
Application app = new Application();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
_taskTray = new TaskTray();
app.Run();
}
}
We then need to make sure we call Application.Shutdown when we want to quit, which we can do from our Exit menu option:
this.exitToolStripMenuItem.Click += (s, e) =>
{
Application.Current.Shutdown();
};
You can grab the code below:
WpfNotifyIconTest.zip