我正在尝试使用SetWinEventHook检测AIMP音乐播放器的窗口标题更改,并且它可以工作,但是问题是,当我将鼠标悬停在按钮上方时(停止,播放,最小化等),它还检测到工具提示弹出窗口。在设置SetWinEventHook或在WinEventProc事件中将其过滤掉时,我想排除这些。有什么想法吗?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp2
{
using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
class NameChangeTracker
{
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
uint idThread, uint dwFlags);
[DllImport("user32.dll")]
static extern bool UnhookWinEvent(IntPtr hWinEventHook);
const uint EVENT_OBJECT_NAMECHANGE = 0x800C;
const uint WINEVENT_OUTOFCONTEXT = 0;
// Need to ensure delegate is not collected while we're using it,
// storing it in a class field is simplest way to do this.
static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);
public static void Main()
{
// Listen for name change changes across all processes/threads on current desktop...
IntPtr hhook = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, IntPtr.Zero,
procDelegate, (uint)Process.GetProcessesByName("AIMP").FirstOrDefault().Id, 0, WINEVENT_OUTOFCONTEXT);
MessageBox.Show("Tracking name changes on HWNDs, close message box to exit.");
UnhookWinEvent(hhook);
}
static void WinEventProc(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
// filter out non-HWND namechanges... (eg. items within a listbox)
if (idObject != 0 || idChild != 0)
{
return;
}
if (Process.GetProcessesByName("AIMP").FirstOrDefault().MainWindowHandle.ToInt32() == hwnd.ToInt32())
{
Console.WriteLine("Current song: " + Process.GetProcessesByName("AIMP").FirstOrDefault().MainWindowTitle);
}
}
}
}
输出:
Current song: Michael Jackson - Speed Demon
Current song: Minimize
答案 0 :(得分:0)
问题目标已从使用Hooks(SetWinEventHook
)的解决方案更改为UI Automation。
由于您以前从未使用过UI Automation,所以这可能是一场牛仔竞技表演,因此,我将尝试解释为某些可能对这项任务有用的事件添加Automation Event处理程序的过程。 / p>
手头的任务:
当以下属性的状态时,需要通知您的程序 应用程序的UI元素(在这种情况下为
TitleBar
值) 变化。
首先,您可能想知道程序启动时目标应用程序是否已在运行。
我们可以使用Process.GetProcessesByName()来确定应用程序进程是否处于活动状态。
element in the UI Automation tree
)关联。 注意:
我们无法将目标主窗口与特定的自动化相关联 设置检测应用程序的事件处理程序时的元素 主窗口创建。
我们可以 AutomationElement.FromHandle([Handle])方法,使用句柄 由Process.MainWindowHandle返回。但是这种自动化 元素将严格绑定到特定的流程实例,因此 特定的Process.Id
。如果目标应用程序已关闭,并且 重新打开后,其Process.Id
将有所不同,并且事件处理程序 不会识别它。
UI自动化提供了Event Handlers和Patterns,可用于跟踪所有描述的事件。
在应用程序启动时检测:
我们需要使用AutomationEventHandler设置一个Automation.AddAutomationEventHandler委托,该委托在创建Window时引发一个事件。
AddAutomationEventHandler
要求:
Automation Event
的类型Automation Element
Automation Element
或扩展到其所有祖先和后代元素。 事件类型由WindowPattern.WindowOpenedEvent字段提供。
自动化元素可以是特定元素,也可以是 RootElement
(先前描述)。
范围由TreeScope枚举提供:它可以是元素本身( TreeScope.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
对象;在以下位置需要缓存的元素 有些情况。
我特意跳过此功能,以保持其为 简单(简短)。
没有元素,模式和 无论如何,这里讨论的属性值严格地需要缓存。
在属性值更改时检测:
使用 AutomationPropertyChangedEventHandler
通知属性更改,要求:
TreeScope.Element
):我们只想跟踪其属性之一,不涉及任何后代。 AutomationPropertyChangedEventHandler
委托(标准委托)可以使用RootElement
(主窗口)FindFirst()方法来确定自动化元素:我们需要指定搜索到的元素是后代(TreeScope.Descendants
),并指定用于匹配元素。
PropertyCondition
是ControlTypeProperty,而ControlType是 ControlType.TitleBar
。 “文档”列出了此类的所有预定义Automation Identifiers。
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 Automation Control Types。
样本测试代码:
我使用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
或Window
)关闭时,删除仍处于活动状态的自动化事件处理程序:
Automation.RemoveAllEventHandlers();