I'm trying to detect window title changes of AIMP music player using the SetWinEventHook and it works, the problem is that it also detects Tooltip popups when I hover over buttons with mouse (stop, play, minimize, etc).
I would like to exclude these when setting the SetWinEventHook or filter it out in the WinEventProc event.
Any ideas?
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
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);
}
}
}
outputs:
Current song: Michael Jackson - Speed Demon
Current song: Minimize
Question objective changed, as discussed in the comments, from a solution using Hooks (
SetWinEventHook) to a UI Automation one.Since you've never used UI Automation before, this could be a rodeo, so I'll try to explain the process of adding Automation Event handlers for some type of events that can be useful for this task.
The task at hand:
First of all, you probably want to know whether the target application is already running when your program starts.
We can use Process.GetProcessesByName() to determine if an application process is active.
element in the UI Automation tree).Note:
Our program might need to make some decisions based on the status of the target application.
Or simply notify the User and/or update its own UI.
UI Automation provides Event Handlers and Patterns that can be used to track all the described events.
Detect when application starts:
We need to set an AutomationEventHandler delegate, using Automation.AddAutomationEventHandler, that raises an event when a Window is created.
The
AddAutomationEventHandlerrequires:Automation Eventthat will be handledAutomation Elementthat is associated with the eventAutomation Elementspecified or extended to all its ancestors and descendants elements.The Event type is provided by the WindowPattern.WindowOpenedEvent field.
The Automation Element can be a specific Element or the
RootElement(previously described).The Scope is provided by the TreeScope enumeration: it can be the Element itself (
TreeScope.Element) or all the subtree of the specified Element (TreeScope.Subtree). We're using the latter in this case, it's required when referencing theRootElementin this context.The method delegate is a standard event handler delegate:
Detect when application closes:
Same as above, except the
eventIdis provided by a WindowPattern.WindowClosedEvent field instead.Note:
Detect when a property value changes:
A property change is notified using a
AutomationPropertyChangedEventHandler, which requires:TreeScope.Element): we only want to track one of its properties, no descendants are involved.AutomationPropertyChangedEventHandlerdelegate that will handle the event (standard delegate)The Automation Element can be determined using the
RootElement(Main Window) FindFirst() method: we need to specify that the searched Element is a descendant (TreeScope.Descendants) and the criteria used to match the Element.PropertyConditionis then a ControlTypeProperty and the ControlType isControlType.TitleBar.The Docs list all the pre-defined Automation Identifiers for this class.
See also: UI Automation Control Types.
Sample Test Code:
I'm using Windows Notepad as the target Application to track. It can be any other application.
Also, I'm using the Application Class Name to identify it. It could be any other know detail that can single it out.
This code requires a Project reference to:
UIAutomationClient
UIAutomationTypes
When the application (or the
FormorWindow) closes, remove the Automation Event Handlers still active: