views:

1020

answers:

3

I have an application that on a subsequent start detects if there's a process with the same name already running and, if so, activates the running app's window and then exits.

The problem is that the main window could be hidden (only a notification area icon visible), thus leaving me with no window handle.

At startup, previous instance's MainWindowHandle property is 0, so I can't send ShowWindow or PostMessage.

Is there any way I can send a message that can be intercepted by the running app, thus allowing it to display its main window?

The application is written in C#, the code I'm using to achieve this below.

[STAThread]
static void Main()
{
    bool createdNew = true;
    using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew))
    {
        if (createdNew)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
        else
        {
            Process current = Process.GetCurrentProcess();
            foreach (Process process in Process.GetProcessesByName(current.ProcessName))
            {
                if (process.Id != current.Id)
                {
                    Interop.WINDOWINFO pwi = new Interop.WINDOWINFO();
                    IntPtr handle = process.MainWindowHandle;
                    var isVisible = Interop.GetWindowInfo(handle, ref pwi);
                    if (!isVisible)
                    {
                        MessageBox.Show(Constants.APP_NAME + " is already running, check the notification area (near the clock).", 
                                        Constants.APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information);//temporary message, until I find the solution
                        //Interop.ShowWindow(handle, Interop.WindowShowStyle.ShowNormal);
                        //Interop.PostMessage(handle, Interop.WM_CUSTOM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);
                    }
                    else
                        Interop.SetForegroundWindow(handle);//this works when the window is visible
                        break;
                    }
                }
            }
        }
    }
}
+1  A: 

Here's how I've done this:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
public partial class MainForm : Form
{
    #region Dll Imports
    private const int HWND_BROADCAST = 0xFFFF;

    private static readonly int WM_MY_MSG = RegisterWindowMessage( "WM_MY_MSG" );

    [DllImport( "user32" )]
    private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

    [DllImport( "user32" )]
    private static extern int RegisterWindowMessage(string message);
    #endregion Dll Imports
    static Mutex _single = new Mutex(true, "{4EABFF23-A35E-F0AB-3189-C81203BCAFF1}");
    [STAThread]
    static void Main()
    {
        // See if an instance is already running...
        if (_single.WaitOne(TimeSpan.Zero, true)) {
            // No...start up normally.
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            try {
                Application.Run(new MainForm());
            } catch (Exception ex) {
                // handle exception accordingly
            } finally {
                _single.ReleaseMutex();
            }
        } else {
            // Yes...Bring existing instance to top and activate it.
            PostMessage(
                (IntPtr) HWND_BROADCAST,
                WM_MY_MSG,
                new IntPtr(0xCDCD),
                new IntPtr(0xEFEF));
        }
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MY_MSG) {
            if ((m.WParam.ToInt32() == 0xCDCD) && (m.LParam.ToInt32() == 0xEFEF)) {
                if (WindowState == FormWindowState.Minimized) {
                    WindowState = FormWindowState.Normal;
                }
                // Bring window to front.
                bool temp = TopMost;
                TopMost = true;
                TopMost = temp;
                // Set focus to the window.
                Activate();
            }
        } else {
            base.WndProc(ref m);
        }
    }
}

I hope I've transcribed this correctly. I had to leave out a lot of other stuff, but I think I got what is necessary. What I have works for me without fail. If you have a problem, let me know, and I'll see what I've missed.

Matt Davis
You will need a reference to 'System.Runtime.InteropServices'
BillW
@BillW: Thanks. Post is updated accordingly.
Matt Davis
@Matt Nice work, Matt ! fyi : builds, works fine in VS 2010 beta 2 compiled against FrameWork 4.0. What I really like about this is you can put a call to 'MessageBox.Show("..."); in the case you are re-activating the one-and-only instance to let the end-user know what's going on. My one question would be around the issue of putting the activation in a try-catch block so you can release the Mutex : does that have any implications for application behavior if the main form instance goes on to create other forms or whatever ?
BillW
@Matt I voted your answer up, and then accidentally hit the up-vote hickey again, and now SO won't let me re up-vote, putting up a message that my vote is "too old" to be changed unless the answer is updated ! I'll write to SO about this : if my up-vote was "too old" in the first place, why did it, apparently, reverse the up-vote ?
BillW
@BillW: The only adverse implication I can think of is if a foreground thread is left running after the MainForm has exited. In this case, the app disappears from view but remains alive until the foreground thread finishes. This could potentially be bad mojo if the user tried to "re-start" the app because the same foreground thread could be created a second time. In my case, this is not an issue because I only use background threads, so the app dies immediately when the MainForm exits.
Matt Davis
@Matt fyi : I experimented with replacing the try/catch block with a 'using statement : using (_single = new Mutex(true, "{4EABFF23-A35E-F0AB-3189-C81203BCAFF1}")) { // application start code ... } and verfied the resulting WinForm behaves in the same way as using a try/catch block ... but I am not, unfortunately, competent enough (yet) to grok the implications of using this variation, and whether or not that effectively "releases" the Mutex, or opens up possible problems with threading, etc.
BillW
@BillW: There is nothing technically wrong with replacing the try/catch block with a using statement. The using statement will ensure the Mutex is disposed of properly *even if an exception is thrown*. But this is precisely why I use the try/catch block. If an unhandled exception occurs, my catch statement logs the error to the event viewer so I at least have some insight into what occurred. Alternatively, you could display the exception details in a MessageBox. The using statement does not afford you the ability to do this.
Matt Davis
Thank you Matt Davis, I just realized that I forgot to say it :)At that moment I was very eager to implement/test your solution.
chitza
@chitza: Your welcome. Have a great Thanksgiving. Getting ready to head out myself...
Matt Davis
A: 

