2
3

More than 3 years have passed since last update.

C# - AutomationElementの情報を取得するツールをつくった(Windows10)

Last updated at Posted at 2020-01-01

機能

クリックした座標にあるコントロールの情報(実行パスとAutomationElementInformation)を取得します。
(ウィンドウハンドルを取得して、該当座標を含む、子AutomationElementを再帰的に探します。重なっている場合はどれか1つになります。)

AutomationElementの使い方は、手前味噌ですが
Windows10でBASIC認証画面にIDとパスワードを自動入力するソフトを作ったなどを参照ください。

注意事項

  • マルチスクリーン環境での動作は未確認です。
  • マウスのグローバルフックを使用しています。

スクリーンショット

例:getボタンを押して、コマンドプロンプトのタイトルバーをクリックした場合。
image.png

ソースコード


using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;


public static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }

    public const int WH_MOUSE_LL = 14;
    public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idHook, HookProc callback, IntPtr hInstance, int threadId);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnhookWindowsHookEx(IntPtr hHook);

    [DllImport("user32.dll")]
    public static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);

    public const int WM_MOUSEMOVE   = 0x0200;
    public const int WM_LBUTTONDOWN = 0x0201;
    public const int WM_LBUTTONUP   = 0x0202;
    public const int WM_RBUTTONDOWN = 0x0204;
    public const int WM_RBUTTONUP   = 0x0205;
    public const int WM_MOUSEWHEEL  = 0x020A;
    public const int WM_MOUSEHWHEEL = 0x020E;

    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr WindowFromPoint(POINT point);

    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr GetAncestor(IntPtr hWnd, int gaFlags);
    public const int GA_PARENT    = 1;
    public const int GA_ROOT      = 2;
    public const int GA_ROOTOWNER = 3;

    [DllImport("User32.dll")]
    public static extern IntPtr GetDC(IntPtr hwnd);

    [DllImport("User32.dll")]
    public static extern void ReleaseDC(IntPtr hwnd, IntPtr dc);

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

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetProcessDPIAware();
}


public class AutomationTest : Form
{
    IntPtr _hHook;
    NativeMethods.HookProc _handler;
    GCHandle _hookProcHandle;
    NativeMethods.POINT _lastPoint;
    bool _hookExitReq;

    System.Windows.Forms.Timer timer;
    Button btn;
    TextBox txtProc;
    ListView lsv;

    AutomationTest()
    {
        NativeMethods.SetProcessDPIAware();

        timer = new System.Windows.Forms.Timer();
        timer.Interval = 10;
        timer.Tick += (s,e)=>{Timer_Tick();};

        btn = new Button(){Text="get"};
        btn.Click += (s,e)=>{Btn_Click();};
        Controls.Add(btn);

        txtProc = new TextBox(){
            ReadOnly = true,
            Location = new System.Drawing.Point(100,0)
        };
        txtProc.KeyDown += (sender,e)=>{
            if (e.Control && e.KeyCode == Keys.A) { txtProc.SelectAll(); }
        };
        Controls.Add(txtProc);

        lsv = new ListView(){
            Location = new System.Drawing.Point(0, 40),
            FullRowSelect = true,
            GridLines = true,
            HideSelection = false,
            MultiSelect = false,
            View = View.Details
        };
        lsv.MouseDoubleClick += Lsv_MouseDoubleClick;
        lsv.Columns.AddRange(new ColumnHeader[]{
            new ColumnHeader(){Text="ClassName",Width=150},
            new ColumnHeader(){Text="AutomationId",Width=80},
            new ColumnHeader(){Text="ControlType",Width=100},
            new ColumnHeader(){Text="FrameworkId",Width=50},
            new ColumnHeader(){Text="Name",Width=150},
            new ColumnHeader(){Text="ItemType",Width=50},
            new ColumnHeader(){Text="X"     ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Y"     ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Width" ,Width=50,TextAlign=HorizontalAlignment.Right},
            new ColumnHeader(){Text="Height",Width=50,TextAlign=HorizontalAlignment.Right},
        });
        Controls.Add(lsv);

        this.Text = "AutomationElement Information Getter";
        this.ClientSize = new System.Drawing.Size(700, 500);

        FormClosed += (s,e)=>{UnHook();};

        Load += (s,e)=>{MyResize();};
        Resize += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
    }

    void MyResize()
    {
        int w = ClientSize.Width - txtProc.Left;
        int h = ClientSize.Height - lsv.Top;
        if (w<50){w=50;}
        if (h<50){h=50;}
        lsv.Size = new System.Drawing.Size(ClientSize.Width, h);
        txtProc.Width = w;
    }

    void Timer_Tick()
    {
        UnHook();
        if ( !timer.Enabled ) {
            return;
        }
        timer.Stop();
        btn.Enabled = true;

        var p = new System.Windows.Point(_lastPoint.x, _lastPoint.y);

        IntPtr hWnd = NativeMethods.WindowFromPoint(_lastPoint);
        hWnd = NativeMethods.GetAncestor(hWnd, NativeMethods.GA_ROOT);
        var elem = AutomationElement.FromHandle(hWnd);

        {
            int pid;
            NativeMethods.GetWindowThreadProcessId(hWnd, out pid);
            var proc = System.Diagnostics.Process.GetProcessById(pid);
            txtProc.Text = proc.MainModule.FileName;
        }

        if ( elem != null) {
            lsv.BeginUpdate();
            try {
                AutomationElement.AutomationElementInformation elemInfo;
                do {
                    try {
                        elemInfo = elem.Current;
                    }
                    catch( ElementNotAvailableException ) {
                        return;
                    }

                    lsv.Items.Add(AeToListItem(elemInfo));
                    elem = FindNextElementFromPoint(elem, p);
                }
                while(elem != null);

                DrawPointAndRectToScreen(p, elemInfo.BoundingRectangle);
            }
            finally {
                lsv.EndUpdate();
            }
        }
    }

