C++/C#通过进程ID查找对应窗口句柄

新任务,要打开设置页面,并移动到指定位置,并设定窗口大小。

打开设置和移动窗口是非常简单,直接用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);
    }
}

思路说起来顺利,但实际过程曲折,还是得多动手才行啊。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值