Named Pipes can be used for this. It might be the more acceptable method with .net.You can define a service in the main application that accepts a message from the calling application. Here's a sample of the service, in vb. It calls the main app and passes a string to it, in this case, a filename. It also returns a string, but any parameters can be used here.

Public Class PicLoadService : Implements IMainAppPicLoad

Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
' do some stuff here.
LoadPic = "return string"
End Function

End Class

The setup in the calling application is a little more involved. The calling and main application can be the same application.

Imports System.Diagnostics
Imports System.ServiceModel
Imports System.IO
Imports vb = Microsoft.VisualBasic

Module MainAppLoader

Sub Main()

Dim epAddress As EndpointAddress
Dim Client As picClient
Dim s As String
Dim loadFile As String
Dim procs() As Process
Dim processName As String = "MainApp"

loadFile = "" ' filename to load

procs = Process.GetProcessesByName(processName)

If UBound(procs) >= 0 Then
  epAddress = New EndpointAddress("net.pipe://localhost/MainAppPicLoad")
  Client = New picClient(New NetNamedPipeBinding, epAddress)
  s = Client.LoadPic(loadFile)
End If

End Sub

<System.Diagnostics.DebuggerStepThroughAttribute(), _
 System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Partial Public Class picClient
    Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad)
    Implements IMainAppPicLoad

    Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
        MyBase.New(binding, remoteAddress)
    End Sub

    Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
        Return MyBase.Channel.LoadPic(fName)
    End Function

End Class

' from here down was auto generated by svcutil.
' svcutil.exe /language:vb /out:generatedProxy.vb /config:app.config http://localhost:8000/MainAppPicLoad
' Some has been simplified after auto code generation.
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0"), _
 System.ServiceModel.ServiceContractAttribute(ConfigurationName:="IMainAppPicLoad")> _
Public Interface IMainAppPicLoad
  <System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/IMainAppPicLoad/LoadPic", ReplyAction:="http://tempuri.org/IMainAppPicLoad/LoadPicResponse")&gt; _
  Function LoadPic(ByVal fName As String) As String
End Interface

<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Public Interface IMainAppPicLoadChannel
  Inherits IMainAppPicLoad, System.ServiceModel.IClientChannel
End Interface

<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Partial Public Class IMainAppPicLoadClient
  Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad)
  Implements IMainAppPicLoad

  Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
    MyBase.New(binding, remoteAddress)
  End Sub

  Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
    Return MyBase.Channel.LoadPic(fName)
  End Function
End Class

End Module

<ServiceContract()> Public Interface IMainAppPicLoad
<OperationContract()> Function LoadPic(ByVal fName As String) As String
End Interface
xpda
For the sake of context, this example uses WCF to exchange data via the named pipe
Matt Davis
I investigated other means of inter-process communication and indeed, this was one of the alternatives, but I believe using simple Widows messages is ligher and simpler.
chitza
+1  A: 

For other people wanting to achieve this, I'm posting below my implementation, using Matt Davis' solution.

In Program.cs

static class Program
{
    #region Dll Imports
    public const int HWND_BROADCAST = 0xFFFF;

    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
    #endregion Dll Imports

    public static readonly int WM_ACTIVATEAPP = RegisterWindowMessage("WM_ACTIVATEAPP");

    [STAThread]
    static void Main()
    {
        bool createdNew = true;
        //by creating a mutex, the next application instance will detect it
        //and the code will flow through the "else" branch 
        using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew))//make sure it's an unique identifier (a GUID would be better)
        {
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
            }
            else
            {
                //we tried to create a mutex, but there's already one (createdNew = false - another app created it before)
                //so there's another instance of this application running
                Process currentProcess = Process.GetCurrentProcess();

                //get the process that has the same name as the current one but a different ID
                foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
                {
                    if (process.Id != currentProcess.Id)
                    {
                        IntPtr handle = process.MainWindowHandle;

                        //if the handle is non-zero then the main window is visible (but maybe somewhere in the background, that's the reason the user started a new instance)
                        //so just bring the window to front
                        if (handle != IntPtr.Zero)
                            SetForegroundWindow(handle);
                        else
                            //tough luck, can't activate the window, it's not visible and we can't get its handle
                            //so instead notify the process that it has to show it's window
                            PostMessage((IntPtr)HWND_BROADCAST, WM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);//this message will be sent to MainForm

                        break;
                    }
                }
            }
        }
    }
}

In MainForm.cs

protected override void WndProc(ref Message m)
{
 base.WndProc(ref m);
            //someone (another process) said that we should show the window (WM_ACTIVATEAPP)
 if (m.Msg == Program.WM_ACTIVATEAPP)
  this.Show();
}
chitza
It would be valuable to me (for others ?) if you put a few comments in your code that describe what you are doing in the enumeration of processes. thanks,
BillW
I've added the comments, sorry for the long delay.
chitza
Many thanks for adding comments !
BillW
@chitza, tested your solution in VS2010b2 on FrameWork 3.5, 4.0 : I can't find any case that triggers the WndProc. If I launch one app instance, then minimize, launching another app instance leaves the current instance minimized. If the window is not minimized, it works as you would expect, bringing the one-and-only instance to front. I did find that if I moved the call to the WndProc to after the call to 'SetForeGroundWindow (essentially eliminating the else case), that allowed me, in the WndProc intercept code in the MainForm, to test for WindowState = Minimized and do the right thing.
BillW
I didn't think about the form being minimized. The code only handles hidden windows and non-minimized background windows.However, the code should be rewritten and only post the message in Program.cs, all window activation/restore code should be in WndProc (MainForm.cs). I'll rewrite it during week-end and repost it.
chitza