C#でWindow情報を取得して一覧表にするツール作ってみた

WinAPIを使ってWindowの情報を取得して一覧表示します。
開発に役立てれればと思っておいておきます。
解説なし＆てきとうコーディングなので、あしからず。

[＞＞機能追加版はこちら＜＜](https://qiita.com/kob58im/items/3587d8e595e655e9391d)

こんな感じ↓（部分的にペイントで隠してます）
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/92713de1-19e1-fe47-75bf-06170233261f.png)

 
このアプリでは影響するか分かりませんが
STAThreadつけ忘れてたので追加しました。2019.08.22

```csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;


public class TopLevelWindowInfo
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    
    [DllImport("user32.dll",SetLastError = true)]
    private static extern int GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);
    
    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWINFO
    {
        public int   cbSize;
        public RECT  rcWindow;
        public RECT  rcClient;
        public int   dwStyle;
        public int   dwExStyle;
        public int   dwWindowStatus;
        public uint  cxWindowBorders;
        public uint  cyWindowBorders;
        public short atomWindowType;
        public short wCreatorVersion;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;  // external area. source:  https://docs.microsoft.com/en-us/previous-versions/dd162897(v%3Dvs.85)
        public int bottom; // 
        public int width{get{return right-left;}}
        public int height{get{return bottom-top;}}
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
    
    
    public const int MaxLength = 500;

    IntPtr _hWnd;
    string _className;
    string _windowText;
    WINDOWINFO _wi;
    int _pid;
    
    public IntPtr hWnd        {get{return _hWnd;}}
    public string className   {get{return _className;}}
    public string windowText  {get{return _windowText;}}
    public int    windowStatus{get{return _wi.dwWindowStatus;}}
    public int    left        {get{return _wi.rcWindow.left;}}
    public int    top         {get{return _wi.rcWindow.top;}}
    public int    width       {get{return _wi.rcWindow.width;}}
    public int    height      {get{return _wi.rcWindow.height;}}
    
    public int    clientLeft  {get{return _wi.rcClient.left;}}
    public int    clientTop   {get{return _wi.rcClient.top;}}
    public int    clientWidth {get{return _wi.rcClient.width;}}
    public int    clientHeight{get{return _wi.rcClient.height;}}

    public int    windowStyle {get{return _wi.dwStyle;}}
    public int    pid         {get{return _pid;}}
    
    public TopLevelWindowInfo(IntPtr arg_hWnd)
    {
        int retCode;
        
        _hWnd = arg_hWnd;
        
        //ウィンドウのクラス名を取得する
        StringBuilder csb = new StringBuilder(MaxLength);
        retCode = GetClassName(hWnd, csb, csb.Capacity);
        
        _className = csb.ToString();
        
        //ウィンドウのタイトルを取得する
        StringBuilder tsb = new StringBuilder(MaxLength);
        retCode = GetWindowText(hWnd, tsb, tsb.Capacity);
        
        if ( retCode > 0 ) {
            _windowText = tsb.ToString();
        }
        else {
            _windowText = "";
        }
        
        _wi = new WINDOWINFO();
        _wi.cbSize = Marshal.SizeOf(_wi);  // sizeof(WINDOWINFO);でもよいようだが sizeof()を使う場合は unsafe{}が必要
        retCode = GetWindowInfo(_hWnd, ref _wi);
        if ( retCode == 0 ) {
            Console.WriteLine("GetWindowInfo returns 0.");
        }
        
        GetWindowThreadProcessId(_hWnd, out _pid);
    }
    
    
    const int WS_VISIBLE = 0x10000000;
    const int WS_ICONIC  = 0x20000000;
    
    public bool IsVisibleWindow()
    {
        if ((_wi.dwStyle & WS_VISIBLE) == 0) return false;
        if ((_wi.dwStyle & WS_ICONIC) == WS_ICONIC) return false;
        if ( width <= 0 || height <= 0 ) return false;
        
        return true;
    }
}


public class MainForm:Form
{
    [DllImport("user32.dll",SetLastError = true)]
    private static extern IntPtr WindowFromPoint(POINT point);
    
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }
    
    [DllImport("user32.dll",SetLastError = true)]
    private static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
    const uint GA_PARENT    = 1;
    const uint GA_ROOT      = 2;
    const uint GA_ROOTOWNER = 3; // 複数windowを持つ場合は、そのownerが返る
    
    
    public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc,   IntPtr lparam);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
    
    
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetCursorPos(out POINT lpPoint);

    public enum LsvSortAttr {
        ByNumeric,
        ByHex,
        ByString
    };
    
    
    System.Windows.Forms.Timer timer;
    Button   btnRefresh;
    Button   btnPeriodicRefresh;
    CheckBox chkShowInvisibleWindow;
    ListView lsv;
    List<TopLevelWindowInfo> wndInfos;
    List<LsvSortAttr> SortOrderIdent;
    

    MainForm()
    {
        wndInfos = new List<TopLevelWindowInfo>();
        
        Text = "Listup Window Handles";
        SortOrderIdent = new List<LsvSortAttr>();
        ClientSize = new System.Drawing.Size(700,530);
        
        Shown += (sender,e)=>{EnumWndUpdateList();};
        Resize    += (sender,e)=>{MyResizeHandler();};
        ResizeEnd += (sender,e)=>{MyResizeHandler();};
        
        btnRefresh = new Button();
        btnRefresh.Text = "Refresh";
        btnRefresh.Click += (sender,e)=>{EnumWndUpdateList();};
        Controls.Add(btnRefresh);
        
        btnPeriodicRefresh = new Button();
        btnPeriodicRefresh.Left = 100;
        btnPeriodicRefresh.Text = "Auto";
        btnPeriodicRefresh.Click += (sender,e)=>
        {
            if ( timer==null || !timer.Enabled ) {
                btnPeriodicRefresh.Text = "Stop";
                StartTimer();
            }
            else {
                btnPeriodicRefresh.Text = "Auto";
                StopTimer();
            }
        };
        Controls.Add(btnPeriodicRefresh);
        
        
        chkShowInvisibleWindow = new CheckBox();
        chkShowInvisibleWindow.Left = 200;
        chkShowInvisibleWindow.Text = "ShowInvisible";
        chkShowInvisibleWindow.Click += (sender,e)=>{UpdateListControl();}; // 取得済みデータをフィルタするのは面倒なので 再取得
        Controls.Add(chkShowInvisibleWindow);
        
        
        lsv = new ListView();
        lsv.Location = new Point(0,30);
        lsv.Size = new System.Drawing.Size(700,500);
        lsv.View = View.Details;
        lsv.FullRowSelect = true;
        lsv.GridLines = true;
        lsv.Columns.Add("No"         , 35, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("HWND"       , 70, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByHex);
        lsv.Columns.Add("PID"        , 50, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("ClassName"  ,150, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByString);
        lsv.Columns.Add("WindowText" ,150, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByString);
        lsv.Columns.Add("Left"       , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("Top"        , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("Width"      , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("Height"     , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("cLeft"      , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("cTop"       , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("cWidth"     , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("cHeight"    , 45, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByNumeric);
        lsv.Columns.Add("Style"      , 70, HorizontalAlignment.Left);SortOrderIdent.Add(LsvSortAttr.ByHex);
        lsv.ColumnClick += (sender,e)=>{Lsv_ColumnClick(e);};
        Controls.Add(lsv);
    }
    
    void MyResizeHandler()
    {
        lsv.Size = new System.Drawing.Size(ClientSize.Width, ClientSize.Height - lsv.Top);
    }
    
    void Lsv_ColumnClick(ColumnClickEventArgs e)
    {
        if ( e.Column >= 0 && e.Column < lsv.Columns.Count ) {
            lsv.ListViewItemSorter = new ListViewItemComparer(e.Column, SortOrderIdent[e.Column]);
        }
        else {
            Console.WriteLine("Out of column index");
        }
    }
    
    public class ListViewItemComparer : IComparer
    {
        private int _column;
        private LsvSortAttr _sortAttr;

        public ListViewItemComparer(int col, LsvSortAttr sortAttr)
        {
            _column   = col;
            _sortAttr = sortAttr;
        }
        
        private int CompareInt64(Int64 a, Int64 b)
        {
            if(a<b){return -1;}
            if(a>b){return  1;}
            return 0;
        }

        public int Compare(object obj1, object obj2)
        {
            string s1 = ((ListViewItem)obj1).SubItems[_column].Text;
            string s2 = ((ListViewItem)obj2).SubItems[_column].Text;
            
            if ( _sortAttr == LsvSortAttr.ByNumeric ) {
                Int64 n1;
                Int64 n2;
                try {
                    n1 = Convert.ToInt64(s1);
                    n2 = Convert.ToInt64(s2);
                    return CompareInt64(n1, n2);
                }
                catch(Exception e){Console.WriteLine(e);} // catchしたら文字列比較へ
            }
            else if ( _sortAttr == LsvSortAttr.ByHex ) {
                // 未実装.. とりあえず文字列比較
            }
            return string.Compare(s1, s2);
        }
    }
    
    public ListViewItem ConvertToListViewItem(int index, TopLevelWindowInfo a)
    {
        var ss = new string[]
        {
            index.ToString(),
            a.hWnd.ToString("X8"),
            a.pid.ToString(),
            a.className,
            a.windowText,
            a.left.ToString(),
            a.top.ToString(),
            a.width.ToString(),
            a.height.ToString(),
            a.clientLeft.ToString(),
            a.clientTop.ToString(),
            a.clientWidth.ToString(),
            a.clientHeight.ToString(),
            a.windowStyle.ToString("X8")
        };
        
        var t = new ListViewItem(ss);
        t.Tag = a;
        return t;
    }

    void StartTimer()
    {
        timer = new System.Windows.Forms.Timer();
        timer.Interval = 2000;
        timer.Tick += (sender,e)=>{EnumWndUpdateList();};
        timer.Start();
    }
    
    void StopTimer()
    {
        if ( timer != null ) {
            timer.Stop();
        }
    }

    void EnumWndUpdateList()
    {
        wndInfos = new List<TopLevelWindowInfo>();
        
        //ウィンドウを列挙する
        EnumWindows(EnumWindowCallBack, IntPtr.Zero);
        
        UpdateListControl();
        
        POINT p = new POINT();
        GetCursorPos(out p);
        IntPtr hWnd = WindowFromPoint( p );
        IntPtr hWndRoot = IntPtr.Zero;
        
        if ( hWnd != IntPtr.Zero ) {
            hWndRoot = GetAncestor(hWnd, GA_ROOT);
        }
        
        Console.Write("HWND on cursor = 0x");
        Console.WriteLine(((int)hWndRoot).ToString("X8"));
    }
    
    
    void UpdateListControl()
    {
        bool showInvisWnd = chkShowInvisibleWindow.Checked;
        
        lsv.Items.Clear();
        lsv.BeginUpdate();
        try {
            int itemNo=0;
            foreach ( var t in wndInfos ) {
                itemNo++;
                if ( showInvisWnd || t.IsVisibleWindow() ) {
                    lsv.Items.Add(ConvertToListViewItem(itemNo, t));
                }
            }
        }
        finally {
            lsv.EndUpdate();
        }
    }
    
    /// <summary>
    /// エントリポイント
    /// </summary>
    [STAThread]
    public static void Main(string[] args)
    {
        Application.Run(new MainForm());
    }

    private bool EnumWindowCallBack(IntPtr hWnd, IntPtr lparam)
    {
        wndInfos.Add(new TopLevelWindowInfo(hWnd));
        return true;        //すべてのウィンドウを列挙する
    }
}


```