Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / Windows Forms

Sort Windows on the Windows Taskbar or Minimize them to System Tray

Rate me:
Please Sign up or sign in to vote.
4.78/5 (14 votes)
12 Feb 2011CPOL8 min read 46.5K   1.4K   28   16
TaskbarSorterXP is a small utility which allows the user to sort the windows on the Windows Taskbar. Additionally windows can be minimized to system tray

Table of Contents

  1. Introduction
    • What this project will demonstrate
    • Summary
  2. Background
  3. Using the code
    • Retrieving all relevant windows
    • Hiding and Showing windows
    • Sorting the windows
    • Set transparency of a window
    • Retrieve icon(s) from an executable
    • Minimize a window to system tray
  4. Make a window stay always on top
  5. Points of Interest
  6. History

Introduction

What this Project will Demonstrate

  • Calling unmanaged code (P/Invoke, API call)
  • Showing/Hiding/Activating application windows with unmanaged code
  • Set transparency of a window with unmanaged code
  • Minimize a window to System Tray with NotifyIcon
  • Retrieve the application icon(s) from an executable
  • Use of Delegates to realize callback functions
  • Moving ListViewItems within a ListView
  • Raised System.Windows.Forms.Panel with P-Invocation
  • Saving/Retrieving User Settings
  • ContextMenuStrip bound to a ListView

Summary

TaskbarSorterXP is a small utility which allows the user to sort the windows on the Windows Taskbar.

Windows XP does not allow to sort the windows on the taskbar like it's possible in Windows 7. Personally, I open the applications always in the same order to have them "sorted" on the taskbar (e.g. Outlook, then Explorer, then Browser, ...). But sometimes you have to close and reopen them - so the taskbar is inevitably unsorted.

Windows 7 however supports reordering the windows. But I was too lazy to do this manually then and when.

What I was looking for, was a one-click solution to sort the windows on the taskbar in my preferred order. This was the reason to start this project.

The solution of sorting the windows on the taskbar is simple:

  1. Hide all windows
  2. Show the windows in the sorted order

First, I was messing around with:

C#
System.Diagnostics.Process.GetProcesses()

But Process does not expose a property to set the visible state of a window. This is obviously logic because a Process does not always represent a form/window.

Luckily, I've done hiding/showing windows forms already with VBA and Win32 API calls. That's why this solution is interoperating with unmanaged code like:

C#
// <summary>
// Used to show, hide, minimize or maximize a window
// </summary>
// <param name="hWnd">handle of window to manipulate</param>
// <param name="nCmdShow">see consts below</param>
// <returns></returns>
[DllImport("user32.dll", EntryPoint="ShowWindowAsync")]
public static extern Boolean ApiShowWindowAsync(IntPtr hWnd, int nCmdShow);

Sorting the windows is done by a simple GUI. Starting the application shows a Form with a ListView containing all currently visible windows. The user has two possibilities to sort the windows:

  1. Sorting the ListViewItems by the context menu or the Buttons
  2. Apply a predefined sort order from user settings

TaskbarSorterXP1.png

The main GUI

TaskbarSorterXP2.png

The preferences GUI with a raised Panel

All you have to specify is the executable's name.

Background

Why should one need sorting the windows on the taskbar? Well, I like to have done my computer work efficiently. So why waste time to search the Windows Explorer on the taskbar? Or the browser window? Of course, it takes only a second to check the taskbar - but how many times do you activate a window by clicking it on the taskbar?

Why should one need sorting the windows on the taskbar in ages of Windows Vista/7? Well, my personal notebook is still running Windows XP. Fast, stable, reliably.

With v1.1.0, all my needs were fulfilled. But again, it was too painful (for me) to run TaskBarSorterXP from program start menu again and again. So I've added a NofifyIcon and was now able to run TaskBarSorterXP from the System Tray. But why just only minimize TaskBarSorterXP to the System Tray? E.g. a playing Media Player is an application you don't need staying on the taskbar. The mail client as well. That's why I've added in v1.2.0 the possibility to minimize any window to system tray.

