如何获取作为我的表单父级的 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 assemblies 是 Windows 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];
}
}
}
}
}
我有一个 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 assemblies 是 Windows 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];
}
}
}
}
}