Click here to Skip to main content
15,861,168 members
Articles / Programming Languages / C#
Article

Window Tray Minimizer

Rate me:
Please Sign up or sign in to vote.
4.91/5 (60 votes)
26 Oct 2007CPOL6 min read 177.6K   4.1K   203   43
An article showing how to minimize any Window to the system tray
Image 1
Image 2

Contents

Introduction

The program presented here allows you to minimize any open Window to the system tray with just one click. After reading this article you will know how to call unmanaged code from a .NET application using P/Invoke.

Background

All the functionality of this application is achieved using Windows API functions so you should be familiar with basic winapi programming. Consequently you should know what P/Invoke is and how it works.

Basic P/Invoke

P/Invoke or Platform Invoke allows you to call unmanaged functions from managed code. To do that you need to declare a method and specify the name of the DLL file which contains the method using DllImport attribute. For example here is a declaration needed to call ShowWindow function from a C# application:

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

extern keyword is used to indicate that the method is implemented externally. Perhaps the most difficult task with P/Invoke is to know how to substitute unmanaged types with managed types. This is a great website which has lots of winapi function declarations. There are many tutorials about P/Invoke so I won't go into more details here. See references for the links.

How the Program Works

When you run the application it hides the main form and an icon is shown in the system tray. When you right-click the icon a list of all visible Windows is displayed. If you click one of them then that Window will disappear and a new icon will appear in the system tray. The icon is the same as the icon of the executable that launched the Window. Clicking on the icon will bring the Window back. Apart from this there are two commands available to minimize all Windows to tray or to show all minimized Windows. Both commands have hotkeys associated with them and they can be changed from the options Window. To open the options Window just double-click the main icon. Other options include adding the program to the start-up program list and ignoring Windows without a title.

Code Behind the Application

Enumerating Windows

When you right-click the icon a list of all visible Windows appears. To enumerate all Windows you should call winapi function called EnumWindows. The function takes two parameters. The first one is a pointer to a callback function. The code snippet below shows how this function works:

C#
//This function call EnumWindows and passes delegate to the callback function
private void getwindows()
{
   winapi.EnumWindowsProc callback = new winapi.EnumWindowsProc(enumwindows);
   winapi.EnumWindows(callback, 0);
}

private bool enumwindows(IntPtr hWnd, int lParam)
{
   //Ignore invisible window
   if (!winapi.IsWindowVisible(hWnd))
    return true;

   StringBuilder title = new StringBuilder(256);
   winapi.GetWindowText(hWnd, title, 256);

   if (string.IsNullOrEmpty(title.ToString())&& set.IgnoreTitle)
   {
    return true;
   }

   //Ignore statusbar and add other windows to the list
   if (title.Length != 0 || (title.Length == 0 & hWnd != winapi.statusbar))
   {
    windows.Add(new window(hWnd, title.ToString(), winapi.IsIconic(hWnd),
                                        winapi.IsZoomed(hWnd)));
   }

   return true;
}

window is a class that has just four fields:

  • hwnd - to uniquely identify the window
  • title - the title of the window
  • isminimized and ismaximized store information about the state of the window

windows is an instance of the List<window> class.

Hiding the Window...

When a user clicks on one of the Windows we need to hide it. ShowWindow() API is used to achieve this. Just pass the handle to the Window as a first parameter and SW_HIDE constant (which is equal to zero) as a second parameter.

C#
private void showwindow(window wnd, bool hide)
{
   winapi.ShowWindow(wnd.handle, state(wnd, hide));
}

private int state(window wd, bool hide)
{
   if (hide)
   {
    return winapi.SW_HIDE;
   }

   if (wd.isminimzed)
   {
    return winapi.SW_MINIMIZE;
   }

   if (wd.ismaximized)
   {
    return winapi.SW_MAXIMIZE;
   }
    return winapi.SW_SHOW;
}

... and Displaying Icon

The Window is hidden so we need to retrieve the icon of the executable file that created this Window. To achieve this we need to get the path of the executable first. These are the required steps:

  1. Get the process id that created the specified Window using GetWindowThreadProcessId() function
  2. Get a handle of the process by OpenProcess() function
  3. Get the executable path by calling GetModuleFileNameEx() function

These steps were suggested by Mark Salsbery here: retrieve executable path by hwnd. All these functions are winapi functions imported by dllimport attribute. Here is the actual implementation ported to C#.

C#
private string pathfromhwnd(IntPtr hwnd)
{
   uint dwProcessId;

   //Get the process id
   winapi.GetWindowThreadProcessId(hwnd, out dwProcessId);

   //Get the handle of the process
IntPtr hProcess = winapi.OpenProcess(
 winapi.ProcessAccessFlags.VMRead | winapi.ProcessAccessFlags.QueryInformation,
                            false, dwProcessId);

   //Get the executable path
   StringBuilder path = new StringBuilder(1024);
   winapi.GetModuleFileNameEx(hProcess, IntPtr.Zero, path, 1024);

   winapi.CloseHandle(hProcess);
   return path.ToString();
}

To retrieve the path to the executable file you can also use GetProcessImageFileName API but it returns a path in device form and works on XP and higher. GetWindowModuleFileName seemed to be another option but it works only if the specified Window is owned by the calling process.

Now when we have the path to the executable, we can extract the icon that is displayed by explorer using the SHGetFileInfo() function. You need to pass the path of the executable and an instance of SHFILEINFO structure that will receive the result. After you have retrieved a handle to the icon you must call DestoyIcon() API to prevent a memory leak.

C#
private Icon Iconfrompath(string path)
{
   System.Drawing.Icon icon = null;

   if (System.IO.File.Exists(path))
   {
    //Retrieve SHFILEINFO type variable
    winapi.SHFILEINFO info = new winapi.SHFILEINFO();
    winapi.SHGetFileInfo(path, 0, ref info, (uint)Marshal.SizeOf(info),
                winapi.SHGFI_ICON | winapi.SHGFI_SMALLICON);

    //Create icon and destroy the handle
    System.Drawing.Icon temp = System.Drawing.Icon.FromHandle(info.hIcon);
    icon = (System.Drawing.Icon)temp.Clone();
    winapi.DestroyIcon(temp.Handle);
   }

   return icon;
}

At this point we have the necessary icon so we just create and show new NotifyIcon and setup its click event handler. When the click event occurs, the corresponding Window is shown and NotifyIcon is destroyed.

The Click

When an icon is clicked we need to display the corresponding Window. First check if there exists a Window with the specified handle and then call ShowWindow() again. This time the second parameter depends on the state of the window. If the Window was maximized (minimized) while hiding it, it will be shown maximized (minimized).

C#
void tray_Click(object sender, EventArgs e)
{
   NotifyIcon tray = sender as NotifyIcon;
   window wnd = tray.Tag as window;
   if (winapi.IsWindow(wnd.handle))
   {
    showwindow(wnd, false);
   }
   else
    MessageBox.Show("Window does not exist");

   //Don't forget to remove event handler.
   //Otherwise GC won't collect the notifyicon.
   tray.Click -= new EventHandler(tray_Click);
   tray.Dispose();
}

Hotkeys

This program allows to customize hotkeys for two commands: 'all to tray' and 'show all'. To add hotkey functionality to your application, first you need to register it. RegisterHotKey() is a function that is responsible for doing it. It takes four parameters:

  1. Handle to the window that will receive WM_HOTKEY messages generated by the hot key
  2. ID of the hotkey
  3. Combination of modifier keys (Alt, Ctrl, Shift etc.)
  4. The key itself that should be pressed. The code below registers hotkey for 'All to tray' command
C#
if (mod > 0 && key > 0)
{
   winapi.RegisterHotKey(this.Handle, 1729, mod, 64 + key);
}

To handle the WM_HOTKEY message generated by the hotkey in a .NET application you should override WndProc function which processes Windows messages. Don't forget to call WndProc method of the base class so that other messages get handled.

C#
protected override void WndProc(ref Message m)
{
   switch (m.Msg)
   {
      //We are interested only in WM_HOTKEY message
      case winapi.WM_HOTKEY:
      //m.WParam is the ID we used when registering the hotkey
      ProcessHotkey(m.WParam);
      break;
   }

   //Pass message to base class
   base.WndProc(ref m);
}

private void ProcessHotkey(IntPtr wparam)
{
   if (wparam.ToInt32() == 1729)
   {
      alltray_Click(null, null);
   }

   if (wparam.ToInt32() == 1730)
   {
      showall();
   }
}

When the application exits or hotkey changes we don't need the existing hotkey any more so we should unregister it. It is easier then registering the hotkey and is achieved by calling UnregisterHotKey() function. Just pass the handle of the window and ID of the hotkey:

C#
if (mod > 0 && key > 0)
{
   winapi.UnregisterHotKey(this.Handle, 1729);
}

Managing Start-up

You can add the program to start-up from the options Window. To add program to start-up you need to navigate to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run key, create a new string and set its value equal to the application's path. Removing the program from start-up is easier: you just remove the value. The code snippet below shows how to do it:

C#
private void startup(bool add)
{
   isinstartup = add;
   RegistryKey key = Registry.CurrentUser.OpenSubKey(
              @"Software\Microsoft\Windows\CurrentVersion\Run", true);
   if (add)
   {
    //Surround path with " " to make sure that there are no problems
    //if path contains spaces.
    key.SetValue("Tray minimizer", "\"" + Application.ExecutablePath + "\"");
   }
   else
    key.DeleteValue("Tray minimizer");

   key.Close();
}