    AutomationElement FindNextElementFromPoint(AutomationElement elem, System.Windows.Point p)
    {
        var childElements = elem.FindAll(TreeScope.Children, Condition.TrueCondition);

        foreach(AutomationElement childElem in childElements) {
            AutomationElement.AutomationElementInformation elemInfo;
            try {
                elemInfo = childElem.Current;
            }
            catch( ElementNotAvailableException ) {
                return null;
            }

            if ( elemInfo.BoundingRectangle.Contains(p) ) {
                return childElem;
            }
        }
        return null;
    }

    ListViewItem AeToListItem(AutomationElement.AutomationElementInformation a)
    {
        System.Windows.Rect r = a.BoundingRectangle;

        return new ListViewItem(new string[]{
            a.ClassName,
            a.AutomationId,
            a.ControlType.ToString(),
            a.FrameworkId,
            a.Name,
            a.ItemType,
            r.X.ToString(),
            r.Y.ToString(),
            r.Width.ToString(),
            r.Height.ToString()
        });
    }

    void Btn_Click()
    {
        try {
            _hookExitReq = false;
            SetHook();
        }
        catch (System.ComponentModel.Win32Exception e) {
            MessageBox.Show(e.ToString());
            return;
        }

        btn.Enabled = false;
        lsv.Items.Clear();
    }

    void Lsv_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        ListViewHitTestInfo info = lsv.HitTest(e.Location);
        if ( info.SubItem != null ) {
            SubForm f = new SubForm(info.SubItem.Text);
            f.ShowDialog();
        }
    }

    void DrawPointAndRectToScreen(System.Windows.Point p, System.Windows.Rect rect)
    {
        IntPtr desktopDC = NativeMethods.GetDC(IntPtr.Zero);

        if (desktopDC == IntPtr.Zero) {
            // failed
            return;
        }

        try {
            var pen = new System.Drawing.Pen(System.Drawing.Color.Blue, 6.0f);
            using (var g = System.Drawing.Graphics.FromHdc(desktopDC)) {
                g.DrawLine(pen, (float)(p.X-5), (float)(p.Y-5), (float)(p.X+5), (float)(p.Y+5));
                g.DrawLine(pen, (float)(p.X-5), (float)(p.Y+5), (float)(p.X+5), (float)(p.Y-5));
                g.DrawRectangle(pen, (float)rect.X, (float)rect.Y, (float)rect.Width, (float)rect.Height);
            }
        }
        finally {
            NativeMethods.ReleaseDC(IntPtr.Zero, desktopDC);
        }
    }

    void SetHook()
    {
        IntPtr module = IntPtr.Zero;
        _handler = CallbackProc;
        _hookProcHandle = GCHandle.Alloc(_handler);
        _hHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, _handler, module, 0);

        if ( _hHook == IntPtr.Zero ) {
            // failed
            int errorCode = Marshal.GetLastWin32Error();
            _hookProcHandle.Free();
            _handler = null;
            throw new System.ComponentModel.Win32Exception(errorCode);
        }
    }

    void UnHook()
    {
        if ( _hHook != IntPtr.Zero ) {
            NativeMethods.UnhookWindowsHookEx(_hHook);
            _hHook = IntPtr.Zero;
            _hookProcHandle.Free();
            _handler = null;
        }
    }

    IntPtr CallbackProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if ( nCode < 0 || _hookExitReq ) {
            return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }
        else {
            if ( (long)wParam == NativeMethods.WM_MOUSEMOVE ) {
                return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
                // 注意:WM_MOUSEMOVE をキャンセルすると、カーソル表示が更新されない
            }
            else {
                if ( (long)wParam == NativeMethods.WM_LBUTTONDOWN ||
                     (long)wParam == NativeMethods.WM_RBUTTONDOWN ) {
                    _hookExitReq = true;
                    var p = Cursor.Position;
                    _lastPoint = new NativeMethods.POINT(){x=p.X, y=p.Y};
                    timer.Start();
                }
                // cancel
                return new IntPtr(1);
            }
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new AutomationTest());
    }
}

internal class SubForm : Form
{
    internal SubForm(string text)
    {
        var txt = new TextBox(){
            Text = text,
            Multiline = true,
            ScrollBars = ScrollBars.Both,
            Dock = DockStyle.Fill
        };
        txt.KeyDown += (sender,e)=>{
            if (e.Control && e.KeyCode == Keys.A) { txt.SelectAll(); }
        };
        Controls.Add(txt);
    }
}

コンパイルバッチ

compile.bat

csc ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationClient\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationClient.dll ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\UIAutomationTypes\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationTypes.dll ^
 /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll ^
 %*
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3