TaskbarSorterXP3.png

Minimized windows in the system tray

Using the Code

This solution consists of six small classes:

TaskBarSorterGUI The main GUI. Handles user interaction.
TaskBarSorterHelpers Some helper functions for the GUI, Saving/Loading Settings, ...
TaskBarSorterPreferences GUI to define preferred sort order of application windows.
Unmanaged Declarations of the unmanaged API calls.
WindowItem This class represents a window
WindowsList This class holds all (visible) windows (handles).
ListViewHelpers Helper functions for ListView concerns.

Retrieving All Relevant Windows

To retrieve (all of) the windows, I use managed code (s. Unmanaged):

C#
// <summary>
// Unmanaged function to enumerate all top-level windows on the screen.
// Returns the window of every opened window to the callback function
// </summary>
// <param name="lpEnumFunc">Pointer (callback) to a function which is 
// called for every opened window</param>
// <param name="lParam">application defined. In this context always 0</param>
// <returns></returns>
[DllImport("user32.Dll", EntryPoint = "EnumWindows")]
public static extern int ApiEnumWindows(WindowsList.WinCallBack lpEnumFunc, int lParam);

The first parameter requires a pointer to a callback function. This function is called for every opened window and passes the window handle to the callback function. Callback functions in .NET are realized by Delegates. So the next step was to implement a Delegate (s. WindowList) ...

C#
// <summary>
// Delegate for the Unmanaged.ApiEnumWindows() function.
// In .NET callback functions can be realized by delegates.
// </summary>
// <param name="hwnd">window handle passed by unmanaged function call</param>
// <param name="lParam">unused, but needed to match callback function signature</param>
// <returns></returns>
public delegate Boolean WinCallBack(int hwnd, int lParam);

... with the related function call (s. WindowList.init()) ...

C#
// retrieve the windows(-handles)
// fill property by call back function
Unmanaged.ApiEnumWindows(new WinCallBack(EnumWindowCallBack), 0);

This expression calls for every opened window a function named EnumWindowCallBack and passes the window handle. What we need now is to implement this callback function which receives a window handle. What we do with this window handle afterwards is up to us. There are plenty of other handy unmanaged functions which require a window handle (s. Unmanaged).

C#
// <summary>
// called by delegate function (s. above).
// Callback function for Unmanaged.ApiEnumWindows() which is called for
// every opened window.
// </summary>
// <param name="hwnd">window handle passed by unmanaged function call</param>
// <param name="lParam">unused, but needed to match callback function signature</param>
// <returns></returns>
// <remarks>
// v1.1.0 : use of WINDOWPLACEMENT
// </remarks>
private bool EnumWindowCallBack(int hwnd, int lParam) {
   IntPtr windowHandle = (IntPtr)hwnd;

   StringBuilder sbWindowTitle = new StringBuilder(1024);

   // get window title text
   Unmanaged.ApiGetWindowText((int)windowHandle, sbWindowTitle,
                  sbWindowTitle.Capacity);

   // handle only processes with a title
   if (sbWindowTitle.Length > 0) {

      // get the process class (don't handle 'Progman'
      StringBuilder sbProcessClass = new StringBuilder(256);
      Unmanaged.ApiGetClassName
          (hwnd, sbProcessClass, sbProcessClass.Capacity);
      String processClass = sbProcessClass.ToString();

      // is the window visible?
      Boolean isVisible = Unmanaged.ApiIsWindowVisible(windowHandle);

      // only relevant windows?
      Boolean isRelevant = false;
      if (this.ReturnOnlyRelevantWindows) {
         isRelevant = (isVisible && !processClass.Equals
          ("Progman", StringComparison.CurrentCultureIgnoreCase));
      } else {
         isRelevant = true;
      }

      if (isRelevant) {
         // determine window size and position (just because)
         Unmanaged.RECT r = new Unmanaged.RECT();
         Unmanaged.ApiGetWindowRect(windowHandle, ref r);

         // determine window's appearance
         Unmanaged.WINDOWPLACEMENT windowPlacement =
                  new Unmanaged.WINDOWPLACEMENT();
         windowPlacement.length =
            System.Runtime.InteropServices.Marshal.SizeOf(windowPlacement);
         Unmanaged.ApiGetWindowPlacement(hwnd, ref windowPlacement);

         // create new WindowItem
         WindowItem wi = new WindowItem(sbWindowTitle.ToString(),
          windowHandle,
          processClass,
          isVisible,
          new Unmanaged.POINT(r.Left, r.Top),
          new Unmanaged.POINT(r.Right - r.Left, r.Bottom - r.Top)
          );
         // set additional values
         wi.WindowPlacement = windowPlacement;
         wi.WindowRect = r;

         // add to collection
         this.Windows.Add(wi);
      } else {
         // window is not relevant
      }
   } else {
      // empty window titles are not of any interest (i believe ...)
   }
   return true;
}