Making the Application Single-instance

If you try to launch the second instance of the application you will get a message box saying that it is already running. This is achieved using mutex class. Mutex allows to share resources between threads. When the first instance of the program is launched it creates a new mutex. When a second instance is launched it checks the existence of the mutex. If it exists then it exits. When the first instance quits it releases the existing mutex.

C#
static void Main()
{
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);
   Mutex mt = null;

   //Try to open existing mutex
   try
   {
    mt = Mutex.OpenExisting("Tray minimizer");
   }
   catch (WaitHandleCannotBeOpenedException)
   {

   }

   if (mt == null)
   {
    //If the mutex doesn't exist create it and launch the application.
    mt = new Mutex(true, "Tray minimizer");
    Application.Run(new Form1());

    //Tell GC not to destroy mutex until the application is running and
    //release the mutex when application exits.
    GC.KeepAlive(mt);
    mt.ReleaseMutex();
   }
   else
   {
    //The mutex existed so exit
    mt.Close();
    MessageBox.Show("Application already running");
    Application.Exit();
   }
}

Points of Interest

This application shows how powerful winapi is and how it can be used from a .NET application to achieve effects that would be otherwise impossible.

References

History

  • July 2, 2007 - Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Georgia Georgia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionFork for automatic action Pin
Gluups23-Jan-19 6:54
Gluups23-Jan-19 6:54 
AnswerRe: Fork for automatic action Pin
Gluups19-Feb-19 19:21
Gluups19-Feb-19 19:21 
QuestionNice. What about preventing from closing ? Pin
Gluups29-Oct-18 0:05
Gluups29-Oct-18 0:05 
General:) Pin
L3th4l29-Dec-13 12:16
L3th4l29-Dec-13 12:16 
GeneralMy vote of 5 Pin
Abhishek Pant24-Oct-12 3:36
professionalAbhishek Pant24-Oct-12 3:36 
GeneralThx Pin
iHUPPz Rwt7-Sep-12 10:39
iHUPPz Rwt7-Sep-12 10:39 
Questioncode C# Pin
sajad_zero28-Jul-12 22:03
sajad_zero28-Jul-12 22:03 
very good, thanks
GeneralMy vote of 5 Pin
dapuzz21-Jun-12 23:12
dapuzz21-Jun-12 23:12 
GeneralThanks Pin
diegogomez869-Feb-12 10:17
diegogomez869-Feb-12 10:17 
General"Start" - Windows 7 specific window. Pin
Vercas29-Sep-10 5:59
Vercas29-Sep-10 5:59 
GeneralPossible Wrong URL Link Pin
hakimgada29-Nov-09 23:13
hakimgada29-Nov-09 23:13 
GeneralThanx Pin
mr_sawari26-Sep-09 6:33
mr_sawari26-Sep-09 6:33 
GeneralAwesome! Pin
shervin186-Oct-08 22:43
shervin186-Oct-08 22:43 
GeneralRe: Awesome! Pin
Giorgi Dalakishvili7-Oct-08 0:09
mentorGiorgi Dalakishvili7-Oct-08 0:09 
Questionhotkey question Pin
svh198523-Jun-08 4:39
svh198523-Jun-08 4:39 
AnswerRe: hotkey question Pin
Giorgi Dalakishvili23-Jun-08 8:56
mentorGiorgi Dalakishvili23-Jun-08 8:56 
GeneralThanks Sooooo Much Pin
Xmen Real 1-Jan-08 17:17
professional Xmen Real 1-Jan-08 17:17 
GeneralGooooooooooooooooooood :) Pin
eusta30-Nov-07 5:10
eusta30-Nov-07 5:10 
GeneralRe: Gooooooooooooooooooood :) Pin
Giorgi Dalakishvili30-Nov-07 7:12
mentorGiorgi Dalakishvili30-Nov-07 7:12 
GeneralNice work Pin
Sandeep Akhare30-Oct-07 5:17
Sandeep Akhare30-Oct-07 5:17 
GeneralRe: Nice work Pin
Giorgi Dalakishvili30-Oct-07 9:05
mentorGiorgi Dalakishvili30-Oct-07 9:05 
QuestionQuestions Pin
nhc198729-Sep-07 22:29
nhc198729-Sep-07 22:29 
AnswerRe: Questions [modified] Pin
Giorgi Dalakishvili10-Oct-07 10:20
mentorGiorgi Dalakishvili10-Oct-07 10:20 
GeneralUseful tool Pin
Juraj Borza6-Aug-07 20:28
Juraj Borza6-Aug-07 20:28 
GeneralRe: Useful tool Pin
Giorgi Dalakishvili7-Aug-07 0:00
mentorGiorgi Dalakishvili7-Aug-07 0:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.