Friday, May 10, 2013

How to have a single instance app that minimizes to the system tray, as well as restores from the tray and any duplicate instance bring the original to the foreground

For a recent project, I wanted to minimize my application to the system tray, as to not inhibit the precious toolbar space for an application that is accessed at the beginning of the workday and the end of it. I also wanted to be able to restore the application from the system tray, as well as not allow more than 1 instance of the application to be opened and if the user attempted to do that, to bring the original application into the foreground before closing the duplicate app.

This proved to be much more convoluted and difficult then I had originally expected. I found a ton of different suggestions on the internet as to how to do this, but nothing straight forward and plug and play, so this blog post will be all about adding code into an existing project that is plug and play as well as giving a simplistic example application to cherry pick from or build off of. I tried to be as detailed as possible, if I missed a step please comment so I can explain a step or add a step into this.

Create a form in Visual studio, go to the toolbox and add a notify icon to the form1, then choose your icon and the icon name. You'll need to modify the third entry below named this.nofityIcon1.Icon to whatever you name your icon by going into the solution explorer, right clicking on the project name, clicking on the resources menu tab and then add a resource/add existing file. At this point that file will now reside in your resources and be accessible via Properties.Resources .

You can add the following to the Form1_Load method if you would like, it is optional

this.notifyIcon1.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info; //Shows the info icon so the user doesn't thing there is an error.
                this.notifyIcon1.BalloonTipText = "[Balloon Text when Minimized]";
                this.notifyIcon1.BalloonTipTitle = "[Balloon Title when Minimized]";
                this.notifyIcon1.Icon = ((System.Drawing.Icon)(Properties.Resources.alarm_clock_face_s)); //The tray icon to use
                this.notifyIcon1.Text = "[ApplicationName] application, double click to restore program";


Inside of Form1() you'll need to capture 2 events via the event handlers and have two corresponding functions to handle those events as well as add a new warning message form. Under solution explorer, right click on the solution, then add, then windows form. Name the form (I called it the default name of form2.cs) and then add a label and a checkbox to it for this code, or anything else you would like to customize it with.

this.Resize += new EventHandler(form1_Resize);
            notifyIcon1.DoubleClick += new EventHandler(notifyIcon1_DoubleClick);



       private void form1_Resize(object sender, EventArgs e)
        {
            if (FormWindowState.Minimized == WindowState)
            {
                Form2 warningForm = new Form2();
                try
                {
                    RegistryKey key = Registry.CurrentUser.OpenSubKey("Software", true);
                    if (key.OpenSubKey("TimeKeeper") == null)
                    {
                        key.CreateSubKey("TimeKeeper");
                    }

                    RegistryKey subKey = key.OpenSubKey([KeyName], true);
                    string regValue = subKey.GetValue("minimizeWarningHide").ToString();

                    if (regValue == "true")
                    {
                        // continue on
                    }
                    else
                    {
                        warningForm.Show();
                    }


                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + System.Environment.NewLine + ex.Source + System.Environment.NewLine + ex.TargetSite + System.Environment.NewLine + ex.StackTrace + System.Environment.NewLine + System.Environment.NewLine);
                }

                this.Hide();
            
            }


        }
    }
}


      private void notifyIcon1_DoubleClick(object sender,System.EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
        }

In the new form2 you'll want to paste the following code after adding checkBox1 (which I renamed to ckbxMinimizeWarning), again be sure to change the [KeyName] value to your registry key name that you've chosen. You also want to add the include using Microsoft.Win32; to the top. You'll also want to disable the minimizebox and maximizebox buttons for this warning message, they aren't necessary and could confuse your user.


private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            try
            {
                RegistryKey key = Registry.CurrentUser.OpenSubKey("Software", true);
                if (key.OpenSubKey([KeyName]) == null)
                {
                    key.CreateSubKey([KeyName]);
                }

                RegistryKey subKey = key.OpenSubKey([KeyName], true);

                if (ckbxMinimizeWarning.Checked == true)
                {
                    subKey.SetValue("minimizeWarningHide", "true", RegistryValueKind.String);
                }

                if (ckbxMinimizeWarning.Checked == false)
                {
                    subKey.SetValue("minimizeWarningHide", "false", RegistryValueKind.String);
                }
            }
            catch (Exception)
            {
              
                throw;
            }
        }


