Single Instance App on Whidbey
In prior versions of .NET, there were lots of discussions about how to support single-instance WinForms applications. Here are a couple of links discussed some ways to do that: here and here and here's one in C++. For Whidbey, some of that functionality has been built into the Visual Basic libararies. But, the WindowsFormsApplicationBase and related classes are also accessible from C#.
First, to gain access to the WindowsFormsApplicationBase class in your C# project, you'll need to add the Microsoft.VisualBasic.ApplicationServices namespace to your .cs files. And, you'll need to add a reference in your project to the Micorosft.VisualBase.dll file. At this point, you have access to this class in your code.
Then, I defined a SingleInstanceApplication class that derives from WindowsFormsApplicationBase. This class ensures that only one application of this type is ever run. If additional instances attempt to start, the Run method does not launch another main form, instead it fires a StartupNextInstance event back to your first process and exits without launching a new instance of your app.
/// <summary>
/// This class ensures that only a single instance of this application is
/// ever created.
/// </summary>
public class SingleInstanceApplication : WindowsFormsApplicationBase
{
/// <summary>
/// Constructor that intializes the authentication mode for this app.
/// </summary>
/// <param name="mode">Mode in which to run app.</param>
public SingleInstanceApplication(AuthenticationMode mode)
: base(mode)
{
InitializeAppProperties();
}
/// <summary>
/// Default constructor.
/// </summary>
public SingleInstanceApplication()
{
InitializeAppProperties();
}
/// <summary>
/// Initializes this application with the appropriate settings.
/// </summary>
protected virtual void InitializeAppProperties()
{
this.IsSingleInstance = true;
this.EnableVisualStyles = true;
}
/// <summary>
/// Runs the specified mainForm in this application context.
/// </summary>
/// <param name="mainForm">Form that is run.</param>
public virtual void Run(Form mainForm)
{
// set up the main form.
this.MainForm = mainForm;
// then, run the the main form.
this.Run(this.CommandLineArgs);
}
/// <summary>
/// Runs this.MainForm in this application context. Converts the command
/// line arguments correctly for the base this.Run method.
/// </summary>
/// <param name="commandLineArgs">Command line collection.</param>
private void Run(ReadOnlyCollection<string> commandLineArgs)
{
// convert the Collection<string> to string[], so that it can be used
// in the Run method.
ArrayList list = new ArrayList(commandLineArgs);
string[] commandLine = (string[])list.ToArray(typeof(string));
this.Run(commandLine);
}
}
Next, we need to replace the default code that's in the Main() function for your program. Typically, this code looks like:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
The updated code uses the SingleInstanceApplication class instead. It adds a listener for the StartupNextInstance event and calls the Run method on the class passing in an instance of the main form.
static class Program
{
// private members
private static Form mainForm;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// ensure only a single instance of this app runs.
SingleInstanceApplication app = new SingleInstanceApplication();
app.StartupNextInstance += new StartupNextInstanceEventHandler(OnAppStartupNextInstance);
mainForm = new d20RpgSuiteMainFrame();
app.Run(mainForm);
}
/// <summary>
/// Event handler for processing when the another application instance tries
/// to startup. Bring the previous instance of the app to the front and
/// process any command-line that's needed.
/// </summary>
/// <param name="sender">Object sending this message.</param>
/// <param name="e">Event argument for this message.</param>
static void OnAppStartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
// if the window is currently minimized, then restore it.
if (mainForm.WindowState == FormWindowState.Minimized)
{
mainForm.WindowState = FormWindowState.Normal;
}
// activate the current instance of the app, so that it's shown.
mainForm.Activate();
// todo: implement command-line processing as needed...
}
}
As you can see from the code above, the OnAppStartupNextInstance event handler activates the first main form and ensures that it is restored from a minimized state. At this point, the first instance of the application will come up whenever a user tries to create a new instance. Also, you will probably need to respond to the command line of the new instance that's trying to be created. Those command line arguments are in the StartupNextInstanceEventArgs that is passed into the event handler. Many times new instance launching happens from the Windows Explorer when the user opens a file with an extension that is registered for your app. By responding to the command line, you can load the specified file in that single instance rather than always launching new instances.
This new class (WindowsFormsApplicationBase) really helps abstract away a lot of code that needed to be written in previous versions that dealt with named pipes between processes, memory mapped files, windows messages and more just for this single instance functionality. With Whidbey, using the SingleInstanceApplication class and adding an event handler in your Program class, it's pretty easy to do.
Comments
- Anonymous
March 09, 2005
So why isn't this in the Base Class Library instead of being VB specific? I have no real problem with referencing the VB application services from C# but it sure seems like Microsoft is trying to play the game with two competing teams again. - Anonymous
March 09, 2005
Single Instance App on Whidbey - Anonymous
March 10, 2005
This is part of the My library that Visual Basic created for simplifying some of the concepts in the .NET Framework. And, make it easier to develop applications.
It just so happens that this class is very useful for this type of functionality (whether on VB or C#). I'm going to have to spend some time investigating some of the other stuff that's in that library, because there could be other similarly simplified concepts that would really benefit C# developers as well.