Using C# and WPF under .NET (rather than Windows Forms or console), what is the correct way to create an application that can only be run as a single instance?
I know it has something to do with some mythical thing called a mutex, rarely can I find someone that bothers to stop and explain what one of these are.
The code needs to also inform the already-running instance that the user tried to start a second one, and maybe also pass any command-line arguments if any existed.
Here is a very good article regarding the Mutex solution. The approach described by the article is advantageous for two reasons.
First, it does not require a dependency on the Microsoft.VisualBasic assembly. If my project already had a dependency on that assembly, I would probably advocate using the approach shown in another answer. But as it is, I do not use the Microsoft.VisualBasic assembly, and I'd rather not add an unnecessary dependency to my project.
Second, the article shows how to bring the existing instance of the application to the foreground when the user tries to start another instance. That's a very nice touch that the other Mutex solutions described here do not address.
UPDATE
As of 8/1/2014, the article I linked to above is still active, but the blog hasn't been updated in a while. That makes me worry that eventually it might disappear, and with it, the advocated solution. I'm reproducing the content of the article here for posterity. The words belong solely to the blog owner at Sanity Free Coding.
Today I wanted to refactor some code that prohibited my application
from running multiple instances of itself.
Previously I had use System.Diagnostics.Process to search for an
instance of my myapp.exe in the process list. While this works, it
brings on a lot of overhead, and I wanted something cleaner.
Knowing that I could use a mutex for this (but never having done it
before) I set out to cut down my code and simplify my life.
In the class of my application main I created a static named Mutex:
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
...
}
Having a named mutex allows us to stack synchronization across
multiple threads and processes which is just the magic I'm looking
for.
Mutex.WaitOne has an overload that specifies an amount of time for us
to wait. Since we're not actually wanting to synchronizing our code
(more just check if it is currently in use) we use the overload with
two parameters: Mutex.WaitOne(Timespan timeout, bool exitContext).
Wait one returns true if it is able to enter, and false if it wasn't.
In this case, we don't want to wait at all; If our mutex is being
used, skip it, and move on, so we pass in TimeSpan.Zero (wait 0
milliseconds), and set the exitContext to true so we can exit the
synchronization context before we try to aquire a lock on it. Using
this, we wrap our Application.Run code inside something like this:
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
static void Main() {
if(mutex.WaitOne(TimeSpan.Zero, true)) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
} else {
MessageBox.Show("only one instance at a time");
}
}
}
So, if our app is running, WaitOne will return false, and we'll get a
message box.
Instead of showing a message box, I opted to utilize a little Win32 to
notify my running instance that someone forgot that it was already
running (by bringing itself to the top of all the other windows). To
achieve this I used PostMessage to broadcast a custom message to every
window (the custom message was registered with RegisterWindowMessage
by my running application, which means only my application knows what
it is) then my second instance exits. The running application instance
would receive that notification and process it. In order to do that, I
overrode WndProc in my main form and listened for my custom
notification. When I received that notification I set the form's
TopMost property to true to bring it up on top.
Here is what I ended up with:
Program.cs
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
static void Main() {
if(mutex.WaitOne(TimeSpan.Zero, true)) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
} else {
// send our Win32 message to make the currently running instance
// jump on top of all the other windows
NativeMethods.PostMessage(
(IntPtr)NativeMethods.HWND_BROADCAST,
NativeMethods.WM_SHOWME,
IntPtr.Zero,
IntPtr.Zero);
}
}
}
NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
public const int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
}
Form1.cs (front side partial)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if(m.Msg == NativeMethods.WM_SHOWME) {
ShowMe();
}
base.WndProc(ref m);
}
private void ShowMe()
{
if(WindowState == FormWindowState.Minimized) {
WindowState = FormWindowState.Normal;
}
// get our current "TopMost" value (ours will always be false though)
bool top = TopMost;
// make our form jump to the top of everything
TopMost = true;
// set it back to whatever it was
TopMost = top;
}
}
You could use the Mutex class, but you will soon find out that you will need to implement the code to pass the arguments and such yourself. Well, I learned a trick when programming in WinForms when I read Chris Sell's book. This trick uses logic that is already available to us in the framework. I don't know about you, but when I learn about stuff I can reuse in the framework, that is usually the route I take instead of reinventing the wheel. Unless of course it doesn't do everything I want.
When I got into WPF, I came up with a way to use that same code, but in a WPF application. This solution should meet your needs based off your question.
First, we need to create our application class. In this class we are going override the OnStartup event and create a method called Activate, which will be used later.
public class SingleInstanceApplication : System.Windows.Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
// Call the OnStartup event on our base class
base.OnStartup(e);
// Create our MainWindow and show it
MainWindow window = new MainWindow();
window.Show();
}
public void Activate()
{
// Reactivate the main window
MainWindow.Activate();
}
}
Second, we will need to create a class that can manage our instances. Before we go through that, we are actually going to reuse some code that is in the Microsoft.VisualBasic assembly. Since, I am using C# in this example, I had to make a reference to the assembly. If you are using VB.NET, you don't have to do anything. The class we are going to use is WindowsFormsApplicationBase and inherit our instance manager off of it and then leverage properties and events to handle the single instancing.
public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
private SingleInstanceApplication _application;
private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;
public SingleInstanceManager()
{
IsSingleInstance = true;
}
protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
{
// First time _application is launched
_commandLine = eventArgs.CommandLine;
_application = new SingleInstanceApplication();
_application.Run();
return false;
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
// Subsequent launches
base.OnStartupNextInstance(eventArgs);
_commandLine = eventArgs.CommandLine;
_application.Activate();
}
}
Basically, we are using the VB bits to detect single instance's and process accordingly. OnStartup will be fired when the first instance loads. OnStartupNextInstance is fired when the application is re-run again. As you can see, I can get to what was passed on the command line through the event arguments. I set the value to an instance field. You could parse the command line here, or you could pass it to your application through the constructor and the call to the Activate method.
Third, it's time to create our EntryPoint. Instead of newing up the application like you would normally do, we are going to take advantage of our SingleInstanceManager.
public class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
SingleInstanceManager manager = new SingleInstanceManager();
manager.Run(args);
}
}
Well, I hope you are able to follow everything and be able use this implementation and make it your own.
From here.
A common use for a cross-process Mutex is to ensure that only instance of a program can run at a time. Here's how it's done:
class OneAtATimePlease {
// Use a name unique to the application (eg include your company URL)
static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");
static void Main()
{
// Wait 5 seconds if contended – in case another instance
// of the program is in the process of shutting down.
if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
{
Console.WriteLine("Another instance of the app is running. Bye!");
return;
}
try
{
Console.WriteLine("Running - press Enter to exit");
Console.ReadLine();
}
finally
{
mutex.ReleaseMutex();
}
}
}
A good feature of Mutex is that if the application terminates without ReleaseMutex first being called, the CLR will release the Mutex automatically.
MSDN actually has a sample application for both C# and VB to do exactly this: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx
The most common and reliable technique
for developing single-instance
detection is to use the Microsoft .NET
Framework remoting infrastructure
(System.Remoting). The Microsoft .NET
Framework (version 2.0) includes a
type, WindowsFormsApplicationBase,
which encapsulates the required
remoting functionality. To incorporate
this type into a WPF application, a
type needs to derive from it, and be
used as a shim between the application
static entry point method, Main, and
the WPF application's Application
type. The shim detects when an
application is first launched, and
when subsequent launches are
attempted, and yields control the WPF
Application type to determine how to
process the launches.
For C# people just take a deep breath and forget about the whole 'I don't wanna include VisualBasic DLL'. Because of this and what Scott Hanselman says and the fact that this pretty much is the cleanest solution to the problem and is designed by people who know a lot more about the framework than you do.
From a usability standpoint the fact is if your user is loading an application and it is already open and you're giving them an error message like 'Another instance of the app is running. Bye' then they're not gonna be a very happy user. You simply MUST (in a GUI application) switch to that application and pass in the arguments provided - or if command line parameters have no meaning then you must pop up the application which may have been minimized.
The framework already has support for this - its just that some idiot named the DLL Microsoft.VisualBasic and it didn't get put into Microsoft.ApplicationUtils or something like that. Get over it - or open up Reflector.
Tip: If you use this approach exactly as is, and you already have an App.xaml with resources etc. you'll want to take a look at this too.
This code should go to the main method. Look at here for more information about the main method in WPF.
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
private const int SW_SHOWMAXIMIZED = 3;
static void Main()
{
Process currentProcess = Process.GetCurrentProcess();
var runningProcess = (from process in Process.GetProcesses()
where
process.Id != currentProcess.Id &&
process.ProcessName.Equals(
currentProcess.ProcessName,
StringComparison.Ordinal)
select process).FirstOrDefault();
if (runningProcess != null)
{
ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
return;
}
}
Method 2
static void Main()
{
string procName = Process.GetCurrentProcess().ProcessName;
// get the list of all processes by that name
Process[] processes=Process.GetProcessesByName(procName);
if (processes.Length > 1)
{
MessageBox.Show(procName + " already running");
return;
}
else
{
// Application.Run(...);
}
}
Note : Above methods assumes your process/application has a unique name. Because it uses process name to find if any existing processors. So, if your application has a very common name (ie: Notepad), above approach won't work.
Well, I have a disposable Class for this that works easily for most use cases:
Use it like this:
static void Main()
{
using (SingleInstanceMutex sim = new SingleInstanceMutex())
{
if (sim.IsOtherInstanceRunning)
{
Application.Exit();
}
// Initialize program here.
}
}
Here it is:
/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
#region Fields
/// <summary>
/// Indicator whether another instance of this application is running or not.
/// </summary>
private bool isNoOtherInstanceRunning;
/// <summary>
/// The <see cref="Mutex"/> used to ask for other instances of this application.
/// </summary>
private Mutex singleInstanceMutex = null;
/// <summary>
/// An indicator whether this object is beeing actively disposed or not.
/// </summary>
private bool disposed;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
/// </summary>
public SingleInstanceMutex()
{
this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
}
#endregion
#region Properties
/// <summary>
/// Gets an indicator whether another instance of the application is running or not.
/// </summary>
public bool IsOtherInstanceRunning
{
get
{
return !this.isNoOtherInstanceRunning;
}
}
#endregion
#region Methods
/// <summary>
/// Closes the <see cref="SingleInstanceMutex"/>.
/// </summary>
public void Close()
{
this.ThrowIfDisposed();
this.singleInstanceMutex.Close();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
/* Release unmanaged ressources */
if (disposing)
{
/* Release managed ressources */
this.Close();
}
this.disposed = true;
}
}
/// <summary>
/// Throws an exception if something is tried to be done with an already disposed object.
/// </summary>
/// <remarks>
/// All public methods of the class must first call this.
/// </remarks>
public void ThrowIfDisposed()
{
if (this.disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}
#endregion
}
A new one that uses Mutex and IPC stuff, and also passes any command line arguments to the running instance, is WPF Single Instance Application.
The code C# .NET Single Instance Application that is the reference for the marked answer is a great start.
However, I found it doesn't handle very well the cases when the instance that already exist has a modal dialog open, whether that dialog is a managed one (like another Form such as an about box), or an unmanaged one (like the OpenFileDialog even when using the standard .NET class). With the original code, the main form is activated, but the modal one stays unactive, which looks strange, plus the user must click on it to keep using the app.
So, I have create a SingleInstance utility class to handle all this quite automatically for Winforms and WPF applications.
Winforms:
1) modify the Program class like this:
static class Program
{
public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);
[STAThread]
static void Main(string[] args)
{
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
Singleton.RunFirstInstance(() =>
{
SingleInstanceMain(args);
});
}
public static void SingleInstanceMain(string[] args)
{
// standard code that was in Main now goes here
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
2) modify the main window class like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
// if needed, the singleton will restore this window
Program.Singleton.OnWndProc(this, m, true);
// TODO: handle specific messages here if needed
base.WndProc(ref m);
}
}
WPF:
1) modify the App page like this (and make sure you set its build action to page to be able to redefine the Main method):
public partial class App : Application
{
public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);
[STAThread]
public static void Main(string[] args)
{
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
Singleton.RunFirstInstance(() =>
{
SingleInstanceMain(args);
});
}
public static void SingleInstanceMain(string[] args)
{
// standard code that was in Main now goes here
App app = new App();
app.InitializeComponent();
app.Run();
}
}
2) modify the main window class like this:
public partial class MainWindow : Window
{
private HwndSource _source;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_source = (HwndSource)PresentationSource.FromVisual(this);
_source.AddHook(HwndSourceHook);
}
protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// if needed, the singleton will restore this window
App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);
// TODO: handle other specific message
return IntPtr.Zero;
}
And here is the utility class:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace SingleInstanceUtilities
{
public sealed class SingleInstance
{
private const int HWND_BROADCAST = 0xFFFF;
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int RegisterWindowMessage(string message);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
public SingleInstance(string uniqueName)
{
if (uniqueName == null)
throw new ArgumentNullException("uniqueName");
Mutex = new Mutex(true, uniqueName);
Message = RegisterWindowMessage("WM_" + uniqueName);
}
public Mutex Mutex { get; private set; }
public int Message { get; private set; }
public void RunFirstInstance(Action action)
{
RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
}
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
{
if (action == null)
throw new ArgumentNullException("action");
if (WaitForMutext(wParam, lParam))
{
try
{
action();
}
finally
{
ReleaseMutex();
}
}
}
public static void ActivateWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
return;
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
}
public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
{
if (m == Message)
{
if (restorePlacement)
{
WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
if (placement.IsValid && placement.IsMinimized)
{
const int SW_SHOWNORMAL = 1;
placement.ShowCmd = SW_SHOWNORMAL;
placement.SetPlacement(hwnd);
}
}
if (activate)
{
SetForegroundWindow(hwnd);
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
}
}
}
#if WINFORMS // define this for Winforms apps
public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
{
if (form == null)
throw new ArgumentNullException("form");
if (m == Message)
{
if (activate)
{
if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
{
form.WindowState = System.Windows.Forms.FormWindowState.Normal;
}
form.Activate();
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
}
}
}
public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
{
if (form == null)
throw new ArgumentNullException("form");
OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
}
#endif
public void ReleaseMutex()
{
Mutex.ReleaseMutex();
}
public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
{
bool b = PrivateWaitForMutext(force);
if (!b)
{
PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
}
return b;
}
public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
{
return WaitForMutext(false, wParam, lParam);
}
private bool PrivateWaitForMutext(bool force)
{
if (force)
return true;
try
{
return Mutex.WaitOne(TimeSpan.Zero, true);
}
catch (AbandonedMutexException)
{
return true;
}
}
}
// NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
[StructLayout(LayoutKind.Sequential)]
public struct WindowPlacement
{
public int Length { get; set; }
public int Flags { get; set; }
public int ShowCmd { get; set; }
public int MinPositionX { get; set; }
public int MinPositionY { get; set; }
public int MaxPositionX { get; set; }
public int MaxPositionY { get; set; }
public int NormalPositionLeft { get; set; }
public int NormalPositionTop { get; set; }
public int NormalPositionRight { get; set; }
public int NormalPositionBottom { get; set; }
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
private const int SW_SHOWMINIMIZED = 2;
public bool IsMinimized
{
get
{
return ShowCmd == SW_SHOWMINIMIZED;
}
}
public bool IsValid
{
get
{
return Length == Marshal.SizeOf(typeof(WindowPlacement));
}
}
public void SetPlacement(IntPtr windowHandle)
{
SetWindowPlacement(windowHandle, ref this);
}
public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
{
WindowPlacement placement = new WindowPlacement();
if (windowHandle == IntPtr.Zero)
return placement;
placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
if (!GetWindowPlacement(windowHandle, ref placement))
{
if (throwOnError)
throw new Win32Exception(Marshal.GetLastWin32Error());
return new WindowPlacement();
}
return placement;
}
}
public static class FormUtilities
{
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);
private class ModalWindowUtil
{
private const int GW_OWNER = 4;
private int _maxOwnershipLevel;
private IntPtr _maxOwnershipHandle;
private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
{
int level = 1;
if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
{
if (level > _maxOwnershipLevel)
{
_maxOwnershipHandle = hwnd;
_maxOwnershipLevel = level;
}
}
return true;
}
private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
{
IntPtr o = GetWindow(hwnd, GW_OWNER);
if (o == IntPtr.Zero)
return false;
if (o == owner)
return true;
level++;
return IsOwned(owner, o, ref level);
}
public static void ActivateWindow(IntPtr hwnd)
{
if (hwnd != IntPtr.Zero)
{
SetActiveWindow(hwnd);
}
}
public static IntPtr GetModalWindow(IntPtr owner)
{
ModalWindowUtil util = new ModalWindowUtil();
EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
return util._maxOwnershipHandle; // may be IntPtr.Zero
}
}
public static void ActivateWindow(IntPtr hwnd)
{
ModalWindowUtil.ActivateWindow(hwnd);
}
public static IntPtr GetModalWindow(IntPtr owner)
{
return ModalWindowUtil.GetModalWindow(owner);
}
}
}
Just some thoughts:
There are cases when requiring that only one instance of an application is not "lame" as some would have you believe. Database apps, etc. are an order of magnitude more difficult if one allows multiple instances of the app for a single user to access a database (you know, all that updating all the records that are open in multiple instances of the app on the users machine, etc.).
First, for the "name collision thing, don't use a human readable name - use a GUID instead or, even better a GUID + the human readable name. Chances of name collision just dropped off the radar and the Mutex doesn't care. As someone pointed out, a DOS attack would suck, but if the malicious person has gone to the trouble of getting the mutex name and incorporating it into their app, you are pretty much a target anyway and will have to do MUCH more to protect yourself than just fiddle a mutex name.
Also, if one uses the variant of:
new Mutex(true, "some GUID plus Name", out AIsFirstInstance), you already have your indicator as to whether or not the Mutex is the first instance.
Here is an example that allows you to have a single instance of an application. When any new instances load, they pass their arguments to the main instance that is running.
public partial class App : Application
{
private static Mutex SingleMutex;
public static uint MessageId;
private void Application_Startup(object sender, StartupEventArgs e)
{
IntPtr Result;
IntPtr SendOk;
Win32.COPYDATASTRUCT CopyData;
string[] Args;
IntPtr CopyDataMem;
bool AllowMultipleInstances = false;
Args = Environment.GetCommandLineArgs();
// TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
MessageId = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
SingleMutex = new Mutex(false, "AppName");
if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
{
new Main();
}
else if (Args.Length > 1)
{
foreach (Process Proc in Process.GetProcesses())
{
SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
2000, out Result);
if (SendOk == IntPtr.Zero)
continue;
if ((uint)Result != MessageId)
continue;
CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));
CopyData.dwData = IntPtr.Zero;
CopyData.cbData = Args[1].Length*2;
CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);
Marshal.StructureToPtr(CopyData, CopyDataMem, false);
Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
5000, out Result);
Marshal.FreeHGlobal(CopyData.lpData);
Marshal.FreeHGlobal(CopyDataMem);
}
Shutdown(0);
}
}
}
public partial class Main : Window
{
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource Source;
Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
Source.AddHook(new HwndSourceHook(Window_Proc));
}
private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
{
Win32.COPYDATASTRUCT CopyData;
string Path;
if (Msg == Win32.WM_COPYDATA)
{
CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);
if (WindowState == WindowState.Minimized)
{
// Restore window from tray
}
// Do whatever we want with information
Activate();
Focus();
}
if (Msg == App.MessageId)
{
Handled = true;
return new IntPtr(App.MessageId);
}
return IntPtr.Zero;
}
}
public class Win32
{
public const uint WM_COPYDATA = 0x004A;
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
[Flags]
public enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0000,
SMTO_BLOCK = 0x0001,
SMTO_ABORTIFHUNG = 0x0002,
SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
}
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll")]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}
The following code is my WCF named pipes solution to register a single-instance application. It's nice because it also raises an event when another instance attempts to start, and receives the command line of the other instance.
It's geared toward WPF because it uses the System.Windows.StartupEventHandler class, but this could be easily modified.
This code requires a reference to PresentationFramework, and System.ServiceModel.
Usage:
class Program
{
static void Main()
{
var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");
if (SingleInstanceManager.VerifySingleInstance(applicationId))
{
SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;
// Start the application
}
}
static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
{
// Do something in response to another instance starting up.
}
}
Source Code:
/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
/// <summary>
/// Raised when another instance attempts to start up.
/// </summary>
public static event StartupEventHandler OtherInstanceStarted;
/// <summary>
/// Checks to see if this instance is the first instance running on this machine. If it is not, this method will
/// send the main instance this instance's startup information.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if this instance is the main instance.</returns>
public static bool VerifySingleInstace(Guid guid)
{
if (!AttemptPublishService(guid))
{
NotifyMainInstance(guid);
return false;
}
return true;
}
/// <summary>
/// Attempts to publish the service.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if the service was published successfully.</returns>
private static bool AttemptPublishService(Guid guid)
{
try
{
ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
serviceHost.Open();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Notifies the main instance that this instance is attempting to start up.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
private static void NotifyMainInstance(Guid guid)
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
{
ISingleInstance singleInstance = factory.CreateChannel();
singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
}
}
/// <summary>
/// Creates an address to publish/contact the service at based on a globally unique identifier.
/// </summary>
/// <param name="guid">The identifier for the application.</param>
/// <returns>The address to publish/contact the service.</returns>
private static string CreateAddress(Guid guid)
{
return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
}
/// <summary>
/// The interface that describes the single instance service.
/// </summary>
[ServiceContract]
private interface ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
[OperationContract]
void NotifyMainInstance(string[] args);
}
/// <summary>
/// The implementation of the single instance service interface.
/// </summary>
private class SingleInstance : ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
public void NotifyMainInstance(string[] args)
{
if (OtherInstanceStarted != null)
{
Type type = typeof(StartupEventArgs);
ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(argsField != null);
argsField.SetValue(e, args);
OtherInstanceStarted(null, e);
}
}
}
}
So many answers to such a seemingly simple question. Just to shake things up a little bit here is my solution to this problem.
Creating a Mutex can be troublesome because the JIT-er only sees you using it for a small portion of your code and wants to mark it as ready for garbage collection. It pretty much wants to out-smart you thinking you are not going to be using that Mutex for that long. In reality you want to hang onto this Mutex for as long as your application is running. The best way to tell the garbage collector to leave you Mutex alone is to tell it to keep it alive though out the different generations of garage collection. Example:
var m = new Mutex(...);
...
GC.KeepAlive(m);
I lifted the idea from this page: http://www.ai.uga.edu/~mc/SingleInstance.html
It looks like there is a really good way to handle this:
WPF Single Instance Application
This provides a class you can add that manages all the mutex and messaging cruff to simplify the your implementation to the point where it's simply trivial.
Look at the folllowing code. It is a great and simple solution to prevent multiple instances of a WPF application.
private void Application_Startup(object sender, StartupEventArgs e)
{
Process thisProc = Process.GetCurrentProcess();
if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
{
MessageBox.Show("Application running");
Application.Current.Shutdown();
return;
}
var wLogin = new LoginWindow();
if (wLogin.ShowDialog() == true)
{
var wMain = new Main();
wMain.WindowState = WindowState.Maximized;
wMain.Show();
}
else
{
Application.Current.Shutdown();
}
}
Not using Mutex though, simple answer:
System.Diagnostics;
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;
if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
return;
Put it inside the Program.Main().
Example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
namespace Sample
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//simple add Diagnostics namespace, and these 3 lines below
string thisprocessname = Process.GetCurrentProcess().ProcessName;
if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
return;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Sample());
}
}
}
You can add MessageBox.Show to the if-statement and put "Application already running".
This might be helpful to someone.
Here is what I use. It combined process enumeration to perform switching and mutex to safeguard from "active clickers":
public partial class App
{
[DllImport("user32")]
private static extern int OpenIcon(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var p = Process
.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
{
OpenIcon(t.MainWindowHandle);
SetForegroundWindow(t.MainWindowHandle);
Current.Shutdown();
return;
}
// there is a chance the user tries to click on the icon repeatedly
// and the process cannot be discovered yet
bool createdNew;
var mutex = new Mutex(true, "MyAwesomeApp",
out createdNew); // must be a variable, though it is unused -
// we just need a bit of time until the process shows up
if (!createdNew)
{
Current.Shutdown();
return;
}
new Bootstrapper().Run();
}
}
I found the simpler solution, similar to Dale Ragan's, but slightly modified. It does practically everything you need and based on the standard Microsoft WindowsFormsApplicationBase class.
Firstly, you create SingleInstanceController class, which you can use in all other single-instance applications, which use Windows Forms:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;
namespace SingleInstanceController_NET
{
public class SingleInstanceController
: WindowsFormsApplicationBase
{
public delegate Form CreateMainForm();
public delegate void StartNextInstanceDelegate(Form mainWindow);
CreateMainForm formCreation;
StartNextInstanceDelegate onStartNextInstance;
public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
{
// Set whether the application is single instance
this.formCreation = formCreation;
this.onStartNextInstance = onStartNextInstance;
this.IsSingleInstance = true;
this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);
}
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
if (onStartNextInstance != null)
{
onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
// for example, by clicking on the exe file.
} // This code can determine how to re-activate the existing main window of the running application.
}
protected override void OnCreateMainForm()
{
// Instantiate your main application form
this.MainForm = formCreation();
}
public void Run()
{
string[] commandLine = new string[0];
base.Run(commandLine);
}
}
}
Then you can use it in your program as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;
namespace SingleInstance
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static Form CreateForm()
{
return new Form1(); // Form1 is used for the main window.
}
static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
// the main window is activated again.
{
mainWindow.WindowState = FormWindowState.Maximized;
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
controller.Run();
}
}
}
Both the program and the SingleInstanceController_NET solution should reference Microsoft.VisualBasic . If you just want to reactivate the running application as a normal window when the user tries to restart the running program, the second parameter in the SingleInstanceController can be null. In the given example, the window is maximized.
You should never use a named mutex to implement a single-instance application (or at least not for production code). Malicious code can easily DoS (Denial of Service) your ass...
Update 2017-01-25. After trying few things, I decided to go with VisualBasic.dll it is easier and works better (at least for me). I let my previous answer just as reference...
Just as reference, this is how I did without passing arguments (which I can't find any reason to do so... I mean a single app with arguments that as to be passed out from one instance to another one).
If file association is required, then an app should (per users standard expectation) be instanciated for each doc. If you have to pass args to existing app, I think I would used vb dll.
Not passing args (just single instance app), I prefer not registering a new Window message and not override the message loop as defined in Matt Davis Solution. Although it's not a big deal to add a VisualBasic dll, but I prefer not add a new reference just to do single instance app. Also, I do prefer instanciate a new class with Main instead of calling Shutdown from App.Startup override to ensure to exit as soon as possible.
In hope that anybody will like it... or will inspire a little bit :-)
Project startup class should be set as 'SingleInstanceApp'.
public class SingleInstanceApp
{
[STAThread]
public static void Main(string[] args)
{
Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");
if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
{
try
{
var app = new App();
app.InitializeComponent();
app.Run();
}
finally
{
_mutexSingleInstance.ReleaseMutex();
_mutexSingleInstance.Close();
}
}
else
{
MessageBox.Show("One instance is already running.");
var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
{
if (processes.Length > 1)
{
foreach (var process in processes)
{
if (process.Id != Process.GetCurrentProcess().Id)
{
WindowHelper.SetForegroundWindow(process.MainWindowHandle);
}
}
}
}
}
}
}
WindowHelper:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
namespace HQ.Util.Unmanaged
{
public class WindowHelper
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
Named-mutex-based approaches are not cross-platform because named mutexes are not global in Mono. Process-enumeration-based approaches don't have any synchronization and may result in incorrect behavior (e.g. multiple processes started at the same time may all self-terminate depending on timing). Windowing-system-based approaches are not desirable in a console application. This solution, built on top of Divin's answer, addresses all these issues:
using System;
using System.IO;
namespace TestCs
{
public class Program
{
// The app id must be unique. Generate a new guid for your application.
public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";
// The stream is stored globally to ensure that it won't be disposed before the application terminates.
public static FileStream UniqueInstanceStream;
public static int Main(string[] args)
{
EnsureUniqueInstance();
// Your code here.
return 0;
}
private static void EnsureUniqueInstance()
{
// Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
string lockDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"UniqueInstanceApps");
string lockPath = Path.Combine(lockDir, $"{AppId}.unique");
Directory.CreateDirectory(lockDir);
try
{
// Create the file with exclusive write access. If this fails, then another process is executing.
UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);
// Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
// (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
UniqueInstanceStream.Flush();
}
catch
{
throw new Exception("Another instance of the application is already running.");
}
}
}
}
[I have provided sample code for console and wpf applications below.]
You only have to check the value of the createdNew variable (example below!), after you create the named Mutex instance.
The boolean createdNew will return false:
if the Mutex instance named "YourApplicationNameHere" was already
created on the system somewhere
The boolean createdNew will return true:
if this is the first Mutex named "YourApplicationNameHere" on the
system.
Console application - Example:
static Mutex m = null;
static void Main(string[] args)
{
const string mutexName = "YourApplicationNameHere";
bool createdNew = false;
try
{
// Initializes a new instance of the Mutex class with a Boolean value that indicates
// whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex,
// and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.
using (m = new Mutex(true, mutexName, out createdNew))
{
if (!createdNew)
{
Console.WriteLine("instance is alreday running... shutting down !!!");
Console.Read();
return; // Exit the application
}
// Run your windows forms app here
Console.WriteLine("Single instance app is running!");
Console.ReadLine();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
WPF-Example:
public partial class App : Application
{
static Mutex m = null;
protected override void OnStartup(StartupEventArgs e)
{
const string mutexName = "YourApplicationNameHere";
bool createdNew = false;
try
{
// Initializes a new instance of the Mutex class with a Boolean value that indicates
// whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex,
// and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.
m = new Mutex(true, mutexName, out createdNew);
if (!createdNew)
{
Current.Shutdown(); // Exit the application
}
}
catch (Exception)
{
throw;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
if (m != null)
{
m.Dispose();
}
base.OnExit(e);
}
}
I use Mutex in my solution for preventing multiple instances.
static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = #"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);
if (!onlyInstance)
{
MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
Application.Current.Shutdown();
}
I can't find a short solution here so I hope someone will like this:
UPDATED 2018-09-20
Put this code in your Program.cs:
using System.Diagnostics;
static void Main()
{
Process thisProcess = Process.GetCurrentProcess();
Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
if (allProcesses.Length > 1)
{
// Don't put a MessageBox in here because the user could spam this MessageBox.
return;
}
// Optional code. If you don't want that someone runs your ".exe" with a different name:
string exeName = AppDomain.CurrentDomain.FriendlyName;
// in debug mode, don't forget that you don't use your normal .exe name.
// Debug uses the .vshost.exe.
if (exeName != "the name of your executable.exe")
{
// You can add a MessageBox here if you want.
// To point out to users that the name got changed and maybe what the name should be or something like that^^
MessageBox.Show("The executable name should be \"the name of your executable.exe\"",
"Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Following code is default code:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
Use mutex solution:
using System;
using System.Windows.Forms;
using System.Threading;
namespace OneAndOnlyOne
{
static class Program
{
static String _mutexID = " // generate guid"
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Boolean _isNotRunning;
using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
{
if (_isNotRunning)
{
Application.Run(new Form1());
}
else
{
MessageBox.Show("An instance is already running.");
return;
}
}
}
}
}
I added a sendMessage Method to the NativeMethods Class.
Apparently the postmessage method dosent work, if the application is not show in the taskbar, however using the sendmessage method solves this.
class NativeMethods
{
public const int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
}
Here's a lightweight solution I use which allows the application to bring an already existing window to the foreground without resorting to custom windows messages or blindly searching process names.
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
static readonly string guid = "<Application Guid>";
static void Main()
{
Mutex mutex = null;
if (!CreateMutex(out mutex))
return;
// Application startup code.
Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}
static bool CreateMutex(out Mutex mutex)
{
bool createdNew = false;
mutex = new Mutex(false, guid, out createdNew);
if (createdNew)
{
Process process = Process.GetCurrentProcess();
string value = process.Id.ToString();
Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
}
else
{
string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
Process process = null;
int processId = -1;
if (int.TryParse(value, out processId))
process = Process.GetProcessById(processId);
if (process == null || !SetForegroundWindow(process.MainWindowHandle))
MessageBox.Show("Unable to start application. An instance of this application is already running.");
}
return createdNew;
}
Edit: You can also store and initialize mutex and createdNew statically, but you'll need to explicitly dispose/release the mutex once you're done with it. Personally, I prefer keeping the mutex local as it will be automatically disposed of even if the application closes without ever reaching the end of Main.
Here's the same thing implemented via Event.
public enum ApplicationSingleInstanceMode
{
CurrentUserSession,
AllSessionsOfCurrentUser,
Pc
}
public class ApplicationSingleInstancePerUser: IDisposable
{
private readonly EventWaitHandle _event;
/// <summary>
/// Shows if the current instance of ghost is the first
/// </summary>
public bool FirstInstance { get; private set; }
/// <summary>
/// Initializes
/// </summary>
/// <param name="applicationName">The application name</param>
/// <param name="mode">The single mode</param>
public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
{
string name;
if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
name = $"Local\\{applicationName}";
else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
name = $"Global\\{applicationName}{Environment.UserDomainName}";
else
name = $"Global\\{applicationName}";
try
{
bool created;
_event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
FirstInstance = created;
}
catch
{
}
}
public void Dispose()
{
_event.Dispose();
}
}
This is how I ended up taking care of this issue. Note that debug code is still in there for testing. This code is within the OnStartup in the App.xaml.cs file. (WPF)
// Process already running ?
if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
{
// Show your error message
MessageBox.Show("xxx is already running. \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);
// This process
Process currentProcess = Process.GetCurrentProcess();
// Get all processes running on the local computer.
Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
// ID of this process...
int temp = currentProcess.Id;
MessageBox.Show("This Process ID: " + temp.ToString());
for (int i = 0; i < localAll.Length; i++)
{
// Find the other process
if (localAll[i].Id != currentProcess.Id)
{
MessageBox.Show("Original Process ID (Switching to): " + localAll[i].Id.ToString());
// Switch to it...
SetForegroundWindow(localAll[i].MainWindowHandle);
}
}
Application.Current.Shutdown();
}
This may have issues that I have not caught yet. If I run into any I'll update my answer.
A time saving solution for C# Winforms...
Program.cs:
using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;
namespace YourNamespace
{
public class SingleInstanceController : WindowsFormsApplicationBase
{
public SingleInstanceController()
{
this.IsSingleInstance = true;
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
{
e.BringToForeground = true;
base.OnStartupNextInstance(e);
}
protected override void OnCreateMainForm()
{
this.MainForm = new Form1();
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs();
SingleInstanceController controller = new SingleInstanceController();
controller.Run(args);
}
}
}
Please check the proposed solution from here that uses a semaphore to determine if an existing instance is already running, works for a WPF application and can pass arguments from second instance to the first already running instance by using a TcpListener and a TcpClient:
It works also for .NET Core, not only for .NET Framework.
I am coding away on my plugin for SDL Trados Studio.
The last part of the plugin requires some automation that is not exposed by the APIs at all, so all I have (hold on to something) is to automate the default keyboard shortcuts.
I have the code working perfectly for the English keyboard layout (and Hungarian, too!), but it of course does not work for Greek, Russian and so forth.
I have been searching for the solution but I was not able to find it until now, not on the web nor on SO, such as this post: Change keyboard layouts through code c#
I need to change the keyboard layout to English so it can take the correct shortcuts (and other character strings). Then I need to switch it back to what it was before. I am working with a very limited API, so I only have SendKeys at my disposal.
Here is the working code:
//Save the document
SendKeys.SendWait("^s");
//Open files view
SendKeys.SendWait("%v");
SendKeys.SendWait("i");
SendKeys.SendWait("1");
Application.DoEvents();
//get url and credentials from a custom input form
string[] psw = UploadData.GetPassword(
Settings.GetValue("Upload", "Uri", ""),
Vars.wsUsername == null ? Settings.GetValue("Upload", "User", "") : Vars.wsUsername,
Vars.wsPassword == null ? "" : Vars.wsPassword
);
Application.DoEvents();
if (psw != null)
{
try
{
//start upload
SendKeys.SendWait("%h");
SendKeys.Send("r");
//select all files
SendKeys.Send("%a");
SendKeys.Send("%n");
//enter login url
SendKeys.Send("%l");
SendKeys.Send("{TAB}");
SendKeys.Send(psw[0]);
SendKeys.Send("{TAB}");
SendKeys.Send("{ENTER}");
//enter username
SendKeys.Send("%l");
SendKeys.Send("+{END}");
SendKeys.Send(psw[1]);
//enter credentials
SendKeys.Send("%p");
SendKeys.Send(SendEscape(psw[2]));
SendKeys.Send("{ENTER}");
//start upload
SendKeys.SendWait("%f");
}
catch (Exception)
{
MessageBox.Show("Cannot do automatic upload, please use the default method of Trados Studio.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
finally
{
//switch back to editor view
SendKeys.SendWait("%vd");
}
}
So the questions I have:
Can somebody help me with a code to actually store the current keyboard layout and switch to English, then switch it back at the end?
Is there a simpler solution? I tried to look at the native methods but it is too high for me, so I would really appreciate any help to convert my code into native if that is the way to go instead of switching the keyboard layout. Any suggestions?
Switching the keyboard layout requires some P/Invoke; you´ll need at least the following Windows functions to get it working: LoadKeyboardLayout, GetKeyboardLayout and ActivateKeyboardLayout. The following import declarations worked for me...
[DllImport("user32.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode,
EntryPoint = "LoadKeyboardLayout",
SetLastError = true,
ThrowOnUnmappableChar = false)]
static extern uint LoadKeyboardLayout(
StringBuilder pwszKLID,
uint flags);
[DllImport("user32.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode,
EntryPoint = "GetKeyboardLayout",
SetLastError = true,
ThrowOnUnmappableChar = false)]
static extern uint GetKeyboardLayout(
uint idThread);
[DllImport("user32.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode,
EntryPoint = "ActivateKeyboardLayout",
SetLastError = true,
ThrowOnUnmappableChar = false)]
static extern uint ActivateKeyboardLayout(
uint hkl,
uint Flags);
static class KeyboardLayoutFlags
{
public const uint KLF_ACTIVATE = 0x00000001;
public const uint KLF_SETFORPROCESS = 0x00000100;
}
Whenever I have to use native API methods I try to encapsulate them in a class that hides their declaration from the rest of the project´s codebase. So, I came up with a class called KeyboardLayout; that class can load and activate a layout by a given CultureInfo, which comes in handy...
internal sealed class KeyboardLayout
{
...
private readonly uint hkl;
private KeyboardLayout(CultureInfo cultureInfo)
{
string layoutName = cultureInfo.LCID.ToString("x8");
var pwszKlid = new StringBuilder(layoutName);
this.hkl = LoadKeyboardLayout(pwszKlid, KeyboardLayoutFlags.KLF_ACTIVATE);
}
private KeyboardLayout(uint hkl)
{
this.hkl = hkl;
}
public uint Handle
{
get
{
return this.hkl;
}
}
public static KeyboardLayout GetCurrent()
{
uint hkl = GetKeyboardLayout((uint)Thread.CurrentThread.ManagedThreadId);
return new KeyboardLayout(hkl);
}
public static KeyboardLayout Load(CultureInfo culture)
{
return new KeyboardLayout(culture);
}
public void Activate()
{
ActivateKeyboardLayout(this.hkl, KeyboardLayoutFlags.KLF_SETFORPROCESS);
}
}
If you only need to have the layout be active for a short while - and you want make sure to properly restore the layout when done, you could write some kind of a scope type using the IDiposable interface. For instance...
class KeyboardLayoutScope : IDiposable
{
private readonly KeyboardLayout currentLayout;
public KeyboardLayoutScope(CultureInfo culture)
{
this.currentLayout = KeyboardLayout.GetCurrent();
var layout = KeyboardLayout.Load(culture);
layout.Activate();
}
public void Dispose()
{
this.currentLayout.Activate();
}
}
Than you can use it like this...
const int English = 1033;
using (new KeyboardLayoutScope(CultureInfo.GetCultureInfo(English))
{
// the layout will be valid within this using-block
}
You should know that in newer versions of Windows (beginning in Windows 8) the keyboard layout cannot be set for a certain process anymore, instead it is set globally for the entire system - and the layout can also be changed by other applications, or by the user (using the Win + Spacebar shortcut).
I would also recommend to not use SendKeys (or its native counterpart SendInput) since it simulates keyboard input which will be routed to the active/focused window. Using the SendMessage function instead is suitable, but you might want combine that with functionality that can properly determine the target window; but to explain such technique would go beyond the scope of the this question and answer. This answer here illustrates a possible solution: How to send keystrokes to a window?
Change keyboard layouts through c# code:
using System;
using System.Linq;
using System.Globalization;
using System.Windows.Forms;
...
public static void Switch_keyboard(string lang)
{
CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture(lang);
InputLanguage inputLanguage = InputLanguage.FromCulture(cultureInfo);
InputLanguage.CurrentInputLanguage = inputLanguage;
}
private void button1_Click(object sender, EventArgs e)
{
var list = InputLanguage.InstalledInputLanguages.Cast<InputLanguage>().Select(c => c.Culture.Name).ToList();
Switch_keyboard(list[0]); // "ru-RU" or "ru-BY" ...
}
Change class #Matze,
internal class KeyboardLayoutScope : IDisposable, IKeyboardLayoutScope
{
private readonly KeyboardLayout currentLayout;
public KeyboardLayoutScope() { }
public KeyboardLayoutScope(CultureInfo culture)
{
currentLayout = KeyboardLayout.GetCurrent();
KeyboardLayout.Load(culture).Activate();
}
public KeyboardLayoutScope(KeyboardLayout currentLayout) => this.currentLayout = currentLayout;
public void Dispose() => currentLayout.Activate();
}
internal interface IKeyboardLayoutScope
{
void Dispose();
}
Other Class
internal sealed class KeyboardLayout
{
private readonly uint hkl;
private KeyboardLayout(CultureInfo cultureInfo) => hkl = NativeMethods.LoadKeyboardLayout(new StringBuilder(cultureInfo.LCID.ToString("x8")), KeyboardLayoutFlags.KLF_ACTIVATE);
private KeyboardLayout(uint hkl) => this.hkl = hkl;
public uint Handle => hkl;
public static KeyboardLayout GetCurrent() => new KeyboardLayout(NativeMethods.GetKeyboardLayout((uint)Thread.CurrentThread.ManagedThreadId));
public static KeyboardLayout Load(CultureInfo culture) => new KeyboardLayout(culture);
public void Activate() => NativeMethods.ActivateKeyboardLayout(hkl, KeyboardLayoutFlags.KLF_SETFORPROCESS);
}
Try thisone:
public void switch_keyboard(string lang)
{
string keyboard_lang = InputLanguage.CurrentInputLanguage.Culture.TwoLetterISOLanguageName;
if (keyboard_lang != lang)
{
int en_index = -1;
for (int i = 0; i < System.Windows.Forms.InputLanguage.InstalledInputLanguages.Count - 1; i++)
{
try
{
if (System.Windows.Forms.InputLanguage.InstalledInputLanguages[i].Culture.TwoLetterISOLanguageName == lang)
{ en_index = i; break; }
}
catch { break; }
}
if (en_index != -1)
{ InputLanguage.CurrentInputLanguage = System.Windows.Forms.InputLanguage.InstalledInputLanguages[en_index]; }
}
}
In 4.6.1 all methods posted here isn't working properly>
I got my solution:
// set keyboard layout
LoadKeyboardLayout("en-EN", 0x00000100);
// methods
[DllImport("user32.dll")] public static extern IntPtr GetKeyboardLayout(uint thread);
[DllImport("user32.dll")]
public static extern long LoadKeyboardLayout(
string pwszKLID, // input locale identifier
uint Flags // input locale identifier options
);
public static CultureInfo GetCurrentKeyboardLayout()
{
try
{
int keyboardLayout = GetKeyboardLayout(0).ToInt32() & 0xFFFF;
return new CultureInfo(keyboardLayout);
}
catch
{
return new CultureInfo(1033); // Assume English if something went wrong.
}
}
I'm building an application in C# using WPF. How can I bind to some keys?
Also, how can I bind to the Windows key?
This is a full working solution, hope it helps...
Usage:
_hotKey = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler);
...
private void OnHotKeyHandler(HotKey hotKey)
{
SystemHelper.SetScreenSaverRunning();
}
Class:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mime;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace UnManaged
{
public class HotKey : IDisposable
{
private static Dictionary<int, HotKey> _dictHotKeyToCalBackProc;
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
public const int WmHotKey = 0x0312;
private bool _disposed = false;
public Key Key { get; private set; }
public KeyModifier KeyModifiers { get; private set; }
public Action<HotKey> Action { get; private set; }
public int Id { get; set; }
// ******************************************************************
public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true)
{
Key = k;
KeyModifiers = keyModifiers;
Action = action;
if (register)
{
Register();
}
}
// ******************************************************************
public bool Register()
{
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);
if (_dictHotKeyToCalBackProc == null)
{
_dictHotKeyToCalBackProc = new Dictionary<int, HotKey>();
ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
}
_dictHotKeyToCalBackProc.Add(Id, this);
Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
return result;
}
// ******************************************************************
public void Unregister()
{
HotKey hotKey;
if (_dictHotKeyToCalBackProc.TryGetValue(Id, out hotKey))
{
UnregisterHotKey(IntPtr.Zero, Id);
}
}
// ******************************************************************
private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
{
if (!handled)
{
if (msg.message == WmHotKey)
{
HotKey hotKey;
if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
{
if (hotKey.Action != null)
{
hotKey.Action.Invoke(hotKey);
}
handled = true;
}
}
}
}
// ******************************************************************
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// ******************************************************************
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be _disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be _disposed.
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this._disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
Unregister();
}
// Note disposing has been done.
_disposed = true;
}
}
}
// ******************************************************************
[Flags]
public enum KeyModifier
{
None = 0x0000,
Alt = 0x0001,
Ctrl = 0x0002,
NoRepeat = 0x4000,
Shift = 0x0004,
Win = 0x0008
}
// ******************************************************************
}
I'm not sure of what you mean by "global" here, but here it goes (I'm assuming you mean a command at the application level, for example, Save All that can be triggered from anywhere by Ctrl + Shift + S.)
You find the global UIElement of your choice, for example, the top level window which is the parent of all the controls where you need this binding. Due to "bubbling" of WPF events, events at child elements will bubble all the way up to the root of the control tree.
Now, first you need
to bind the Key-Combo with a Command using an InputBinding like this
you can then hookup the command to your handler (e.g. code that gets called by SaveAll) via a CommandBinding.
For the Windows Key, you use the right Key enumerated member, Key.LWin or Key.RWin
public WindowMain()
{
InitializeComponent();
// Bind Key
var ib = new InputBinding(
MyAppCommands.SaveAll,
new KeyGesture(Key.S, ModifierKeys.Shift | ModifierKeys.Control));
this.InputBindings.Add(ib);
// Bind handler
var cb = new CommandBinding( MyAppCommands.SaveAll);
cb.Executed += new ExecutedRoutedEventHandler( HandlerThatSavesEverthing );
this.CommandBindings.Add (cb );
}
private void HandlerThatSavesEverthing (object obSender, ExecutedRoutedEventArgs e)
{
// Do the Save All thing here.
}
Registering OS level shortcuts is hardly ever a good thing: users don't want you to mess with their OS.
That said, there is a much simpler and user friendly way of doing this in WPF, if you're ok with the hotkey working within the application only (i.e as long as your WPF app has the focus):
In App.xaml.cs :
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(Window), Window.PreviewKeyUpEvent, new KeyEventHandler(OnWindowKeyUp));
}
private void OnWindowKeyUp(object source, KeyEventArgs e))
{
//Do whatever you like with e.Key and Keyboard.Modifiers
}
It's that simple
If you're going to mix Win32 and WPF, here's how I did it:
using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Media;
using System.Threading;
using System.Windows;
using System.Windows.Input;
namespace GlobalKeyboardHook
{
public class KeyboardHandler : IDisposable
{
public const int WM_HOTKEY = 0x0312;
public const int VIRTUALKEYCODE_FOR_CAPS_LOCK = 0x14;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private readonly Window _mainWindow;
WindowInteropHelper _host;
public KeyboardHandler(Window mainWindow)
{
_mainWindow = mainWindow;
_host = new WindowInteropHelper(_mainWindow);
SetupHotKey(_host.Handle);
ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage;
}
void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled)
{
if (msg.message == WM_HOTKEY)
{
//Handle hot key kere
}
}
private void SetupHotKey(IntPtr handle)
{
RegisterHotKey(handle, GetType().GetHashCode(), 0, VIRTUALKEYCODE_FOR_CAPS_LOCK);
}
public void Dispose()
{
UnregisterHotKey(_host.Handle, GetType().GetHashCode());
}
}
}
You can get the virtual-key code for the hotkey you want to register here: http://msdn.microsoft.com/en-us/library/ms927178.aspx
There may be a better way, but this is what I've got so far.
Cheers!
This is similar to the answers already given, but I find it a bit cleaner:
using System;
using System.Windows.Forms;
namespace GlobalHotkeyExampleForm
{
public partial class ExampleForm : Form
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
enum KeyModifier
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
WinKey = 8
}
public ExampleForm()
{
InitializeComponent();
int id = 0; // The id of the hotkey.
RegisterHotKey(this.Handle, id, (int)KeyModifier.Shift, Keys.A.GetHashCode()); // Register Shift + A as global hotkey.
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0312)
{
/* Note that the three lines below are not needed if you only want to register one hotkey.
* The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF); // The key of the hotkey that was pressed.
KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF); // The modifier of the hotkey that was pressed.
int id = m.WParam.ToInt32(); // The id of the hotkey that was pressed.
MessageBox.Show("Hotkey has been pressed!");
// do something
}
}
private void ExampleForm_FormClosing(object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 0); // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey.
}
}
}
I've found it on fluxbytes.com.
With the NHotKey package, you can make your hotkey global:
https://github.com/thomaslevesque/NHotkey
https://thomaslevesque.com/2014/02/05/wpf-declare-global-hotkeys-in-xaml-with-nhotkey/ (use web.archive.org if the link is broken)
In short, for XAML, all you need to do is to replace
<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}" />
by
<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}"
HotkeyManager.RegisterGlobalHotkey="True" />
I'm not sure about WPF, but this may help. I used the solution described in RegisterHotKey (user32) (modified to my needs of course) for a C# Windows Forms application to assign a CTRL-KEY combination within Windows to bring up a C# form, and it worked beautifully (even on Windows Vista). I hope it helps and good luck!
I've found the Global Hotkeys in WPF project on codeproject.com which does the job for me. It's relatively recent, does not need a reference to System.Windows.Forms and works "globally" in terms of reacting to the hotkey being pressed even if "your" application is not the active window.
Baboon's solution works best because you may have multiple windows. I did tweak it so it uses the PreviewKeyDownEvent instead of the PreviewKeyUpEvent in order to handle repetition in keystrokes.
I would advise against OS-level registration unless you are writing something like a snipping tool or an audio recording app as it will let you access functionality when the window is not focused.
Although RegisterHotKey is sometimes precisely what you want, in most cases you probably do not want to use system-wide hotkeys. I ended up using code like the following:
using System.Windows;
using System.Windows.Interop;
namespace WpfApp
{
public partial class MainWindow : Window
{
const int WM_KEYUP = 0x0101;
const int VK_RETURN = 0x0D;
const int VK_LEFT = 0x25;
public MainWindow()
{
this.InitializeComponent();
ComponentDispatcher.ThreadPreprocessMessage +=
ComponentDispatcher_ThreadPreprocessMessage;
}
void ComponentDispatcher_ThreadPreprocessMessage(
ref MSG msg, ref bool handled)
{
if (msg.message == WM_KEYUP)
{
if ((int)msg.wParam == VK_RETURN)
MessageBox.Show("RETURN was pressed");
if ((int)msg.wParam == VK_LEFT)
MessageBox.Show("LEFT was pressed");
}
}
}
}
RegisterHotKey() suggested by John could work - the only catch is that it requires an HWND (using PresentationSource.FromVisual(), and casting the result to an HwndSource).
However, you'll also need to respond to the WM_HOTKEY message - I'm not sure if there is a way to get access to the WndProc of a WPF window or not (which can be done for Windows Forms windows).