起こったこと
Windows10、C#で、最小化した電卓に対しFindWindow(null, "電卓")
でハンドルを取得、ShowWindow(hWnd, nCmdShow)
でウィンドウの状態を操作しようとしたが反応しなかった*1。最小化状態でハンドルを取得後、手動で非最小化状態にしてから操作しても期待とは異なる動きをした。
原因
非最小化状態と最小化状態では取得したウィンドウハンドルが異なるため。理由はしらぬい。
非最小化状態ではApplicationFrameWindow
のウィンドウハンドルを取得、最小化状態だとWindows.UI.Core.CoreWindow
のウィンドウハンドルを取得していた。
対処療法
次の処理をしてからFindWindow
を実行。
EnumWindows
ウィンドウを列挙、タイトルが目的のものと同じかつ最小化状態であれば最小化状態を解除。
他、FindWindow
の一つ目の引数(lpClassName)でApplicationFrameWindow
を指定する、EnumWindows
でGetClassName
でクラスネームも見てApplicationFrameWindow
のものを取得する、でもいけるかもいけないかも *2 ?(未確認)
背景
UIAutomationであそぼーとやってるうちにウィンドウの最大化、最小化とかもしたいなーとなって適当にやっているうちに、最小化状態で開始すると、最大化などができないことに気づきました。ごちゃごちゃやってるうちにどうも取得しているハンドルが違うっぽい?っぽい!Spy++で確認、なんやこれ。なんかEnumWindowsだと全部取得できるらしいですよ奥さん、めんどうだから見つけ次第最小化解除してみたらええんちゃうか、いけたからまあええかみたいな感じ。
ソース
static void Main(string[] args) { Console.WriteLine("AppName"); var name = Console.ReadLine(); var automationHelper = new AutomationHelper(name); while (true) { Console.WriteLine("Command or AutomationId:"); var autoid = Console.ReadLine(); switch (autoid) { case "Maximize": automationHelper.MaximizeWindow(); break; case "Minimize": automationHelper.MinimizeWindow(); break; case "Normalize": automationHelper.NormalizeWindow(); break; case "Click": Console.WriteLine(" x y:"); var points = Console.ReadLine().Split().Select(x => int.Parse(x)).ToArray(); automationHelper.Click(points[0], points[1]); break; default: var btnClear = automationHelper.FindInvokePatternById(autoid); btnClear.Invoke(); break; } } } class AutomationHelper { const int MOUSEEVENTF_LEFTDOWN = 0x0002; const int MOUSEEVENTF_LEFTUP = 0x0004; const int SW_MAXIMIZE = 3; const int SW_MINIMIZE = 6; const int SW_RESTORE = 9; AutomationElement rootElement; IntPtr hWnd; string title; [StructLayout(LayoutKind.Sequential)] struct INPUT { public int type; public MOUSEINPUT mi; } [StructLayout(LayoutKind.Sequential)] struct MOUSEINPUT { public int dx; public int dy; public int mouseData; public int dwFlags; public int time; public IntPtr dwExtraInfo; } [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern bool ClientToScreen(IntPtr hwnd, out System.Drawing.Point lpPoint); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern uint SendInput(uint nInt, INPUT[] pInputs, int cbSize); [DllImport("user32.dll ", CharSet = CharSet.Auto)] extern static int ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern int GetWindowTextLength(IntPtr hWnd); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern bool IsIconic(IntPtr hWnd); delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam); [DllImport("user32.dll ", CharSet = CharSet.Auto)] static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lparam); bool EnumWindowCallBack(IntPtr hWnd, IntPtr lparam) { int textLen = GetWindowTextLength(hWnd); if (0 < textLen) { var tsb = new StringBuilder(textLen + 1); GetWindowText(hWnd, tsb, tsb.Capacity); //該当のものの非最小化する if (tsb.ToString() == title && IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); } return true; } public AutomationHelper(string title) { this.title = title; EnumWindows(new EnumWindowsDelegate(EnumWindowCallBack), IntPtr.Zero); hWnd = FindWindow(null, title); rootElement = AutomationElement.FromHandle(hWnd); } //指定座標(クライアント座標基準)をクリック public void Click(int x, int y) { var pt = new System.Drawing.Point(); ClientToScreen(hWnd, out pt); var ptt = new System.Drawing.Point(pt.X + x, pt.Y + y); System.Windows.Forms.Cursor.Position = ptt; INPUT[] input = new INPUT[2]; input[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; input[1].mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(2, input, Marshal.SizeOf(input[0])); } //AutomationIdから取得 public AutomationElement FindElementById(string automationId) { var cnd = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId); return rootElement.FindFirst(TreeScope.Element | TreeScope.Descendants, cnd); } public InvokePattern FindInvokePatternById(string automationId) { return FindElementById(automationId).GetCurrentPattern(InvokePattern.Pattern) as InvokePattern; } //Nameから取得 public IEnumerable<AutomationElement> FindElementsByName(string name) { var cnd = new PropertyCondition(AutomationElement.NameProperty, name); return rootElement.FindAll(TreeScope.Element | TreeScope.Descendants, cnd).Cast<AutomationElement>(); } public InvokePattern FindInvokePatternByName(string name) { return FindElementsByName(name).FirstOrDefault().GetCurrentPattern(InvokePattern.Pattern) as InvokePattern; } public IEnumerable<AutomationElement> FindButtonByName(string name) { const string BUTTON_CLASS_NAME = " Button "; return FindElementsByName(name).Where(x => x.Current.ClassName == BUTTON_CLASS_NAME); } public InvokePattern FindButtonInvokePatternByName(string name) { return FindButtonByName(name).FirstOrDefault().GetCurrentPattern(InvokePattern.Pattern) as InvokePattern; } public void MaximizeWindow() => ControlWindow(SW_MAXIMIZE); public void MinimizeWindow() => ControlWindow(SW_MINIMIZE); public void NormalizeWindow() => ControlWindow(SW_RESTORE); public int ControlWindow(int nCmdShow) => ShowWindow(hWnd, nCmdShow); }
参考
UIAutomationとかいろいろ
RPA九人衆による「アカネチャンカワイイヤッタ」の自動化 - Qiita
UIAutomationで.Net製デスクトップアプリのGUIコンポーネントの自動制御を試みるまでのハートフルストーリー - たーせる日記
C# - c#でcalc.exeを最小化するには|teratail