Today we are going to be taking a look at how to build a single instance
application in WPF. Not a single instance in
this
sense, but in the sense that you can only run one instance of the
application at a time. Generally, you can run as many instances of an
app at once (at least until you run out of resources). For instance,
Notepad. You can run Notepad a dozen times, and you will get a dozen
separate Notepad windows, and a dozen separate lines in "Task Manager"
that read "notepad.exe". Killing one of those lines just kills one of
those Notepad windows, and the rest live on happily.
On the other hand, you have an application like Firefox. At any given
time, there should only be one line in Task Manager that reads
"firefox.exe". This is because every time you hit the Firefox shortcut,
or double click the executable, instead of running a new instance of the
app, the running instance gets a message (which is how Firefox knows to
open a new browser window).
So why would you as a developer write an app that behaved in this way?
The most common reason has to do with resources - your application needs
an exclusive lock on some resource. That resource could be anything from
a hardware device to a file on disk. But if your app needs an exclusive
lock, you better not let other instances run, because those other
instances will fail.
Ok, so how do we do this in .NET (and, more specifically, WPF)? It
actually isn't that bad (.NET fortunately has a useful built-in class
that we get to use), but it does take some drastic changes to the
default structure of a WPF application.
To get started, go and create a new WPF Visual Studio project. By
default, it comes up with two main items in the solution -
"Window1.xaml" (which I renamed to ExampleWindow.xaml") and "App.xaml".
Both of these also have their respective code behind files. So first
off, do something you have probably never done before - delete
"App.xaml" and "App.xaml.cs". We won't be needing them, because we will
be doing our own Application creation.
Now create a new class (I called it "ExampleApplcation"). This will be
our application. The two main pieces of logic that this class needs to
have are for showing the main window and for processing command line
arguments. The first piece will only happen once - the initial
application start up. The second, however, will happen every time a user
tries to run the app (and we will see how that works in a moment). Take
a look at the code:
using System.Windows;
namespace SingleInstanceExample
{
public class ExampleApplication : Application
{
public ExampleWindow MyWindow { get; private set; }
public ExampleApplication()
: base()
{ }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MyWindow = new ExampleWindow();
ProcessArgs(e.Args, true);
MyWindow.Show();
}
public void ProcessArgs(string[] args, bool firstInstance)
{
//Process Command Line Arguments Here
}
}
}
OnStartup
will only get called once - the very initial application
startup. So it is here that we make a new window and show it. We also
process the command line arguments, and add a flag saying that these are
the arguments to the first instance. We will be calling ProcessArgs
from somewhere else when the user tries to start other instances of the
app.
Ok, so that code would get a window off the ground - but what calls that
code? Well, for that, we need another class (and this class will
actually hold the entry point for the application). The important thing
for this class is that it has to derive from
WindowsFormsApplicationBase
. To get that class, you actually need to
add a special reference to your Visual Studio project -
"Microsoft.VisualBasic.dll". Don't ask me why this class is stuck in
that dll - that is just where it happens to be.using System;
using System.Linq;
using Microsoft.VisualBasic.ApplicationServices;
namespace SingleInstanceExample
{
public sealed class SingleInstanceManager : WindowsFormsApplicationBase
{
[STAThread]
public static void Main(string[] args)
{ (new SingleInstanceManager()).Run(args); }
public SingleInstanceManager()
{ IsSingleInstance = true; }
public ExampleApplication App { get; private set; }
protected override bool OnStartup(StartupEventArgs e)
{
App = new ExampleApplication();
App.Run();
return false;
}
protected override void OnStartupNextInstance(
StartupNextInstanceEventArgs eventArgs)
{
base.OnStartupNextInstance(eventArgs);
App.MyWindow.Activate();
App.ProcessArgs(eventArgs.CommandLine.ToArray(), false);
}
}
}
It is in this class that all the magic happens. Every time the
application is run, it enters the
Main
method, creates a new instance
of this class, and calls Run
. If it is the first instance, this will
cause OnStartup
to get called, and everything goes from there. If it
is a not the first, OnStartupNextInstance
gets called on the already
running instance, and the instance that was just started shuts down.
It really is as simple as that. The command line arguments for
subsequent instances are even right there in the handy
StartupNextInstanceEventArgs
.
By default, this will only work for multiple instances of the exact same
build of an application. If you need this to work across multiple
builds, there is one more step you have to take. In the
AssemblyInfo.cs
file of your project (generally under the "Properties"
folder") you have to add a GUID for your assembly. This GUID is what
will be checked against when Windows sees if it is allowed to start
another instance of the app. When there is no GUID explicitly set,
Visual Studio generates a new one every time you build (which is why
without this change, the single instance manager will only work for
other instances of the exact same build). You will want your
AssemblyInfo.cs
to look something like this (of course, you want to
use your own GUID):using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SingleInstanceExample")]
[assembly: AssemblyProduct("SingleInstanceExample")]
[assembly: GuidAttribute("1A6236B4-8CD1-4c76-86FD-F5352330D190")]
That is it for writing a single instance application in .NET and WPF.
The code for the example (and the associated Visual Studio solution) can
be found in the zip file below.
Source Files:
Comments
Post a Comment