What my callback function does is simple:

  1. I get (with unmanaged function call) the window title text, because only windows with a window text are relevant (I believe...)
  2. I get (with unmanaged function call) the class name because 'Progman' is not relevant (I believe...)
  3. I check (with unmanaged function call) if the window is visible
  4. I check if the window is relevant or not. A relevant window is:
    • visible and
    • process class is not 'Progman'
  5. If the window is relevant:
    1. Get window's position and size (just because) with unmanaged function call
    2. Get window's appearance (Maximized, Position, ...). With this information, a window can be restored correctly.
    3. Create a new WindowItem
    4. Add the WindowItem to the collection

Finally, we have a collection of 'relevant' WindowItems.

My first run of my project was without the test for 'relevant' windows. So, I simply hid all handles and then displayed all handles again. Well this was funny, because there were handles which did not belong to windows. I had to restart the my notebook because I had hundred thousand million thousand objects on my desktop ...

Hiding and Showing Windows

Hiding and/or showing a window is simply done again by a unmanaged function call (s. Unmanaged:

C#
// <summary>
// Used to show, hide, minimize or maximize a window
// </summary>
// <param name="hWnd">handle of window to manipulate</param>
// <param name="nCmdShow">see consts below</param>
// <returns></returns>
[DllImport("user32.dll", EntryPoint="ShowWindowAsync")]
public static extern Boolean ApiShowWindowAsync(IntPtr hWnd, int nCmdShow);

public const int SW_HIDE = 0;
public const int SW_SHOWNORMAL = 1;
public const int SW_SHOWMINIMIZED = 2;
public const int SW_SHOWMAXIMIZED = 3;
public const int SW_SHOWNOACTIVATE = 4;
public const int SW_RESTORE = 9;
public const int SW_SHOWDEFAULT = 10;

Now we have a list of window handles (in our WindowItem collection) and a function to show or hide a window. So we are prepared to implement our sort logic (first hide all, then show them in sorted order).

Sorting the Windows

To sort the windows, we simply need a function which takes a collection of window handles, hides them and shows them again. Because we also need previous window placement, it's obvious our function will treat a collection of WindowItems (s. WindowsList.SortWindowsByWindowItemList()):

C#
// <summary>
// Sorts the windows on the Taskbar.
//
// In order to restore the windows on the previous place,
// WindowItem has a property WindowPlacement which is set in EnumWindowCallBack()
// </summary>
// <param name="hwndOrdered"></param>
// <see cref="WindowItem">
// <see cref="EnumWindowCallBack">
public static void SortWindowsByWindowItemList(List<windowitem> hwndOrdered) {
   // STEP 1: hide all
   foreach (WindowItem wItem in hwndOrdered) {
      // hide
      Unmanaged.ApiShowWindowAsync(wItem.WindowHandle, Unmanaged.SW_HIDE);
   }
   System.Threading.Thread.Sleep(200);

   // STEP 2: show all windows one after another
   foreach (WindowItem wItem in hwndOrdered) {
      Unmanaged.ApiSetWindowPlacement(wItem.WindowHandle.ToInt32(),
                      wItem.WindowPlacement);

      if (wItem.WindowPlacement.showCmd == Unmanaged.SW_SHOWNORMAL) {
         Unmanaged.ApiShowWindowAsync
          (wItem.WindowHandle, Unmanaged.SW_SHOWNORMAL);
      } else if (wItem.WindowPlacement.showCmd ==
              Unmanaged.SW_SHOWMINIMIZED) {
         Unmanaged.ApiShowWindowAsync
              (wItem.WindowHandle, Unmanaged.SW_SHOWMINIMIZED);
      } else if (wItem.WindowPlacement.showCmd ==
              Unmanaged.SW_SHOWMAXIMIZED) {
         Unmanaged.ApiShowWindowAsync(wItem.WindowHandle,
              Unmanaged.SW_SHOWMAXIMIZED);
      } else {
         Unmanaged.ApiShowWindowAsync
          (wItem.WindowHandle, Unmanaged.SW_SHOWNORMAL);
      }

      // give some time to display the window
      System.Threading.Thread.Sleep(200);
   }
}

If the user sorts the ListView, the WindowItems are taken from the ListViewItem.Tag and passed to the SortWindowsByWindowItemList().

C#
// <summary>
// sorts the windows on the Windows Taskbar by ListView sort order
// </summary>
private void btnSortByListView_Click(object sender, EventArgs e) {
   List<windowitem> windowsOrdered = new List<windowitem>();

   // iterate through list view items and get attached WindowItems
   foreach (ListViewItem lvItem in this.lv_Windows.Items) {
      WindowItem wItem = (WindowItem)lvItem.Tag;
      windowsOrdered.Add(wItem);
   }

   // reorder windows by a list of WindowItem
   WindowsList.SortWindowsByWindowItemList(windowsOrdered);
}

Set Transparency of a Window

The ListView context menu has an entry to set the transparency of the selected WindowItem. Setting the transparency is again done by P/Invoke (s. WindowItem.SetTransparency() and Unmanaged):

C#
// <summary>
// Sets the window transparency
// </summary>
// <param name="Alpha">255 = opaque, 0 = transparent</param>
public void SetTransparency(byte Alpha) {
   // Retrieve the extended window style.
   int extStyle = Unmanaged.ApiGetWindowLong
      (this.WindowHandle, Unmanaged.GWL_EXSTYLE);

   // Change the attribute of the specified window
   Unmanaged.ApiSetWindowLong
   (this.WindowHandle, Unmanaged.GWL_EXSTYLE, extStyle | Unmanaged.WS_EX_LAYERED);

   // Sets the opacity and transparency color key of a layered window.
   Unmanaged.ApiSetLayeredWindowAttributes
      (this.WindowHandle, 0, Alpha, Unmanaged.LWA_ALPHA);
}

Retrieve Icon(s) from an Executable

Before we can minimize a window to the system tray, we need an Icon for our NotifyIcon. The system tray icon should be identical to the window icon. Again, there are API calls to retrieve the icon from an executable:

C#
// get only 1 small and 1 large icon
[DllImport("shell32.dll", EntryPoint = "ExtractIconEx", CharSet=CharSet.Auto)]
public static extern int ApiExtractIconExSingle
  (string stExeFileName, int nIconIndex, ref IntPtr phiconLarge,
  ref IntPtr phiconSmall, int nIcons);

// get all small and all large icons
[DllImport("shell32.dll", EntryPoint= "ExtractIconEx", CharSet=CharSet.Auto)]
public static extern int ApiExtractIconExMulti
  (string stExeFileName, int nIconIndex, IntPtr[] phiconLarge,
  IntPtr[] phiconSmall, int nIcons);

The System.Drawing.Icon class has a public method ExtractAssociatedIcon() which also allows to extract an Icon - but only one. To keep this project simple, I use this method but it will not always return the same icon as window has (e.g., explorer icon). (see TaskBarSorterHelpers.GetIconFromFile(), TaskBarSorterGUI.refreshListView()).

C#
// <summary>
// retrieves 1 icon from a file.
// if the file does not contain an icon or an error occurs
// the default icon will be used
// </summary>
internal static System.Drawing.Icon GetIconFromFile
  (String fileName, System.Drawing.Icon defaultIcon) {
   System.Drawing.Icon result = null;
   try {
      result = System.Drawing.Icon.ExtractAssociatedIcon(fileName);
   } catch (Exception ex) {
      result = defaultIcon;
      MessageBox.Show(ex.Message + "\r\n" +
          ex.StackTrace, "GetIconFromFile()");
   }
   if ((result.Height == 0) || (result.Width == 0)) {
      // empty icon, use default icon
      result = defaultIcon;
   }
   return result;
}

Minimize a Window to System Tray

Minimizing a window to system tray can easily be done with a NotifyIcon (see TaskBarSorterGUI.newNotifyIcon()):

C#
// <summary>
// Minimizes the window to the system tray and
// adds a reference to a list so it can be restored on application exit
// </summary>
// <param name="wItem">WindowItem to minimize to System Tray</param>
private void newNotificationIcon(WindowItem wItem) {

   // create a notification icon
   NotifyIcon icon = new NotifyIcon();
   // get the icon from associated executable
   icon.Icon = TaskBarSorterHelpers.GetIconFromFile
          (wItem.ModulePath, this._DefaultIcon);
   // get window title for tool tip.
   icon.Text = StringHelpers.Left(wItem.WindowTitle, 50);
   icon.Visible = true;

   // add WindowItem data to NotifyIcon (see showMinimizedWindow())
   icon.Tag = wItem;

   // all NotifyIcons use the same event handler
   icon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler
              (this.showMinimizedWindow);

   // add to minimized WindowItems collection
   // see Form_Closing()
   this._minimizedWindowItems.Add(wItem);

   // hide window
   wItem.Hide();
}
  1. Create a new NotifyIcon.
  2. Add the WindowItem to the Tag property. This information is needed to restore the window again.
  3. Add a double click event handler.
  4. Add the WindowItem to collection of minimized windows. On application closing, we have to restore all windows in this collection again.
  5. Finally we hide the window.

Restoring a NotifyIcon is as simple as that:

C#
// <summary>
// Double click event handler for our minimized NotifyIcons
// </summary>
private void showMinimizedWindow(object sender, MouseEventArgs e) {
   NotifyIcon icon = (NotifyIcon)sender;
   WindowItem wItem = (WindowItem)icon.Tag;

   // show the minimized window again
   wItem.Show();

   // remove icon from taskbar
   icon.Dispose();

   // remove from minimized WindowItems collection
   this._minimizedWindowItems.Remove(wItem);
}

Make a Window Stay Always on Top

To make a window to stay always on top, I achieved again with P/Invocation (see Unmanaged<apisetwindowpos() />, <code>WindowItem.StayOnTop():

C#
// used to set a window on top
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool ApiSetWindowPos
  (IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

To keep this article tight, I'll do not post the function body (you can browse the code online, see above) of the following implementations.

Moving ListViewItems within a ListView

With the Buttons and/or the ContextMenu of the ListView, a user may move up and down a selected ListViewItem within the ListView.

  • ListViewHelpers.SwapListViewItems()
  • ListViewHelpers.MoveSelectedListViewItemDown()
  • ListViewHelpers.MoveSelectedListViewItemUp()

Raised System.Windows.Forms.Panel with P-Invocation

The ListView from the TaskBarSorterPreferences is placed in a raised Panel (just because):

  • TaskBarSorterPreferences.initGUI()
  • Unmanaged, see region GUI related APIs

Saving/Retrieving User Settings

Saving/Retrieving User Settings is done with:

  • TaskBarSorterHelpers.GetApplicationSortOrder()
  • TaskBarSorterHelpers.SetApplicationSortOrder()

ContextMenuStrip Bound to a ListView

Within the ContextMenuStrip Opening Event, one can enable/disable ToolStripMenuItem depending on the needs:

  • TaskBarSorterGUI.lvContextMenu_Opening()

Points of Interest

  • WindowsList could inherit List<>
  • Windows which do not have a window title get omitted from the sort process
  • Transparency does not work on Windows 7

History

1.0.0 05.02.2011 Initial post
1.1.0 09.02.2011 Changes:
  • Fix: Windows get restored in previous state
  • New: GUI to edit/save application sort preferences
  • Change: Use of User Settings instead of app.config
  • Change: Code refactoring
1.2.0 12.02.2011 Changes:
  • New: Minimize TaskBarSorter to system tray
  • New: Minimize any of listed windows to system tray
  • New: Set transparency of listed windows
  • New: Make a window to stay on top
  • Changes: Class WindowItem exposes some new methods

License

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


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

Comments and Discussions

 
SuggestionGreat App - but please add List Order Save/Load. Pin
Member 1571017218-Jul-22 7:46
Member 1571017218-Jul-22 7:46 
GeneralMy vote of 5 Pin
GicuPiticu14-Nov-21 9:58
GicuPiticu14-Nov-21 9:58 
QuestionFantastic!......request for 1 added feature... Pin
Member 944274919-Sep-12 14:35
Member 944274919-Sep-12 14:35 
GeneralMy vote of 5 Pin
Member 448140717-Feb-11 15:41
Member 448140717-Feb-11 15:41 
GeneralMy vote of 3 Pin
Theingi Win9-Feb-11 14:51
Theingi Win9-Feb-11 14:51 
GeneralMy vote of 5 Pin
JF20156-Feb-11 18:48
JF20156-Feb-11 18:48 
AnswerRe: My vote of 5 - Sort ListView by ContextMenu Pin
StehtimSchilf6-Feb-11 22:51
StehtimSchilf6-Feb-11 22:51 
Hi JF2015

Thx for your feedback. About sorting inside the form: The ListView has already a ContextMenu attached. I see, I didn't mentioned the ContextMenu in the article. I'll make good for it this evening.
GeneralMy vote of 5 Pin
Warrick Procter6-Feb-11 12:53
Warrick Procter6-Feb-11 12:53 
GeneralYou got my 5! Pin
luisnike196-Feb-11 5:25
luisnike196-Feb-11 5:25 
GeneralRe: You got my 5! Pin
StehtimSchilf6-Feb-11 12:09
StehtimSchilf6-Feb-11 12:09 
AnswerRe: You got my 5! Pin
StehtimSchilf8-Feb-11 17:07
StehtimSchilf8-Feb-11 17:07 
GeneralRe: You got my 5! Pin
luisnike1910-Feb-11 15:23
luisnike1910-Feb-11 15:23 
GeneralRe: You got my 5! Pin
StehtimSchilf10-Feb-11 22:40
StehtimSchilf10-Feb-11 22:40 
GeneralRe: You got my 5! Pin
luisnike1911-Feb-11 2:46
luisnike1911-Feb-11 2:46 
GeneralRe: You got my 5! Pin
StehtimSchilf12-Feb-11 13:00
StehtimSchilf12-Feb-11 13:00 
GeneralRe: You got my 5! Pin
luisnike1913-Feb-11 9:58
luisnike1913-Feb-11 9:58 

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.