Monday, November 12, 2007

Single Instance of Application

Many cases require only one instance of a specified application running. This can be done by using Microsoft .Net Mutex class. However, by using Mutex, you can tell if the application is already running or not. If it is running, either active, inactive, or minimized, how can we bring the application to the front? I found that you have to use API functions to do that.

As a result, I have created a class ApplicaitonUtil. This class has two methods exposed: IsAlreadyRunning(name) and SwitchToCurrentInstance(). They are very straightforwd. Here is an example to use it:

[STAThread]
static void Main()
{
AssemblyInfo1 assemblyInfo = new AssemblyInfo1(
Assembly.GetExecutingAssembly());
FileSystemInfo fileinfo = new FileInfo(
Assembly.GetExecutingAssembly().Location);

string name = string.Format("{0} {1}",
fileinfo.Name,
assemblyInfo.Description.Description);

if (ApplicationUtil.IsAlreadyRunning(name))
{
ApplicationUtil.SwitchToCurrentInstance();
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

Form1 form = new Form1(); // Assume Form1 in your project
form.Text = name; // Set caption for the form
Application.Run(form);
}
}

Here I used assembly exe name and assembly information description (see my previous blog on AssemblyInfo and Related Utility Classes) as a name for the Mutex object. The name can be anything (no path is included! otherwise you would get an exception). The reason I used assembly's description information is that the same exe file might be deployed to local version and QA version. You may need each one as single instance but you can run two at same time.

By the way, I updated my AssemblyInfo class so that it has additional constructor with specified assembly object. If you place this class in another class or exe as library, you need to pass assembly object as shown in above codes.

Here is the my ApplicationUtil class:
public class ApplicationUtil
{
# region Windows APIs
[DllImport("user32.dll")]
private static extern int IsIconic(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hWnd,
int nCmdShow);

[DllImport("user32.dll")]
private static extern int SetForegroundWindow(
IntPtr hWnd);
# endregion

# region Constructor
private ApplicationUtil()
{
}
# endregion

# region Data members
// Define const used for restoring the window
// already opened
private const int SW_RESTORE = 9;
private static Mutex _mutex;
# endregion

# region Public methods
public static bool IsAlreadyRunning(string name)
{
bool mutexCreated;

_mutex = new Mutex(true, name, out mutexCreated);
if (mutexCreated)
_mutex.ReleaseMutex();

return !mutexCreated;
}

public static void SwitchToCurrentInstance()
{
IntPtr handle = GetCurrentInstanceWindowHandle();
if (handle != IntPtr.Zero)
{
// Restore window first if minimized.
// Do not restore if already in
// normal or maximized window state,
// since we don't want to
// change the current state of the window.
if (IsIconic(handle) != 0)
{
ShowWindow(handle, SW_RESTORE);
}

SetWindowToForeground(handle);
}

return;
}

public static void SetWindowToForeground(
IntPtr hWnd)
{
SetForegroundWindow(hWnd);
}
# endregion

# region Private methods
private static IntPtr
GetCurrentInstanceWindowHandle()
{
IntPtr handle = IntPtr.Zero;
Process currentProcess =
Process.GetCurrentProcess();
Process[] processes =
Process.GetProcessesByName(
currentProcess.ProcessName);

foreach (Process process in processes)
{
// Get the first instance that is not this
// instance, has the same process name and
// was started from the same file name
// and location. Also check that the process
// has a valid window handle in this session
// to filter out other user's processes.
if (process.Id != currentProcess.Id &&
process.MainModule.FileName ==
currentProcess.MainModule.FileName &&
process.MainWindowHandle != IntPtr.Zero)
{
handle = process.MainWindowHandle;
break;
}
}

return handle;
}

# endregion
}

This class has another method SetForegroundWindow(), which is used to set a form as foreground. For example, you have a lengthy splash window running, and this form might be set to background if user is working on something else. In this case, when the splash window finishes its job, you can call this method to set the main form to the front so that user is notified the application being ready.

0 comments: