UI Automation Events Raise Twice

I'm having trouble listening to automation events inside a process. I wrote a sample below where I have a simple one-button WPF application. An automation handler is added for the Invoke event in the window using TreeScope: Descendants.

public MainWindow() { InitializeComponent(); Loaded += OnLoaded; } private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { IntPtr windowHandle = new WindowInteropHelper(this).Handle; Task.Run(() => { var element = AutomationElement.FromHandle(windowHandle); Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, element, TreeScope.Descendants, (s, a) => { Debug.WriteLine($"Invoked:{a.EventId.Id}"); }); }); } private void button_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("Clicked!"); } 

When I click the button, this is what I get:

 Invoked:20009 Clicked! Invoked:20009 

Why is the Invoked event handled twice?

If I delete Task.Run, I get it only once as I want, but I read several places that you should not call automation code from the user interface thread (for example, https://msdn.microsoft.com/en- us / library / ms788709 (v = vs. 110) .aspx ). It is also impractical for me to do this in real code.

I use the UIAComWrapper library in this example, but I get the same behavior with both the managed and COM versions of the UIAutomationClient library.

+7
c # white microsoft-ui-automation
source share
1 answer

At first, I thought it might be some kind of bubble event that we see, so the variable with s , presented as an AutomationElement , was added inside the lambda handler to show whether the second invokation will also select the button (according to the comment @Simon Mourier, result: yes the values ​​are identical), and not from its composite label or anything else up or down the visual tree.

After this was ruled out, a closer look at the stacks of calls of the two callbacks revealed something that supported the flow hypothesis. I downloaded UIAComWrapper from git, compiled from the source and debugged with the characters of the source server and turned on.

This is the call stack in the first callback:

call stack on first call

It shows that the starting point is the message pump. The core of WndProc washes it out due to the incredibly thick layer of the framework, almost in the reprogramme of all versions of Windows, ever, roughly decoding it as a left mouse button, until it appears in the OnClick() handler of the OnClick() class, from where the signed an automation event rises and heads towards our lamp. So far, nothing unexpected.

And this is the call stack in the second callback:

call stack in second callbacl

This shows that the second callback is an artifact of the UIAutomationCore . And: it works in the user thread, and not in the user interface thread. Thus, there is a mechanism that ensures that every thread that subscribes receives a copy, and the user interface thread always does.

Unfortunately, all the arguments that go into lambda are identical for the first and second calls. And comparing call stacks, although possible, would be a solution that is even worse than synchronization / counting events.

But: you can filter events by stream and consume only one of them:

 using System; using System.Diagnostics; using System.Threading.Tasks; using System.Windows; using System.Windows.Automation; using System.Windows.Interop; using System.Threading; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs routedEventArgs) { IntPtr windowHandle = new WindowInteropHelper(this).Handle; Task.Run(() => { var element = AutomationElement.FromHandle(windowHandle); Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, element, TreeScope.Descendants, (s, a) => { var ele = s as AutomationElement; var invokingthread = Thread.CurrentThread; Debug.WriteLine($"Invoked on {invokingthread.ManagedThreadId} for {ele}, event # {a.EventId.Id}"); /* detect if this is the UI thread or not, * reference: http://stackoverflow.com/a/14280425/1132334 */ if (System.Windows.Threading.Dispatcher.FromThread(invokingthread) == null) { Debug.WriteLine("2nd: this is the event we would be waiting for"); } else { Debug.WriteLine("1st: this is the event raised on the UI thread"); } }); }); } private void button_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("Clicked!"); } } } 

Result in the output window:

 Invoked on 1 for System.Windows.Automation.AutomationElement, event # 20009 1st: this is the event raised on the UI thread Invoked on 9 for System.Windows.Automation.AutomationElement, event # 20009 2nd: this is the event we would be waiting for 
+1
source share

All Articles