The intention is preventing user from starting multiple instances of the same application. Repeated run should activate (restore) the application's main window.
Having this task already resolved for Visual FoxPro through using semaphores, I chose same approach for WPF. Using direct calls to Semaphore API functions in .NET makes rather little sense, since the
Semaphore Class provides all required functionality.
First step is removing
StartupUri in
App.xaml and overriding
Application.OnStartup method. This allows instantiating application's main window only after checking that no other instances are running.
namespace WpfApplicationRunOnce
{
using System.Windows;
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
if (!AppLauncher.RunApp())
{
this.Shutdown(0);
}
}
}
}
Static
AppLauncher class either creates instance of the main window or activates the one that already exists.
namespace WpfApplicationRunOnce
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
public static class AppLauncher
{
private const string ApplicationId = "MyAppUniqueId";
private static MainWindow mainWindow;
internal enum RegisterResult
{
Success,
AlreadyRunning,
Failed
}
private enum ShowWindowMode
{
Maximize = 3,
Restore = 9
}
private enum WindowStyles
{
Maximize = 0x01000000
}
public static bool RunApp()
{
var result = RegisterApp(ApplicationId);
switch (result)
{
case RegisterResult.Success:
mainWindow = new MainWindow();
mainWindow.Show();
return true;
case RegisterResult.AlreadyRunning:
ActivateAppMainWindow();
break;
case RegisterResult.Failed:
break;
}
return false;
}
private static RegisterResult RegisterApp(string appId)
{
try
{
Semaphore.OpenExisting(appId);
return RegisterResult.AlreadyRunning;
}
catch (WaitHandleCannotBeOpenedException)
{
try
{
var semaphore = new Semaphore(1, 1, appId);
return RegisterResult.Success;
}
catch
{
return RegisterResult.Failed;
}
}
}
private static void ActivateAppMainWindow()
{
var current = Process.GetCurrentProcess();
var running = Process.GetProcesses()
.FirstOrDefault(p => p.Id != current.Id
&& p.ProcessName.Equals(
current.ProcessName,
StringComparison.CurrentCultureIgnoreCase));
if (running == null)
{
return;
}
var handle = running.MainWindowHandle;
var windowStyle = GetWindowLong(handle, -16);
ShowWindow(
handle,
(windowStyle & (long)WindowStyles.Maximize) != 0
? ShowWindowMode.Maximize : ShowWindowMode.Restore);
SetForegroundWindow(handle);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(
IntPtr hWnd,
int nIndex);
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(
IntPtr window);
[DllImport("user32.dll")]
private static extern int ShowWindow(
IntPtr window,
ShowWindowMode showMode);
}
}
In the code above, replace
ApplicationId value with something less generic and more descriptive for your application. This value must be sufficiently unique to be used as the name for a semaphore.
The
RegisterApp method attempts to open existing named semaphore. The success indicates that an instance of the application is already running. Otherwise (
WaitHandleCannotBeOpenedException), the named semaphore is created.
The
ActivateAppMainWindow method uses the name of the current process to find its already running instance. The main window of which gets activated -- either maximized or restored to its original size and position.
There are at least three possible scenarios this approach covers:
- single executable file
- two copies of the executable file having same names
- two copies of the executable file having different names
The executable files in cases 2) and 3) do not need to be byte to byte identical. What matters is the
ApplicationId value shared by the two.
The 3) case behaves differently. While multiple instances are still prevented, the running instance's main window does not get activated since the executable files in this case have different names.
* * *
While adding this functionality to WPF PRISM application, I had to make small changes to the implementation. The testing of the named semaphore existence was moved from the
RegisterApp method to a separate method called
TestInstance.
internal static RegisterResult TestInstance(string appId)
{
try
{
Semaphore.OpenExisting(appId, SemaphoreRights.Synchronize);
return RegisterResult.AlreadyRunning;
}
catch (WaitHandleCannotBeOpenedException ex)
{
return RegisterResult.NotDetected;
}
}
internal static RegisterResult RegisterApp(string appId)
{
try
{
var semaphore = new Semaphore(1, 1, appId);
return RegisterResult.Success;
}
catch
{
return RegisterResult.Failed;
}
}
The OnStartup method in App.xaml.cs was changed as follows.
protected override void OnStartup(StartupEventArgs e)
{
var applicationId = Assembly.GetExecutingAssembly().FullName;
if (AppLauncher.TestInstance(applicationId) == AppLauncher.RegisterResult.AlreadyRunning)
{
AppLauncher.ActivateAppMainWindow();
this.Shutdown(0);
}
base.OnStartup(e);
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
AppLauncher.RegisterApp(applicationId);
}
The named semaphore must be created only after PRISM bootstrapper runs (starts the main process). Otherwise the semaphore has a very short life.