This is the code I implemented so far to create a single instance WPF application:
#region Using Directives
using System;
using System.Globalization;
using S
Just throwing my hat into the ring here. What I do is I create an ApplicationBase subclass of the regular Application class which I keep in a common library I use in all my WPF applications. Then I change the base class (from within the XAML and its code-behind) to use my base class. Finally, I use an EntryPoint.Main as the startup object for my app, which I then check the single instance status, and simply return if I'm not the first.
Note: I also show how to support a flag that lets you override that if you want to launch another instance. However, be careful with such an option. Only use it where it makes actual sense.
Here's the code:
public abstract class ApplicationBase : Application {
public static string? SingleInstanceId { get; private set; }
public static bool InitializeAsFirstInstance(string singleInstanceId){
if(SingleInstanceId != null)
throw new AlreadyInitializedException(singleInstanceId);
SingleInstanceId = singleInstanceId;
var waitHandleName = $"SingleInstanceWaitHandle:{singleInstanceId}";
if(EventWaitHandle.TryOpenExisting(waitHandleName, out var waitHandle)){
// An existing WaitHandle was successfuly opened which means we aren't the first so signal the other
waitHandle.Set();
// Then indicate we aren't the first instance by returning false
return false;
}
// Welp, there was no existing WaitHandle with this name, so we're the first!
// Now we have to set up the EventWaitHandle in a task to listen for other attempts to launch
void taskBody(){
var singleInstanceWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, waitHandleName);
while (singleInstanceWaitHandle.WaitOne()) {
if(Current is ApplicationBase applicationBase)
Current.Dispatcher.BeginInvoke(applicationBase.OtherInstanceLaunched);
}
}
new Task(taskBody, TaskCreationOptions.LongRunning).Start();
return true;
}
public static bool IsSingleInstance
=> SingleInstanceId != null;
protected virtual void OtherInstanceLaunched()
=> Current.MainWindow?.BringToFront();
}
By marking OtherInstanceLaunched as virtual, I can customize that on a per-application basis by simply overriding it, or just let the default implementation do its thing, which here, is an extension method on Window that I added. (Essentially it makes sure it's visible, restored, then focuses it.)
public static class EntryPoint {
public static class CommandLineArgs{
public const string AllowMulti = "/AllowMulti";
public const string NoSplash = "/NoSplash";
}
[STAThread]
public static int Main(string[] args) {
var showSplashScreen = true;
var allowMulti = false;
foreach (var arg in args) {
if (arg.Equals(CommandLineArgs.AllowMulti, StringComparison.CurrentCultureIgnoreCase))
allowMulti = true;
if (arg.Equals(CommandLineArgs.NoSplash, StringComparison.CurrentCultureIgnoreCase))
showSplashScreen = false;
}
// Try and initialize myself as the first instance. If I'm not and 'allowMulti' is false, exit with a return code of 1
if (!ApplicationBase.InitializeAsFirstInstance(ApplicationInfo.ProductName) && !allowMulti)
return 1;
if (showSplashScreen) {
var splashScreen = new SplashScreen("resources/images/splashscreen.png");
splashScreen.Show(true, false);
}
_ = new App();
return 0;
}
}
The advantage of this approach is it hands over execution even before the application itself is instantiated as well as before the splash screen is shown. In other words, it bails out at the earliest possible place.
Note: If you don't even want multi-support, then you can remove that argument check and test. This was just added for illustrative purposes