Recently I had the chance to improve some old "previous instance" detection code to add the ability to activate the previous instance. The code I used to detect a previous instance looked like this:
Imports System.Diagnostics
Private Function PrevInstance() As Boolean
If UBound(Diagnostics.Process.GetProcessesByName(System.Reflection.Assembly.GetExecutingAssembly.GetName.Name)) > 0 Then
Return True
Else
Return False
End If
End Function
Looking at this code now I can see many ways to optimise it. It returns "True" if there's another instance of the program running. I usually make a call to "PrevInstance" after I show a splash screen, and if there's another instance I simply pop up a message box advising the user to switch with ALT+TAB, then close the current instance.
My enhancement was that I wanted to bring the previous instance to the foreground. To do that I need the window handle, and most of the code to get the window handle relies on the FindWindow API. The FindWindow API takes a window title - there's some code at Paul Laudeman's blog "Windows Forms Tip: Ensure only one instance of your application is running at a time" which uses a mutex to do this (this code also works perfectly, if you know the window title, which leads me to my next point).
If you change the window title and you don't know the exact window title, it is still possible to get a window handle using FindWindow based on the class name alone. However I would have had to loop through windows of the type "WindowsForms10.Window.8.app*" (Paul's original post has a "3" in place of the "*", I found I only got results when I used a "9" in place of the "*") to find my window. To further complicate matters, I have my splash screen showing, so it also has the window title that I'm looking for. There may have been a partial solution in VB6 at "Win32 Window Title and Class Name Demo" which has a "FindWindowLike" function which might have helped (I did not have to go that far, thankfully).
I eventually went back to my existing code to see what could be done and played with the properties of the "Process" class, and made the following changes:
Imports System.Diagnostics
Private Function PrevInstance(ByRef handle as IntPtr) As Boolean
' array of running processes with this assembly name
Dim p() As Process = Nothing
Try
' put the processes into an array
p = Diagnostics.Process.GetProcessesByName(System.Reflection.Assembly.GetExecutingAssembly.GetName.Name())
' if there's more than one process with this name
If p.Length > 1 Then
' get the handle of the main window of the first running instance
handle = p(0).MainWindowHandle
' program is running...return True
PrevInstance = True
Else
PrevInstance = False
End If
Catch ex As System.Exception
' set return to False
handle = IntPtr.Zero
PrevInstance = False
Finally
' clean up
p = Nothing
End Try
End Function
Note: as you can see, my above code uses reflection, which incurs a performance hit. All valid comments/criticisms accepted.
The main change I made was to return a ByRef handle to the main window of the previous instance using the "MainWindowHandle" property. Easy.
I also have a function called "ShowPreviousInstance" which takes the window handle:
' parameter for "nCmdShow" for ShowWindow
Private SW_RESTORE As Integer = 9
''' <summary>
''' Is the passed window handle minimised to the taskbar?
''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/isiconic.asp
''' </summary>
Private Declare Auto Function IsIconic Lib "user32" (ByVal hWnd As IntPtr) As IntPtr
''' <summary>
''' Sets a passed window to be in the foreground.
''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/setforegroundwindow.asp
''' </summary>
Private Declare Auto Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long
''' <summary>
''' Show a window with the passed command (state)
''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/showwindow.asp
''' </summary>
Private Declare Auto Function ShowWindow Lib "user32" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr
Private Function ShowPreviousInstance(ByVal handle As IntPtr) As Boolean
' if we got a window of the currently-running instance
If handle.ToInt32 <> IntPtr.Zero.ToInt32 Then
Try
' if the window IsIconic (is in the taskbar), restore it
If IsIconic(handle).ToInt32 <> 0 Then
ShowWindow(handle, SW_RESTORE)
End If
' make foreground window
SetForegroundWindow(handle)
' return True to say we switched to the previous instance
ShowPreviousInstance = True
Catch ex As System.Exception
' return false
ShowPreviousInstance = False
End Try
Else
' no valid handle passed, return false
ShowPreviousInstance = False
End If
End Function
If "ShowPreviousInstance" returns True, I close down the new instance of the application.
I like this code because it's a little shorter than some of the other samples I've seen, and it doesn't use a lot of API calls. The ultimate ease-of-use setting is coming in Visual Basic 2005, but this code is going to do until then.
UPDATE: for this code to work, you need to call Application.Run with an Application COntext, and not just a form. Here's some democode that supplements my original code above:
'(in module)
' private instance of Main form
Private m_frmMain As frmMain ' **** change to your main form here ****
' private Application context
Private m_appContext As ApplicationContext
<STAThread()> _
Public Sub Main()
' initialise Application context
m_appContext = New ApplicationContext
' process handle for currently-running application
Dim handle As IntPtr = IntPtr.Zero
' is the program currently running, and have we activated it?
Dim blnQuit As Boolean = False
' check for previous instance
If PrevInstance(handle) Then
MessageBox.Show("App is already running!")
' did we get a handle from "PrevInstance"?
If handle.ToInt32 <> IntPtr.Zero.ToInt32 Then
' check if we can show the window, and if so, set quit flag to true
blnQuit = ShowPreviousInstance(handle)
End If
End If
If Not blnQuit Then
' set up main form
m_frmMain = New frmMain ' **** change to your main form here ****
' show it
m_frmMain.Show()
' **** this line sets the main window, allowing process "MainWindowHandle" call to work! ****
m_appContext.MainForm = m_frmMain
' run the application context
Application.Run(m_appContext)
' Application will exit when closing main Form
End If
' clean-up code can go here
End Sub