Sunday, August 4, 2013

Topmost window always on top





This project was inspired by PowerMenu 1.51 by Thong Nyguyen. I wrote a small C# system tray application that provides some of the same functionality as PowerMenu. It can set/unset any application window as always on top or topmost. This is useful if you want to compare or edit two documents at once and you want to make a window float, sticky, or pin a window on top. I wrote this tool in C# and am giving you the source code here so you can see how it works. Since .NET does not support such level of control over other applications, our code must call the native WIN32 APIs SetWindowPos and FindWindow to accomplish this.

Besides being a system tray application (NotifyIcon), the application essentially has two main functions: GetWindowTitles(), which uses the Process class to return a list of windows' titles as an array of strings, and AssignTopmostWindow() which takes a windows title as a parameter.

But first, we must define our WIN32 API imports:

#region WIN32API
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
const UInt32 SWP_SHOWWINDOW = 0x0040;

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left, Top, Right, Bottom;
    public RECT(int left,int top,int right,int bottom) {
        Left=left; Top=top; Right=right; Bottom=bottom; }
    public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
    public int X { get { return Left; } set { Right -= (Left - value); Left = value; } }
    public int Y { get { return Top; } set { Bottom -= (Top - value); Top = value; } }
    public int Height { get { return Bottom - Top; } set { Bottom = value + Top; } }
    public int Width { get { return Right - Left; } set { Right = value + Left; } }
    public static implicit operator System.Drawing.Rectangle(RECT r) {
        return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height); }
    public static implicit operator RECT(System.Drawing.Rectangle r) { return new RECT(r); }
}

[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(HandleRef hwnd, out RECT lpRect);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
                                        int x, int y, int cx, int cy, uint uFlags );
#endregion

Notice the RECT structure. The WIN32 RECT struct is different than the .NET version, and we need it to store and pass the window position during our call to SetWindowPos() or the window will likely disappear during resizing.

Here you can see in our implementation of AssignTopmostWindow, we get the window rectangle with GetWindowRect() and pass it straight to our call to SetWindowPos():

bool AssignTopmostWindow(string windowTitle,bool makeTopmost)
{
 IntPtr hWnd = FindWindow((string)null,windowTitle);
 
 RECT rect = new RECT();
 GetWindowRect(new HandleRef(this,hWnd),out rect);
 
 return SetWindowPos(hWnd,
              makeTopmost ? HWND_TOPMOST : HWND_NOTOPMOST,
              rect.X, rect.Y, rect.Width, rect.Height,
              SWP_SHOWWINDOW);
}

So your application is first going to want to get the titles of all the forms and windows:

string[] GetWindowTitles()
{
 List<string> winList = new List<string>();
 
 Process[] procArray = Process.GetProcesses();
 foreach(Process proc in procArray)
 {
  if(!string.IsNullOrWhiteSpace(proc.MainWindowTitle))
  {
   winList.Add(proc.MainWindowTitle);
  }
 }
 return winList.ToArray();
}

Then your application must then fill a listbox or somehow display this data to the user for selection. I choose to populate a dynamic context menu for my NotifyIcon application:

private MenuItem[] DynamicMenu()
{
 string[] titleArray = GetWindowTitles();
 
 List<MenuItem> menuList = new List<MenuItem>();
 foreach(string title in titleArray)
 {
  if(changeLog.ContainsKey(title))
  {
   if(changeLog[title])
   {
    menuList.Add(new MenuItem("* " + title, menuWinClick));
   }
   else
   {
    menuList.Add(new MenuItem(title, menuWinClick));
   }
  }
  else
  {
   menuList.Add(new MenuItem(title, menuWinClick));
  }
 }
 menuList.Add(new MenuItem("_____________"));
 menuList.Add(new MenuItem("About", menuAboutClick));
 menuList.Add(new MenuItem("Exit", menuExitClick));
 
 return menuList.ToArray();
}
Once the user has made a selection, a call to AssignTopmostWindow is made.

If your application should happen to exit without another call to AssignTopmostWindow(selectedTitle,false) to undo the topmost property, the window will remain topmost until reboot. As this is likely undesirable behavior, . My solution was to create a dictionary to keep track of the windows whose topmost property was modified, and 'roll back' those changes when your program/form is ending/closing:


private Dictionary<string,bool> changeLog = new Dictionary<string, bool>();

private void menuExitClick(object sender, EventArgs e)
{
 // Roll-back changes by
 //  unsetting every topmost window
 foreach(KeyValuePair<string,bool> entry in changeLog)
 {
  if(entry.Value) // If topmost
  {
   AssignTopmostWindow(entry.Key,false);
  }
 }   
 Application.Exit();
}


Here is the full code.