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:
Your program needs to be notified when the status of a property of
an UI Element (in this case, a TitleBar
value) of an Application
changes.
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.
- The target application Main Window needs to be associated with a AutomationElement (the Automation object used to identify an UI object - in other words, an
element in the UI Automation tree
).
Note:
We cannot associate the target Main Window with a specific Automation
Element when setting up an event handler that detects an Application
main Window creation.
We could, with the
AutomationElement.FromHandle([Handle]) method, using the Handle
returned by Process.MainWindowHandle. But this Automation
Element will be strictly tied to a specific Process instance, thus a
specific Process.Id
. If the target Application is closed and
reopened, its Process.Id
would be different and the Event Handler
will not recognize it.
- We need to associate the Event Handler that detects a Window creation with the
AutomationElement.RootElement, representing the root element of the current Desktop (any UI element, or Window, in practice), then determine if it's the Main Windows of the target Application, inspecting some relevant property of the Automation Element provided by the Event as the source object (as any standard event). In the sample code, I'm using the Element.Current.ClassName.
- Since the target application can be closed at some point, we need to be notified when this happen, too.
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.
- The target application can be opened and closed over and over during the life-time of the program. We will need to track these changes over time.
- When a property value is changed, we can receive a notification using a AutomationPropertyChangedEventHandler. This event is raised when a specific property of a defined Automation Element or Element Type changes (see the event type descriptions that follow).
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 AddAutomationEventHandler
requires:
- The type of
Automation Event
that will be handled
- The
Automation Element
that is associated with the event
- The scope of Event. The scope can be limited to the
Automation Element
specified or extended to all its ancestors and descendants elements.
- The method delegate that will be called when the event is raised
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 the RootElement
in this context.
The method delegate is a standard event handler delegate:
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;
}
Detect when application closes:
Same as above, except the eventId
is provided by a WindowPattern.WindowClosedEvent field instead.
Note:
Some Elements and Properties should be cached and accessed activating
a pre-defined CacheRequest: not all UIA values can be accessed
using the Element.Current
object; a cached Element is required in
some cases.
I'm deliberately skipping this feature to keep this as
simple (and short) as possible.
None of the Elements, Patterns and
Property values discussed here strictly need caching, anyway.
Detect when a property value changes:
A property change is notified using a AutomationPropertyChangedEventHandler
, which requires:
- The Automation Element with which we want to associate the event handler.
- A Scope for the event; in this case, the scope is the Element itself (
TreeScope.Element
): we only want to track one of its properties, no descendants are involved.
- An
AutomationPropertyChangedEventHandler
delegate that will handle the event (standard delegate)
- One or more UI Automation properties we're interested in.
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.
The Docs list all the pre-defined Automation Identifiers for this class.
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) { }
}
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
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}");
}
}
When the application (or the Form
or Window
) closes, remove the Automation Event Handlers still active:
Automation.RemoveAllEventHandlers();