At this point you'll want to put this code into your Form1_Load() method, ensure to change [KeyName] to the name of the key you want to house your registry settings in, inside of hkcu\software. Also don't forget to add the registry include, using Microsoft.Win32; at the top of your project.


RegistryKey key = Registry.CurrentUser.OpenSubKey("Software", true);
                if (key.OpenSubKey([KeyName]) == null)
                {
                    key.CreateSubKey([KeyName]);
                }

                RegistryKey subKey = key.OpenSubKey([KeyName], true);

                string[] subKeys = subKey.GetValueNames();

                if (!(subKeys.Contains("minimizeWarningHide")))
                {
                    subKey.SetValue("minimizeWarningHide", "false", RegistryValueKind.String);
                }


Lastly go into the program.cs code and we will use pin invoke.

The first thing to do is add the pininvoke include as well as threading include at the top of program.cs
using System.Runtime.InteropServices; and using System.Threading; This part of the code allows you to identify existing windows and to bring a window to the foreground as needed. Be sure to change the Mutex value of ExampleMinimizeToTrayProject per your own needs. This is a unique value and needs to be unique for each application you write, or else the applications will all think they are the same application (no matter the code) when the open. Mutex stands for Mutually Exclusive.

Here is the program.cs code


static class Program
    {
       
<summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            if (IsSingleInstance())
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            else
            {
                bringToFront("Time Keeper");
            }
        }

        static private Mutex _instanceMutex = null;

        [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd,
            IntPtr ProcessId);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("kernel32.dll")]
        public static extern uint GetCurrentThreadId();

        [DllImport("user32.dll")]
        public static extern bool AttachThreadInput(uint idAttach,
            uint idAttachTo, bool fAttach);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool BringWindowToTop(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool BringWindowToTop(HandleRef hWnd);

        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);

        public static void bringToFront(string title)
        {
            IntPtr hWnd = FindWindow(null, title);
            uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
            uint appThread = GetCurrentThreadId();
            const uint SW_SHOW = 5;

            if (foreThread != appThread)
            {
                AttachThreadInput(foreThread, appThread, true);
                BringWindowToTop(hWnd);
                ShowWindow(hWnd, SW_SHOW);
                AttachThreadInput(foreThread, appThread, false);
            }
        }

        static Mutex _m;
        static bool IsSingleInstance()
        {
            try
            {
                // Try to open existing mutex.
                Mutex.OpenExisting("ExampleMinimizeToTrayProject");
            }
            catch
            {
                // If exception occurred, there is no such mutex.
                Program._m = new Mutex(true, "ExampleMinimizeToTrayProject");

                // Only one instance.
                return true;
            }
            // More than one instance.
            return false;
        }
    }
Then add an image to form2 by going to the tool box and adding a picturebox and browsing to an image under the solution explorer for the picturebox, showing the system try and what the icon looks like placed in it by importing it. Then add the label (if you would like) as well as text to the label on form2 explaining to them the minimizing process. I use the following text

<blockquote>
This program will be minimized to the system tray by the clock as pictured above. To restore it, look for the alarm clock icon inside of the system tray icon list and double click it.</blockquote>
Also add the following text to the form2 checkbox explaining its usage

<blockquote>
Select this not to see this warning again.</blockquote>
Lastly, make sure the bringToFront("[texthere]"); in program.cs is looking for the name of your form, whatever text name you've given it in your project. In this project you'll see it is named ExampleMinimizeToTrayProject.


Project for download: (the second link should be a direct link)

http://www.filefactory.com/file/30lkeoyds5pj

http://s37.filefactory.com/dl/f/30lkeoyds5pj//b/3/h/4e0c1626bd0ca3ff8f36f7f7/m/e6c5d3cbdde8f5a9fc3377a40b53d308/n/ExampleMinimizeToTrayProject.zip




References I used:

http://www.codeproject.com/Forums/1649/Csharp.aspx

http://www.codeguru.com/csharp/.net/net_general/systeminformation/article.php/c6933/Placing-Your-C-Application-in-the-System-Tray.htm

http://tech.pro/tutorial/928/how-to-minimize-an-application-to-the-taskbar-tray-in-csharp

http://stackoverflow.com/questions/9168405/mutex-do-not-work-with-two-processes-running



keywords:

single instance app application
duplicate
system tray minimize restore from tray

No comments:

Post a Comment