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.
No comments:
Post a Comment