新任务,要打开设置页面,并移动到指定位置,并设定窗口大小。
打开设置和移动窗口是非常简单,直接用ShellExecute和MoveWindow就可以了,上代码:
using System;using System.Runtime.InteropServices;namespace OpenWindows{class Program{static void Main(string[] args){ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);IntPtr handle = (IntPtr)0x00030E7E;MoveWindow(handle, 960, 0, 960, 1050, true);}[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);[DllImport("User32.dll")]static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);}}
一切顺利,现在来看,只要获取设置窗口的句柄就可以了,本以为是个小问题,但是却花费了大量的时间。
首先,获取设置窗口的句柄,肯定先想着通过setting的进程ID来获取他对应的窗口句柄就行了,先拿进程id,这里的函数为:GetProcessesByName,拿到后发现有一个MainWindowHandle属性,nice,看样子一下子就解决了,以下是代码:
using System;using System.Diagnostics;using System.Runtime.InteropServices;namespace OpenWindows{class Program{static void Main(string[] args){ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");//var setingId = setingProcesses[0].Id;var setingMainWindowHandle = setingProcesses[0].MainWindowHandle;MoveWindow(setingMainWindowHandle, 960, 0, 960, 1050, true);}[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);[DllImport("User32.dll")]static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);}}
然后在实际使用过程中却发现,实际上并没有move成功,打开vs上的spi++查看窗口发现,原来找到的只是子窗口,如下图:
必须找到父窗口才行,获取父窗口的方法为:GetParent,但是在实际中发现,直接调用GetParent得到的值为0,如下图:
搜索了一通才发现,原来是要等新打开的窗口注册为的子窗口后才能获取到父窗口,于是加了等待逻辑:
using System;using System.Diagnostics;using System.Runtime.InteropServices;using System.Threading;namespace OpenWindows{class Program{static void Main(string[] args){ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");//var setingId = setingProcesses[0].Id;IntPtr setingMainWindowHandle = setingProcesses[0].MainWindowHandle;IntPtr setingParentWindowHandle;int i = 0;while (i < 100){setingParentWindowHandle = GetParent(setingMainWindowHandle);if (setingParentWindowHandle != IntPtr.Zero){MoveWindow(setingMainWindowHandle, 960, 0, 960, 1050, true);}Thread.Sleep(100);i++;}}[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);[DllImport("User32.dll")]static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]public static extern IntPtr GetParent(IntPtr hWnd);}}
试下了可行,于是提交代码以为是完成任务了,但后来测了几遍发现,有时候会失效,又测了几遍发现是在人为打开设置页面后,过一会儿再执行代码就不行了,setting进程的MainWindowHandle为0,如下图:
具体是什么原因不是很清楚,个人猜测是setting进程起的窗口,已经注册成了子窗口,而这个函数是不搜索子窗口的,故这个MainWindowHandle值为空,那如果这样就难搞了,因为Windows找窗口的api无论是FindWindow还是EnumWindows都只会找顶层窗口,如下图:
没办法只能转换思路,查看spi++发现,我只要找这个顶层窗口就可以了,不用管那个setting进程起的子窗口:
于是用FindWindow来搜窗口名:
看起来是可行,但是被上级否决,理由是通过文字搜索不可靠,而且用户切换系统语言,适配起来比较麻烦。想想也是,那这条路是不可行的,只能继续找方案。
既然window提供的api只能找顶层窗口,而我也只关注顶层窗口,那直接通过其他方案来搜顶层窗口就可以了:
通过spi++发现,窗口的类名为:ApplicationFrameWindow,那直接通过类名搜索好了。由于FindWindow一次只能查找一个,所以用了EnumWindows。
跑下代码发现,虽然这个设置窗口在结果里,但是这个类名的顶层窗口有多个,并不能作为唯一的判断依据,这下就有点难办了。
看spi++,设置进程起的窗口是他的子窗口,那是不是可以通过搜索顶层窗口的子窗口,查看子窗口的进程id来实现呢?说干就干,一番折腾发现还真可行:
但这种方法也是有局限性,就是刚打开的设置窗口还未注册成子窗口,通过遍历子窗口是不行的,所以还要结合之前的方法,做一次判断才行,下面是完整的代码:
using System;using System.Collections.Generic;using System.Diagnostics;using System.Runtime.InteropServices;using System.Text;using System.Threading;namespace OpenWindows{class Program{private static bool IsChildWindowsByProcessesId(IntPtr handle, int processesId){uint iProcessId;IntPtr childHandle = IntPtr.Zero;while (true){childHandle = FindWindowEx(handle, childHandle, "Windows.UI.Core.CoreWindow", null);if (childHandle == IntPtr.Zero) break;GetWindowThreadProcessId(childHandle, out iProcessId);if (processesId == iProcessId)return true;}return false;}public static IReadOnlyList<IntPtr> FindWindowByClassName(string className){var windowList = new List<IntPtr>();EnumWindows(OnWindowEnum, (IntPtr)0);return windowList;bool OnWindowEnum(IntPtr hwnd, IntPtr lparam){var lpString = new StringBuilder(512);GetClassName(hwnd, lpString, lpString.Capacity);if (lpString.ToString().Equals(className, StringComparison.InvariantCultureIgnoreCase))windowList.Add(hwnd);return true;}}static void Main(string[] args){ShellExecute(IntPtr.Zero, "open", "ms-settings:storagesense", "", null, 9);IntPtr settingHandle = IntPtr.Zero;var setingProcesses = Process.GetProcessesByName("SYSTEMSETTINGS");IntPtr setingMainWindowHandle = setingProcesses[0].MainWindowHandle;if (setingMainWindowHandle != IntPtr.Zero){int i = 0;while (i < 100){settingHandle = GetParent(setingMainWindowHandle);if (settingHandle != IntPtr.Zero){break;}Thread.Sleep(100);i++;}}else{var applicationFrameWindowHandle = FindWindowByClassName("ApplicationFrameWindow");var setingId = setingProcesses[0].Id;foreach (var handle in applicationFrameWindowHandle){if (IsChildWindowsByProcessesId(handle, setingId)){settingHandle = handle;}}}MoveWindow(settingHandle, 960, 0, 960, 1050, true);}public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]static extern IntPtr ShellExecute(IntPtr hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);[DllImport("User32.dll")]static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]public static extern IntPtr GetParent(IntPtr hWnd);[DllImport("user32.dll")][return: MarshalAs(UnmanagedType.Bool)]static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);[DllImport("user32.dll")]private static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);[DllImport("user32.dll", SetLastError = true)]public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, string windowTitle);[DllImport("user32.dll", SetLastError = true)]static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);}}
思路说起来顺利,但实际过程曲折,还是得多动手才行啊。
如果觉得《C++/C#通过进程ID查找对应窗口句柄》对你有帮助,请点赞、收藏,并留下你的观点哦!