Getting Information for all Windows using P/Invoke and C#

A colleague of mine recently asked how to list all the windows on a machine. Microsoft Visual Studio (a developer programming tool) ships with a tool called Spy++ that will list all windows, but Spy++ is a GUI tool and my colleague wanted full programmatic access to windows. Anyway, iterating though all windows is something I do quite frequently so I coded up a short demo. In pseudo-code:

get handle to Desktop window
get all Desktop window info (level, caption, parent)

enqueue desktop window and info
while (queue not empty)
  w = dequeue // curr window
  print info about w
  get all child windows of w
  foreach child
    get all child info
    enqueue child and info
  end
end

The image below shows the demo (which I named SpyMinusMinus) running next to the Spy++ GUI tool. You can see the outputs are essentially the same. All code (in C#) listed below the image.

SpyMinusMinus

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace SpyMinusMinus
{
  class Program
  {
    [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
    static extern IntPtr GetDesktopWindow();

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hwndControl, uint Msg,int wParam, StringBuilder strBuffer); // get text

    [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    static extern int SendMessage(IntPtr hwndControl, uint Msg, int wParam, int lParam);  // text length

    [DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
    static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

    static void Main(string[] args)
    {
      Console.WriteLine("\nGet all windows demo\n");

      Queue q = new Queue(); // use a Stack instead for depth-first traversal

      IntPtr hDesktop = GetDesktopWindow();
      WindowInfo wi = new WindowInfo(); wi.handle = hDesktop; wi.level = 0; wi.caption = "(Desktop)"; wi.parent = IntPtr.Zero - 1;
      q.Enqueue(wi);

      while (q.Count > 0)
      {
        WindowInfo w = q.Dequeue();
        Console.WriteLine(w.ToString());

        List children = GetAllChildrenHandles(w.handle, 5000); // 5000 is max children sanity check
        for (int i = 0; i < children.Count; ++i)
        {
          WindowInfo ci = new WindowInfo(); // child window info
          ci.handle = children[i];
          ci.level = w.level + 1; // parent level + 1
          ci.caption = GetCaptionText(children[i]);
          ci.parent = w.handle;
          q.Enqueue(ci);
        }
      }

      Console.WriteLine("\nEnd demo\n");
      Console.ReadLine();
    }

    static List GetAllChildrenHandles(IntPtr hParent, int maxCount)
    {
      List result = new List();
      int ct = 0;
      IntPtr prevChild = IntPtr.Zero;
      IntPtr currChild = IntPtr.Zero;
      while (true && ct < maxCount)
      {
        currChild = FindWindowEx(hParent, prevChild, null, null);
        if (currChild == IntPtr.Zero) break;
        result.Add(currChild);
        prevChild = currChild;
        ++ct;
      }
      return result;
    }

    static int GetCaptionTextLength(IntPtr hTextBox)
    {
      // helper for GetCaptionText
      uint WM_GETTEXTLENGTH = 0x000E;
      int result = SendMessage(hTextBox, WM_GETTEXTLENGTH,
        0, 0);
      return result;
    }

    static string GetCaptionText(IntPtr hTextBox)
    {
      uint WM_GETTEXT = 0x000D;
      int len = GetCaptionTextLength(hTextBox);
      if (len <= 0) return null;  // no text. consider empty string instead.
      StringBuilder sb = new StringBuilder(len + 1);
      SendMessage(hTextBox, WM_GETTEXT, len + 1, sb);
      return sb.ToString();
    }
  } 

  public class WindowInfo
  {
    public IntPtr handle;
    public int level; // 0 = desktop, 1 = all top-level windows, etc.
    public string caption; // several things such as app title or textbox text
    public IntPtr parent;

    public override string ToString()
    {
      string captionDisplay;
      if (caption == null) captionDisplay = "no caption";
      else captionDisplay = caption.ToString();

      return "handle = " + handle.ToString("X").PadLeft(8, '0') +
        " level = " + level.ToString().PadRight(3) +
        " caption = " + captionDisplay.ToString().PadRight(40) +
        " parent = " + parent.ToString("X").PadLeft(8, '0');
    }
  }
}
This entry was posted in Machine Learning, Software Test Automation. Bookmark the permalink.