如何获取以我的表单为父级的窗口的当前标题? [英] How to get the current Title of a Window parented to my Form?

查看:71
本文介绍了如何获取以我的表单为父级的窗口的当前标题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 WinForm 应用程序,它作为其他进程(例如 Google Chrome)的 Windows 的父级.我正在使用以下代码将 Windows 设置为我的窗体的父级,使用 [Process].MainWindowHandle 返回的句柄.
我正在尝试查找以我的 Form 为父级的所有 Windows 的 MainWindowTitle,以便我可以在标签上显示它们的名称.

I've got a WinForm app that parents Windows of other processes (ex. Google Chrome). I'm using the following code to parent a Windows to my Form, using the Handle returned by [Process].MainWindowHandle.
I'm trying to find the MainWindowTitle of all the Windows that are parented to my Form, so I can display their name on a Label.

当WebBrowser的窗口是嵌入式时,标题将在选择不同的网页时更改,切换选项卡.

When the Window of a WebBrowser is embedded, the Title will change when a different Web Page is selected, switching Tabs.

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

The code I have for starting the program does work as it should:

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 仍然需要 :).

Since you're willing to use UIAutomation to handle this parenting affair, I propose to handle this using Automation methods entirely. Almost, SetParent still required :).

此处显示的类使用 WindowPatter.WindowOpened 事件,用于在系统中打开新窗口时检测和通知.
它可以是任何窗口,包括控制台(仍然是一个窗口).
此方法允许在已创建句柄时识别窗口,因此您不需要任意 超时 或尝试使用 Process.WaitForInputIdle(),它可能没有想要的结果.

The class shown here uses the WindowPatter.WindowOpened event to detect and notify when a new Window is open in the System.
It can be any Window, Console included (still a Window).
This method allows to identify a Window when it's handle is already created, so you don't need an arbitrary time-out or try to use Process.WaitForInputIdle(), which may not have the desired result.

您可以将进程名称列表传递给类的 ProcessNames 属性:当打开属于这些进程之一的任何窗口时,自动化将检测到它,并且公共事件是提高.它通知订阅者列表中的一个进程打开了一个窗口,它是所有者的ProcessId和窗口的句柄.
当引发 ProcessStarted 事件时,这些值在自定义 EventArgs 类中传递,ProcessStartedArgs.

You can pass a list of names of processes to the ProcessNames Property of the class: when any Window that belongs to one of these Processes is opened, the Automation will detect it and a public event is raised. It notifies the subscribers that one of the Processes in the list has opened a Window, which is the ProcessId of the Owner and the handle of the Windows.
These values are passed in a custom EventArgs class, ProcessStartedArgs when the ProcessStarted event is raised.

由于自动化事件是在 UI 线程以外的线程中引发的,因此该类会捕获 SynchronizationContext 类的创建位置(UI 线程,因为您可能正在表单中创建此类)并将事件编组到该线程调用 Post(),传递一个 SendOrPostCallback 委托.
这样,您就可以安全地将表单句柄和窗口句柄传递给 SetParent().

Since the Automation Event is raised in Thread other than the UI Thread, the class captures the 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 Post(), passing a SendOrPostCallback delegate.
This way, you can safely pass the Handle of your Form and the Handle of the Window to SetParent().

要检索父窗口的当前标题(标题),请将先前在事件参数中返回的句柄传递给 GetCurrentWindowTitle() 方法.如果 Window 包含选项卡子窗口,作为 WebBrowser,此方法将返回与当前选择的 Tab 相关的 Title.

To retrieve the current Title (Caption) of parented Window, pass the Handle previously returned in the event argument to the GetCurrentWindowTitle() method. If the Window contains tabbed child Windows, as a WebBrowser, this method will return the Title related to the Tab currently selected.

► 该类是一次性的,您需要调用它的公共 Dispose() 方法.这是自动化事件处理程序被删除的地方,也是您需要订阅以接收通知的公共事件的调用列表中的所有事件.这样,您就可以使用 Lambda 来订阅事件.

► The class is disposable and you need to call its public Dispose() method. This is where the Automation event handler is removed and also all the events in the Invocation List of the public event you need to subscribe to to receive the notifications. This way, you can use a Lambda to subscribe to the event.

使用一个字段来存储这个类的一个实例.在需要时创建实例,传递您感兴趣的进程名称列表.
订阅 ProcessStarted 事件.
当这些进程中的一个打开一个新窗口时,您将收到通知并且可以执行育儿事情:

Use a Field to store an instance of this class. Create the instance when needed, passing a List of Process Names you're interested in.
Subscribe to the ProcessStarted event.
When on of these Processes opens a new Window, you'll get a notification and the parenting thing can be performed:

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);
        };
    }

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

WindowWatcher 类:

WindowWatcher class:

注意:
UI 自动化程序集 是 Windows Presentation Framework 的一部分.
当 WinForms 应用程序中引用这些程序集之一时,WinForms 应用程序将成为 DpiAware (SystemAware),如果它还不是 DpiAware.
这可能会影响一个或多个并非旨在处理 Dpi Awareness 更改和通知的表单的布局.

NOTE:
UI Automation assemblies are part of Windows Presentation Framework.
When one of these assemblies is referenced in a WinForms application, the WinForms application will become DpiAware (SystemAware), if it's not already DpiAware.
This can have an impact on the Layout of one or more Forms that is not designed to handle Dpi Awareness changes and notifications.

需要项目参考:

  • UIAutomationClient
  • UIAutomationTypes
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)
        {
            this.ProcessName = procName;
            this.ProcessId = procId;
            this.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--) {
                    this.ProcessStarted -= (EventHandler<ProcessStartedArgs>)invList[i];
                }
            }
        }
    }
}

这篇关于如何获取以我的表单为父级的窗口的当前标题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