Allowing a Windows app to only have one instance and bring up a minimized or hidden window
This article shows how to keep an app to one instance and how to bring up the existing app if it is minimized or hidden, like with a notify icon in the tray.
Introduction
First of all, I have to say, I have debated about writing this article for a long time. There are already several articles out there that pretty much do the same thing. So why did I do it? Well, it seems that other solutions can find an existing instance of the app, but I haven’t seen too many examples that will bring up the existing instance if it is minimized or hidden.
Background
A few years back, when I was doing more Delphi programming than .NET, I had a nice little unit that helped me ensure I only had one instance of an application running on a user’s local machine. So I decided to try and do something similar in .NET. The only thing that is interesting with my solution is that it will bring up the form if it is minimized or hidden (like in a tray icon).
The Solution
First, I will tell you that I don’t know of any pure .NET solution for this. You have to do API calls to use Windows messaging. I used a Mutex to see if the app is already running. Then, I used Windows messaging to tell the app that is already running to show itself and go to the normal window state if it is minimized.
The Code
If you have never seen how to import a DLL and thus do a Windows API call, here is what the code looks like so we can send a Windows message:
//C#
[DllImport("USER32.DLL", EntryPoint="BroadcastSystemMessageA",
SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern int BroadcastSystemMessage(Int32 dwFlags, ref Int32
pdwRecipients, int uiMessage, int wParam, int lParam);
' VB.net
<DLLIMPORT("USER32.DLL", EntryPoint:="BroadcastSystemMessageA", _
SetLastError:=True, CharSet:=CharSet.Unicode, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall> _
Public Shared Function BroadcastSystemMessage(ByVal dwFlags As Int32, _
ByRef pdwRecipients As Int32, ByVal uiMessage As Integer, _
ByVal wParam As Integer, ByVal lParam As Integer) As Integer
' Leave function empty - DLLImport attribute forwards calls to
' BroadcastSystemMessage to
' BroadcastSystemMessage in USER32.DLL.
End Function
Next, we have the method that is called in the OnLoad
event of the form.
//C#
private void CheckPrevious()
{
//Check for previous instance of this app
m_uniqueIdentifier = Application.ExecutablePath.Replace(@"\", "_");
m_Mutex = new System.Threading.Mutex(false, m_uniqueIdentifier);
//First register the windows message
MessageId = RegisterWindowMessage(m_uniqueIdentifier);
if (m_Mutex.WaitOne(1, true))
{
//we are the first instance don't need to do anything
}
else
{
//Cause the current form to show
//Now brodcast a message to cause the first instance to show up
Int32 BSMRecipients = BSM_APPLICATIONS; //Only go to applications
Int32 tmpuint32 = 0;
tmpuint32 = tmpuint32 | BSF_IGNORECURRENTTASK; //Ignore current app
tmpuint32 = tmpuint32 | BSF_POSTMESSAGE; //Post the windows message
int ret = BroadcastSystemMessage(tmpuint32, ref BSMRecipients,
MessageId, 0, 0);
//A differnt instance already exists exit now.
Application.Exit();
} //else
}
' VB.net
Private Sub checkprevious()
'Check for previous instance of this app
m_uniqueIdentifier = Application.ExecutablePath.Replace("\", "_")
m_Mutex = New System.Threading.Mutex(False, m_uniqueIdentifier)
'First register the windows message
MessageId = RegisterWindowMessage(m_uniqueIdentifier)
If m_Mutex.WaitOne(1, True) Then
'we are the first instance don't need to do anything
Else
'Cause the current form to show
'Now brodcast a message to cause the first instance to show up
Dim BSMRecipients As Int32 = BSM_APPLICATIONS 'Only go to applications
Dim tmpuint32 As Int32 = 0
tmpuint32 = tmpuint32 Or BSF_IGNORECURRENTTASK 'Ignore current app
tmpuint32 = tmpuint32 Or BSF_POSTMESSAGE 'Post the windows message
Dim ret As Integer
ret = BroadcastSystemMessage(tmpuint32, BSMRecipients, MessageId, 0, 0)
'A differnt instance already exists exit now.
Application.Exit()
End If
End Sub
Finally, we have the method that checks the Windows messages for the form. Note: you must be very careful when you override this method. All Windows messages to this app go through this method.
//C#
protected override void DefWndProc(ref System.Windows.Forms.Message m)
{
//This overrides the windows messaging processing
if (m.Msg == MessageId) //If we found our message then activate
{
// Set the WindowState to normal if the form is minimized.
if (this.WindowState == FormWindowState.Minimized)
{
this.Show();
this.WindowState = FormWindowState.Normal;
}
// Activate the form.
this.Activate();
this.Focus();
}
else //Let the normal windows messaging process it.
{
base.DefWndProc(ref m);
}
}
' VB.net
Protected Overrides Sub DefWndProc(ByRef m As System.Windows.Forms.Message)
'This overrides the windows messaging processing
If m.Msg = MessageId Then 'If we found our message then activate
' Set the WindowState to normal if the form is minimized.
If (Me.WindowState = FormWindowState.Minimized) Then
Me.Show()
Me.WindowState = FormWindowState.Normal
End If
' Activate the form.
Me.Activate()
Me.Focus()
Else 'Let the normal windows messaging process it.
MyBase.DefWndProc(m)
End If
End Sub
Conclusion
So it is a pretty straightforward solution. I hope someone finds this helpful. I saw some postings on The Code Project where people were asking about a solution for this problem, so I decided to write this article after all.