如何获取作为我的表单父级的 Window 的当前标题?

How to get the current Title of a Window parented to my Form?

我有一个 WinForm 应用程序 parents Windows 其他进程(例如 Google Chrome)。我正在使用以下代码 parent Windows 到我的表单,使用 [Process].MainWindowHandle.
返回的句柄 我正在尝试找到 parent 编辑到我的表单的所有 Windows 的 MainWindowTitle,这样我就可以在标签上显示他们的名字。

当webbrowser的Window为嵌入时,选择其他网页时,标题将更改,切换选项卡。

我用于启动程序的代码确实可以正常工作:

ProcessStartInfo ps1 = new ProcessStartInfo(@"C:/Users/Jacob/AppData/Roaming/Spotify/Spotify.exe");
ps1.WindowStyle = ProcessWindowStyle.Minimized;
Process p1 = Process.Start(ps1);
// Allow the process to open it's window
Thread.Sleep(1000);
appWin1 = p1.MainWindowHandle;
spotify = p1;

// Put it into this form
SetParent(appWin1, this.Handle);
// Move the window to overlay it on this window
MoveWindow(appWin1, 0, 70, this.Width / 2, this.Height/2, true);

既然你愿意使用UIAutomation来处理这个育儿事件,我建议完全使用自动化方法来处理这个问题。差不多了,SetParent还需要:).

此处显示的 class 使用 WindowPatter.WindowOpenedEvent 来检测并通知何时在系统中打开新的 Window。
它可以是任何 Window,包括控制台(仍然是 Window)。
此方法允许在句柄已创建时识别 Window,因此您不需要任意 time-out 或尝试使用 Process.WaitForInputIdle() , 这可能没有预期的结果。

您可以将进程名称列表传递给 class 的 ProcessNames 属性:当打开属于这些进程之一的任何 Window 时, UIAutomation 检测到它并引发 public 事件。它通知订阅者列表中的一个进程打开了一个 Window,它是 Owner 的 ProcessId 和 Windows.
的句柄 当引发 ProcessStarted 事件时,这些值在自定义 EventArgs class、ProcessStartedArgs 中传递。

由于自动化事件是在 UI 线程以外的线程中引发的,因此 class 捕获 SynchronizationContext where the class is created (the UI Thread, since you're probably creating this class in a Form) and marshals the event to that Thread, calling its Post() method passing a SendOrPostCallback 委托。
这样,您就可以安全地将表单的句柄和 Window 的句柄传递给 SetParent()

要检索父级 Window 的当前标题 (Caption),请将事件参数中先前 return 编辑的句柄传递给 GetCurrentWindowTitle() 方法。如果 Window 包含选项卡 child Windows,作为 Web 浏览器,此方法将 return 与当前所选选项卡相关的标题。

▶ class 是一次性的,你需要调用它的 public Dispose() 方法。这将删除自动化事件处理程序以及您订阅的 public 事件的调用列表中的所有事件。这样,您就可以使用 Lambda 来订阅事件了。


使用字段存储此 class 的实例。在需要时创建实例,传递您感兴趣的进程名称列表。
订阅 ProcessStarted 活动。
当其中一个进程打开一个新的 Window 时,您会收到通知并且可以执行育儿操作:

public partial class SomeForm : Form
{
    private WindowWatcher watcher = null;

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        watcher = new WindowWatcher();
        watcher.ProcessNames.AddRange(new[] { "msedge", "firefox", "chrome", "notepad" });

        watcher.ProcessStarted += (o, ev) => {
            SetParent(ev.WindowHandle, this.Handle);
            MoveWindow(ev.WindowHandle, 0, 70, this.Width / 2, this.Height / 2, true);
            string windowTitle = WindowWatcher.GetCurrentWindowTitle(ev.WindowHandle);
        };
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        watcher.Dispose();
        base.OnFormClosed(e);
    }
}

WindowWatcher class:

注意UI Automation assembliesWindows Presentation Framework 的一部分。
当在 WinForms 应用程序中引用这些程序集之一时,WinForms 应用程序将成为 DpiAware (SystemAware),如果它还不是 DpiAware。
这可能会影响并非设计用于处理 Dpi 感知更改和通知的一个或多个表单的布局。

需要项目参考:

  • UI自动化客户端
  • UI自动化类型
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Automation;

public class WindowWatcher : IDisposable
{
    private SynchronizationContext context = null;
    private readonly SendOrPostCallback eventCallback;
    public event EventHandler<ProcessStartedArgs> ProcessStarted;
    private AutomationElement uiaWindow;
    private AutomationEventHandler WindowOpenedHandler;

    public WindowWatcher() {
        context = SynchronizationContext.Current;
        eventCallback = new SendOrPostCallback(EventHandlersInvoker);
        InitializeWatcher();
    }

    public List<string> ProcessNames { get; set; } = new List<string>();

    private void InitializeWatcher()
    {
        Automation.AddAutomationEventHandler(
            WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
            TreeScope.Children, WindowOpenedHandler = new AutomationEventHandler(OnWindowOpenedEvent));
    }

    public static string GetCurrentWindowTitle(IntPtr handle)
    {
        if (handle == IntPtr.Zero) return string.Empty;
        var element = AutomationElement.FromHandle(handle);
        if (element != null) {
            return element.Current.Name;
        }
        return string.Empty;
    }

    private void OnWindowOpenedEvent(object uiaElement, AutomationEventArgs e)
    {
        uiaWindow = uiaElement as AutomationElement;
        if (uiaWindow == null || uiaWindow.Current.ProcessId == Process.GetCurrentProcess().Id) return;
        var window = uiaWindow.Current;

        var procName = string.Empty;
        using (var proc = Process.GetProcessById(window.ProcessId)) {
            if (proc == null) throw new InvalidOperationException("Invalid Process");
            procName = proc.ProcessName;
        }

        if (ProcessNames.IndexOf(procName) >= 0) {
            var args = new ProcessStartedArgs(procName, window.ProcessId, (IntPtr)window.NativeWindowHandle);
            context.Post(eventCallback, args);
        }
    }

    public class ProcessStartedArgs : EventArgs
    {
        public ProcessStartedArgs(string procName, int procId, IntPtr windowHandle)
        {
            ProcessName = procName;
            ProcessId = procId;
            WindowHandle = windowHandle;
        }

        public string ProcessName { get; }
        public int ProcessId { get; }
        public IntPtr WindowHandle { get; }
    }

    private void EventHandlersInvoker(object state)
    {
        if (!(state is ProcessStartedArgs args)) return;
        ProcessStarted?.Invoke(this, args);
    }

    ~WindowWatcher() { Dispose(false); }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (uiaWindow != null && WindowOpenedHandler != null) {
            Automation.RemoveAutomationEventHandler(
                WindowPattern.WindowOpenedEvent, uiaWindow, WindowOpenedHandler);
        }
            
        if (ProcessStarted != null) {
            var invList = ProcessStarted.GetInvocationList();
            if (invList != null && invList.Length > 0) {
                for (int i = invList.Length - 1; i >= 0; i--) {
                    ProcessStarted -= (EventHandler<ProcessStartedArgs>)invList[i];
                }
            }
        }
    }
}