tags:

views:

242

answers:

3

Here is the scenario, i opened my application then the tray icon shows, if i double-clicked the tray icon, the main interface will be shown. if i open again my application, the main interface should be given focus or if it is not yet shown then it should be shown instead of opening another instance of my app.

here is how my code looks like:

//Program.cs
.....

if(myAppIsNotRunningYet) //the program has not been open yet
{
MyTray = new MyTray();
Application.Run();
}

else //the program is already on the tray
{
//the code to give focus to the mainForm or open it up if not yet open
}

//MyTray.cs
.....
public MyTray()
{
notifyIcon = new NotifyIcon();
....
notifyIcon.Visible = true;
}

private void notifyIcon_DoubleClick(object sender, EventArgs e)
{
MainForm mainForm = new MainForm();
mainForm.ShowDialog();
}
+3  A: 

EDIT: OK, It seems that to override WndProc correctly, you have to use a form which is/has been visible. So below is a different solution using a MessageFilter. This does work, so hopefully you are good to go from here!

 internal sealed class Program
 { 
  /// <summary>
  /// Program entry point.
  /// </summary>
  [STAThread]
  public static void Main(string[] args)
  {
    bool newMutex;
    System.Threading.Mutex mutex = new System.Threading.Mutex(true, "{9F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}", out newMutex);

     // Attempt aquire the mutex
     if(newMutex)
     {
      // If we are able to aquire the mutex, it means the application is not running.
         Application.EnableVisualStyles();
         Application.SetCompatibleTextRenderingDefault(false);

         // Create the new tray icon
         MyTray myTray = new MyTray();

         Application.AddMessageFilter(myTray);
         Application.Run();

         // Release the mutex on exit
         mutex.ReleaseMutex();
     }
     else
     {
        // If the aquire attempt fails, the application is already running
        // so we broadcast a windows message to tell it to wake up.
         NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_SHOWME, IntPtr.Zero, IntPtr.Zero);
     }
     }
 }
}

internal class NativeMethods
 {
     public const int HWND_BROADCAST = 0xffff;
     public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");

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

     [DllImport("user32")]     
     public static extern int RegisterWindowMessage(string message);
 }

 public class MyTray : IMessageFilter
 {
  private NotifyIcon notifyIcon = new NotifyIcon();
  private Form myForm = new Form();

  public MyTray()
  {
     this.notifyIcon.Icon = System.Drawing.Icon.FromHandle(new System.Drawing.Bitmap(16,16).GetHicon());
     this.notifyIcon.Visible = true;
     this.notifyIcon.DoubleClick += delegate(object sender, EventArgs e) { ShowForm(); };
  }

     void ShowForm()
     {
       this.notifyIcon.Visible = false;       
       this.myForm.ShowDialog(); 
       this.notifyIcon.Visible = true;
      }

    public bool PreFilterMessage(ref Message m)
    {
       // If the message is the 'show me' message, then hide the icon and show the form.
       if(m.Msg == NativeMethods.WM_SHOWME)
       {
         if (!this.myForm.Visible)
         {
       ShowForm();
       return true; // Filter the message
         }
       }

       return false; // Forward the message
    }
 }


EDIT: I put together a sample which is closer to your scenario:

 internal sealed class Program
 {
  static System.Threading.Mutex mutex = new System.Threading.Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");

  /// <summary>
  /// Program entry point.
  /// </summary>
  [STAThread]
  private static void Main(string[] args)
  {
   // Attempt aquire the mutex
         if(mutex.WaitOne(TimeSpan.Zero, true))
         {
          // If we are able to aquire the mutex, it means the application is not running.
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);

             // Create the new tray icon
             MyTray myTray = new MyTray();

             Application.Run();

             // Release the mutex on exit
             mutex.ReleaseMutex();
         }
         else
         {
            // If the aquire attempt fails, the application is already running
            // so we broadcast a windows message to tell it to wake up.
             NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_SHOWME, IntPtr.Zero, IntPtr.Zero);
         }
     }
 }

 internal class NativeMethods
 {
     public const int HWND_BROADCAST = 0xffff;
     public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");

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

     [DllImport("user32")]     
     public static extern int RegisterWindowMessage(string message);
 }

 public class MyTray : Control
 {
  private NotifyIcon notifyIcon = new NotifyIcon();

  public MyTray()
  {
   this.notifyIcon.Visible = true;
  }

  /// <summary>
  /// This method listens to all windows messages either broadcast or sent to this control
  /// </summary>
  protected override void WndProc(ref Message m)
  {
   // If the message is the 'show me' message, then hide the icon and show the form.
   if(m.Msg == NativeMethods.WM_SHOWME)
   {
    this.notifyIcon.Visible = false;
    using (Form mainForm = new Form())
    {
     mainForm.ShowDialog();
     this.notifyIcon.Visible = true;
    }  
   }
   else
   {
    base.WndProc(ref m);    
   }   
  }
 }


EDIT: I found an example in C# that combines the mutex and windows message:

C# .NET Single Instance Application



A mutex is probably the best way to go. Combine this with a custom windows message which your application listens for to bring itself into focus (see VB.NET, VB6 and C# Interprocess communication via Window Messaging).

Check out this sample: C# - Single Application Instance

Philip Wallace
the codes in the link are vb codes i'm not a vb programmer so i can't understand it sorry...
murasaki5
Take a look at the new link I posted. It has everything I was trying to describe...
Philip Wallace
tried it but does not work since my main method only has Application.Run() and not Application.Run(new Form()). Like i said, it is my notifyicon that opens the MainForm.
murasaki5
Have MyTray inherit from NotifyIcon. Then you can listen for Windows messages and override OnDoubleClick to open your MainForm.
Philip Wallace
what do you mean listen for messages and override OnDoubleClick? Does notifyicon have such method? Isn't the notifyicon that raises the DoubleClick event and not the Mainform?
murasaki5
Forget that last comment, NotifyIcon is sealed.
Philip Wallace
I added some more sample code which is closer to what you are trying to achieve.
Philip Wallace
the code you gave doesn't seem to work... when i reopen the app nothing happens, the tray is still there.
murasaki5
Do you put some breakpoints in, do any kind of debugging? I don't mind helping you - but some work on your side would be nice too!
Philip Wallace
New solution added. This one uses IMessageFilter instead.
Philip Wallace
Yes i've been debugging the program by stepping into each line of codes but it just passes through the else part of program.cs going to NativeMethods.PostMessage then goes directly to the close curly brace then end. It doesn't seem to step into the Windproc method.
murasaki5
Take a look at the updated code sample. This works...
Philip Wallace
Yes! I just used the new sample and it works. Thank you very much! The IMessageFilter did the trick.
murasaki5
A: 

I don't think you can raise events on another application's windows (even if they are the same executable file).

The way I would solve it though is to use some IPC mechanism to tell the running instance to open up the main window. The same IPC mechanism can also be used to determine whether another instance is running or not.

Isak Savo
A: 

You can also take a look at the SwitchToThisWindow function.

When a second instance of the application starts you can just call that function with the main window handle of the first instance. You can get the main window handle with the Process.MainWindowHandle property.

João Angelo
Will this work? Until the user double clicks, there is no window...
Philip Wallace
You have a point there, I never tried it with an application that minimizes to the tray. If the window is just hidden it could still work, but no guarantees...
João Angelo