Thursday, November 24, 2005 #

Single Instance Application - Part 2 : activating existing instance

Well, I did not have post for a long time. I was working hard on the next version of PixVillage, which will be released soon now.

I'm coming back with a new post in the Single Instance Application series.

In the previous post, we have written a ProcessLock object that help us to detect an existing instance of the program we attempt to run, so that we can prevent running it twice.

Today I'm dealing with the ability to activate an existing instance of a Windows Forms application when running a new one. In order to achieve that, we have to notify the application it has to go foreground and get focus.

This can be done though a message send to the main thread of the application. The message is received by the thread, not by a window, so we have to write a specific handler and register it. The IMessageFilter interface helps providing this kind of service. Let's implement it!

Code Copy HideScrollFull
namespace SingleInstance {
/// <summary>
/// Provides a thread message filter and handle messages.
/// </summary>
public class ThreadMessageFilter : IMessageFilter {
private Form owner;

/// <summary>
/// Initializes a new instance of the <see cref="ThreadMessageFilter"/> class.
/// </summary>
/// <param name="owner">The owner.</param>
public ThreadMessageFilter(Form owner) {
this.owner = owner;
}

/// <exclude/>
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode=true)]
bool IMessageFilter.PreFilterMessage(ref Message m) {
if (m.HWnd != IntPtr.Zero) // Get rid of message if it's sent to a window...
return false;
// Handle the message here...

return false;
}
}
}
. . .

Now, supposing we want to activate the window when a new instance of the application is launched, we can, for instance, send a WM_SHOWWINDOW message to the thread, using the Windows PostThreadMessage API. The IMessageFilter.PreFilterMessage is implemented as follow:

Code Copy HideScrollFull
/// <exclude/>
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode=true)]
bool IMessageFilter.PreFilterMessage(ref Message m) {
if (m.HWnd != IntPtr.Zero) // Get rid of message if it's sent to a window...
return false;
if (m.Msg == WM_SHOWWINDOW) {
// Shows the window
try {
owner.Show();
if (owner.WindowState == FormWindowState.Minimized)
owner.WindowState = FormWindowState.Normal;
return true;
}
catch {} // return false;
}

return false;
}
. . .

With WM_SHOWWINDOW declared as follow:

Code Copy
private const int WM_SHOWWINDOW = 0x18;

Now, we just have to modify the Main() method in order to:

  • Create and show the form the first time the application is started.
  • Registers and unregisters a ThreadMessageFilter object.
  • Send the message to any thread of any process having the same name as the current assembly.

The code is given below. (MainForm is a class derived from System.Windows.Form).

Code Copy HideScrollFull
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SingleInstance
{
public sealed class SingleInstance
{
private SingleInstance(){}
private static readonly string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

[DllImport("user32.dll")]
private static extern bool PostThreadMessage(int threadId, int message, int wParam, int lParam);

private const int WM_SHOWWINDOW = 0x18;

/// <summary>
/// Application entry point.
/// </summary>
[STAThread]
static void Main() {
using(ProcessLock processLock = new ProcessLock(assemblyName)) {
if (processLock.AlreadyExists)
{
// Sets the existing application foreground.
SetForeground();
}
else
{
// The program operation must run inside the 'using' block.
Run();
}
}
}

private static void SetForeground() {
// Find all processes having the same name
Process[] processes = Process.GetProcessesByName(assemblyName);
foreach (Process process in processes)
{
if (process.Id == Process.GetCurrentProcess().Id)                
// This is the current process, pass
continue;
// Activates the other instance window by sending the message to any thread in the process.
foreach (ProcessThread thread in process.Threads)
PostThreadMessage(thread.Id, WM_SHOWWINDOW, 0, 0);
}
}

private static void Run() {
using(MainForm form = new MainForm()) {
IMessageFilter filter = new ThreadMessageFilter(form);
Application.AddMessageFilter(filter);

try
{
form.Show();
Application.Run(form);
}
finally {
Application.RemoveMessageFilter(filter);
}
}
}
}
}
. . .

posted @ 6:00 PM | Feedback (24)