正如评论中所讨论的,问题目标已从使用 Hook ( SetWinEventHook
) 的解决方案更改为 UI 自动化解决方案。
由于您以前从未使用过UI 自动化,因此这可能是一场牛仔竞技表演,因此我将尝试解释为某些类型的事件添加自动化事件处理程序的过程,这些事件可能对此任务有用。
手头的任务:
当应用程序的 UI 元素的属性(在本例中为值)的状态发生变化时,需要通知您的程序。TitleBar
首先,您可能想知道程序启动时目标应用程序是否已经在运行。
我们可以使用Process.GetProcessesByName()来确定应用程序进程是否处于活动状态。
- 目标应用程序主窗口需要与一个AutomationElement(用于标识 UI 对象的自动化对象 - 换句话说,一个
element in the UI Automation tree
)相关联。
注意:
在设置检测应用程序主窗口创建的事件处理程序时,我们无法将目标主窗口与特定自动化元素相关联。
我们可以通过
AutomationElement.FromHandle([Handle])方法,使用Process.MainWindowHandle返回的 Handle 。但是这个自动化元素将严格绑定到一个特定的 Process 实例,因此是一个特定的Process.Id
. 如果目标应用程序关闭并重新打开,它Process.Id
会有所不同,事件处理程序将无法识别它。
UI 自动化提供事件处理程序和模式,可用于跟踪所有描述的事件。
检测应用程序何时启动:
我们需要使用Automation.AddAutomationEventHandler设置一个AutomationEventHandler委托, 它会在创建 Window 时引发一个事件。
AddAutomationEventHandler
要求 :
Automation Event
将被处理 的类型
Automation Element
与事件相关联 的
- 事件的范围。范围可以限制为
Automation Element
指定或扩展到其所有祖先和后代元素。
- 引发事件时将调用的方法委托
事件类型由WindowPattern.WindowOpenedEvent字段提供。
自动化元素可以是特定元素或RootElement
(前面描述的)。
Scope 由TreeScope枚举提供:它可以是 Element 本身(TreeScope.Element
)或指定 Element 的所有子树(TreeScope.Subtree
)。在这种情况下,我们使用后者,在这种情况下引用时需要它RootElement
。
方法委托是标准事件处理程序委托:
AutomationElement TargetElement = AutomationElement.RootElement;
AutomationEventHandler WindowOpenedHandler = null;
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, TargetElement,
TreeScope.Subtree, WindowOpenedHandler = new AutomationEventHandler(OnTargetOpened));
public void OnTargetOpened(object source, AutomationEventArgs e)
{
AutomationElement element = source as AutomationElement;
}
检测应用程序何时关闭:
与上面相同,只是eventId
由WindowPattern.WindowClosedEvent字段提供。
注意:
一些元素和属性应该被缓存和访问,激活预定义的CacheRequest:并非所有 UIA 值都可以使用Element.Current
对象访问;在某些情况下需要缓存元素。
我故意跳过此功能以使其尽可能简单(和简短)。
无论如何,这里讨论的元素、模式和属性值都不需要缓存。
检测属性值何时更改:
使用 a 通知属性更改AutomationPropertyChangedEventHandler
,这需要:
自动化元素可以使用RootElement
(主窗口)FindFirst()方法确定:我们需要指定搜索到的元素是后代 ( TreeScope.Descendants
) 以及用于匹配该元素的条件。
文档列出了此类 的所有预定义自动化标识符。
AutomationPropertyChangedEventHandler TargetTitleBarHandler = null;
Condition titleBarCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TitleBar);
TitleBarElement = RootElement.FindFirst(TreeScope.Descendants, titleBarCondition);
Automation.AddAutomationPropertyChangedEventHandler(TitleBarElement, TreeScope.Element,
TargetTitleBarHandler = new AutomationPropertyChangedEventHandler(OnTargetTitleBarChange),
AutomationElement.NameProperty);
public void OnTargetTitleBarChange(object source, AutomationPropertyChangedEventArgs e)
{
if (e.Property == AutomationElement.NameProperty) { }
}
另请参阅:UI 自动化控件类型。
示例测试代码:
我使用 Windows记事本作为跟踪的目标应用程序。它可以是任何其他应用程序。
另外,我使用应用程序类名称来识别它。它可能是任何其他可以将其挑出来的已知细节。
此代码需要对以下项目的项目引用:
UIAutomationClient
UIAutomationTypes
using System.Windows.Automation;
AutomationEventHandler NotepadHandlerOpen = null;
AutomationEventHandler NotepadHandlerClose = null;
AutomationPropertyChangedEventHandler NotepadTitleBarHandler = null;
AutomationElement NotepadElement = AutomationElement.RootElement;
AutomationElement TitleBarElement = null;
//-----------------------------------------------------------------------------------
// This section of code can be inserted in the app start, Form/Window constructor
// or the event handler of a controls (a Button.Cick maybe)
//-----------------------------------------------------------------------------------
using (Process NotepadProc = Process.GetProcessesByName("notepad").FirstOrDefault())
{
try
{
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, NotepadElement,
TreeScope.Subtree, NotepadHandlerOpen = new AutomationEventHandler(OnNotepadStart));
}
finally
{
if (NotepadProc != null)
this.BeginInvoke(NotepadHandlerOpen,
AutomationElement.FromHandle(NotepadProc.MainWindowHandle),
new AutomationEventArgs(WindowPattern.WindowOpenedEvent));
}
}
//-----------------------------------------------------------------------------------
public void OnNotepadStart(object source, AutomationEventArgs e)
{
AutomationElement element = source as AutomationElement;
if (e.EventId == WindowPattern.WindowOpenedEvent && element.Current.ClassName.Contains("Notepad"))
{
NotepadElement = element;
Console.WriteLine("Notepad is now opened");
Condition titleBarCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TitleBar);
TitleBarElement = NotepadElement.FindFirst(TreeScope.Descendants, titleBarCondition);
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, NotepadElement,
TreeScope.Element, NotepadHandlerClose = new AutomationEventHandler(OnNotepadClose));
Automation.AddAutomationPropertyChangedEventHandler(TitleBarElement, TreeScope.Element,
NotepadTitleBarHandler = new AutomationPropertyChangedEventHandler(OnNotepadTitleBarChange),
AutomationElement.NameProperty);
}
}
public void OnNotepadClose(object source, AutomationEventArgs e)
{
if (e.EventId == WindowPattern.WindowClosedEvent)
{
Console.WriteLine("Notepad is now closed");
Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent, NotepadElement, NotepadHandlerClose);
Automation.RemoveAutomationPropertyChangedEventHandler(TitleBarElement, NotepadTitleBarHandler);
}
}
public void OnNotepadTitleBarChange(object source, AutomationPropertyChangedEventArgs e)
{
if (e.Property == AutomationElement.NameProperty)
{
Console.WriteLine($"New TitleBar value: {e.NewValue}");
}
}
当应用程序(或Form
or Window
)关闭时,删除仍处于活动状态的自动化事件处理程序:
Automation.RemoveAllEventHandlers();