Table of Contents
- Introduction
- What this project will demonstrate
- Summary
- Background
- 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
- Make a window stay always on top
- Points of Interest
- 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
Delegate
s 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:
- Hide all windows
- Show the windows in the sorted order
First, I was messing around with:
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:
[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:
- Sorting the
ListViewItem
s by the context menu or the Button
s
- Apply a predefined sort order from user settings
The main GUI
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.
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
):
[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 Delegate
s. So the next step was to implement a Delegate
(s. WindowList
) ...
public delegate Boolean WinCallBack(int hwnd, int lParam);
... with the related function call (s. WindowList.init()
) ...
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).
private bool EnumWindowCallBack(int hwnd, int lParam) {
IntPtr windowHandle = (IntPtr)hwnd;
StringBuilder sbWindowTitle = new StringBuilder(1024);
Unmanaged.ApiGetWindowText((int)windowHandle, sbWindowTitle,
sbWindowTitle.Capacity);
if (sbWindowTitle.Length > 0) {
StringBuilder sbProcessClass = new StringBuilder(256);
Unmanaged.ApiGetClassName
(hwnd, sbProcessClass, sbProcessClass.Capacity);
String processClass = sbProcessClass.ToString();
Boolean isVisible = Unmanaged.ApiIsWindowVisible(windowHandle);
Boolean isRelevant = false;
if (this.ReturnOnlyRelevantWindows) {
isRelevant = (isVisible && !processClass.Equals
("Progman", StringComparison.CurrentCultureIgnoreCase));
} else {
isRelevant = true;
}
if (isRelevant) {
Unmanaged.RECT r = new Unmanaged.RECT();
Unmanaged.ApiGetWindowRect(windowHandle, ref r);
Unmanaged.WINDOWPLACEMENT windowPlacement =
new Unmanaged.WINDOWPLACEMENT();
windowPlacement.length =
System.Runtime.InteropServices.Marshal.SizeOf(windowPlacement);
Unmanaged.ApiGetWindowPlacement(hwnd, ref windowPlacement);
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)
);
wi.WindowPlacement = windowPlacement;
wi.WindowRect = r;
this.Windows.Add(wi);
} else {
}
} else {
}
return true;
}
What my callback function does is simple:
- I get (with unmanaged function call) the window title text, because only windows with a window text are relevant (I believe...)
- I get (with unmanaged function call) the class name because '
Progman
' is not relevant (I believe...)
- I check (with unmanaged function call) if the window is visible
- I check if the window is relevant or not. A relevant window is:
- visible and
- process class is not '
Progman
'
- If the window is relevant:
- Get window's position and size (just because) with unmanaged function call
- Get window's appearance (Maximized, Position, ...). With this information, a window can be restored correctly.
- Create a new
WindowItem
- Add the
WindowItem
to the collection
Finally, we have a collection of 'relevant' WindowItem
s.
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
:
[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 WindowItem
s (s. WindowsList.SortWindowsByWindowItemList()
):
public static void SortWindowsByWindowItemList(List<windowitem> hwndOrdered) {
foreach (WindowItem wItem in hwndOrdered) {
Unmanaged.ApiShowWindowAsync(wItem.WindowHandle, Unmanaged.SW_HIDE);
}
System.Threading.Thread.Sleep(200);
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);
}
System.Threading.Thread.Sleep(200);
}
}
If the user sorts the ListView
, the WindowItem
s are taken from the ListViewItem.Tag
and passed to the SortWindowsByWindowItemList()
.
private void btnSortByListView_Click(object sender, EventArgs e) {
List<windowitem> windowsOrdered = new List<windowitem>();
foreach (ListViewItem lvItem in this.lv_Windows.Items) {
WindowItem wItem = (WindowItem)lvItem.Tag;
windowsOrdered.Add(wItem);
}
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
):
public void SetTransparency(byte Alpha) {
int extStyle = Unmanaged.ApiGetWindowLong
(this.WindowHandle, Unmanaged.GWL_EXSTYLE);
Unmanaged.ApiSetWindowLong
(this.WindowHandle, Unmanaged.GWL_EXSTYLE, extStyle | Unmanaged.WS_EX_LAYERED);
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:
[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);
[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()
).
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)) {
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()
):
private void newNotificationIcon(WindowItem wItem) {
NotifyIcon icon = new NotifyIcon();
icon.Icon = TaskBarSorterHelpers.GetIconFromFile
(wItem.ModulePath, this._DefaultIcon);
icon.Text = StringHelpers.Left(wItem.WindowTitle, 50);
icon.Visible = true;
icon.Tag = wItem;
icon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler
(this.showMinimizedWindow);
this._minimizedWindowItems.Add(wItem);
wItem.Hide();
}
- Create a new
NotifyIcon
.
- Add the
WindowItem
to the Tag
property. This information is needed to restore the window again.
- Add a double click event handler.
- Add the
WindowItem
to collection of minimized windows. On application closing, we have to restore all windows in this collection again.
- Finally we hide the window.
Restoring a NotifyIcon
is as simple as that:
private void showMinimizedWindow(object sender, MouseEventArgs e) {
NotifyIcon icon = (NotifyIcon)sender;
WindowItem wItem = (WindowItem)icon.Tag;
wItem.Show();
icon.Dispose();
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, WindowItem.StayOnTop()
:
[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 Button
s 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
|