新任务,要打开设置页面,并移动到指定位置,并设定窗口大小。
打开设置和移动窗口是非常简单,直接用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);
}
}
思路说起来顺利,但实际过程曲折,还是得多动手才行啊。