既然你愿意用UI自动化 https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation来处理这个养育子女事情,我建议完全使用自动化方法来处理这个问题。几乎,SetParent
仍然需要:)。
此处显示的类使用WindowPatter.WindowOpenedEvent https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.windowpattern.windowopenedevent检测并通知系统中何时打开新窗口。
它可以是任何窗口,包括控制台(仍然是窗口)。
此方法允许在已创建窗口句柄时识别窗口,因此您不需要任意time-out或尝试使用Process.WaitForInputIdle()
,这可能不会达到预期的结果。
您可以将进程名称列表传递给ProcessNames
类的属性:当打开属于这些进程之一的任何窗口时,UIAutomation 会检测到它并引发公共事件。它通知订阅者列表中的进程之一打开了一个窗口,这是ProcessId
所有者和 Windows 句柄。
这些值在自定义中传递EventArgs
class, ProcessStartedArgs
当。。。的时候ProcessStarted
事件被引发。
由于自动化事件是在 UI 线程以外的线程中引发的,因此该类捕获同步上下文 https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext创建类的位置(UI 线程,因为您可能在表单中创建此类)并将事件编组到该线程,调用其Post() https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext.post方法传递一个发送或后回调 http://SendOrPostCallback代表。
这样,您可以安全地将表单的句柄和窗口的句柄传递给SetParent()
.
要检索父窗口的当前标题 (Caption),请将先前在事件参数中返回的句柄传递给GetCurrentWindowTitle()
方法。如果窗口包含选项卡式子窗口,作为 Web 浏览器,此方法将返回与当前所选选项卡相关的标题。
▶ 该类是一次性的,您需要调用它的 publicDispose()
方法。这将删除自动化事件处理程序以及您已订阅的公共事件的调用列表中的所有事件。这样,您就可以使用 Lambda 来订阅事件。
使用 Field 来存储此类的实例。在需要时创建实例,并传递您感兴趣的进程名称列表。
订阅ProcessStarted
event.
当这些进程中的一个打开一个新窗口时,您将收到一条通知,并且可以执行父子关系:
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:
NOTE: UI 自动化组件 https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation是一部分Windows Presentation Framework
.
当 WinForms 应用程序中引用这些程序集之一时,WinForms 应用程序将变为 DpiAware (SystemAware
),如果还不是 DpiAware。
这可能会对一个或多个并非设计用于处理 Dpi 感知更改和通知的表单的布局产生影响。
需要项目参考:
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];
}
}
}
}
}