Na początku myślałem, że to może być jakieś bulgotanie zdarzeń widzimy, więc zmienna z s
lanego jako AutomationElement
został dodany wewnątrz lambda procedury obsługi, aby zobaczyć, czy druga invokation również pochodzi z przycisku (zgodnie z komentarzem @Simon Mourier, wynik: tak, wartości są identyczne), a nie od jego etykiety składowej lub cokolwiek innego w górę lub w dół wizualnego drzewa.
Po tym, jak zostało to wykluczone, bliższe spojrzenie na stosy wywołania dwóch wywołań zwrotnych ujawniło coś, co wspiera hipotezy związane z wątkami. Pobrałem UIAComWrapper z git, skompilowałem ze źródła i debugowałem za pomocą symboli serwera źródłowego i natywnie.
To stos wywołań w pierwszym zwrotnego:

To pokazuje, że punkt pochodzenia jest pompa wiadomość. Rdzeń WndProc wymyśla tę niesamowitą, grubą warstwę szkieletu, prawie w podsumowaniu wszystkich wersji Windows, dekonstruując go jak lewą myszką, aż do końca w obsłudze OnClick()
klasy przycisków, skąd zasubskrybowane wydarzenie automatyzacji zostało podniesione i skierowane na naszą Lamę. Nic nieoczekiwanego do tej pory.
I to jest stos wywołań w drugim zwrotnego:

To pokazuje, że druga zwrotna jest artefaktem z UIAutomationCore
. Oraz: działa na wątku użytkownika, a nie na wątku interfejsu użytkownika. Tak więc istnieje najwyraźniej mechanizm, który gwarantuje, że każdy wątek, który subskrybował, otrzymuje kopię, a wątek interfejsu zawsze działa.
Niestety, wszystkie argumenty, które kończą się w lambda, są identyczne dla pierwszego i drugiego połączenia. Porównanie zestawów połączeń, choć możliwe, byłoby rozwiązaniem jeszcze gorszym niż czas/liczenie zdarzeń.
Ale: Można filtrować zdarzenia przez gwint i spożywać tylko jeden z nich:
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!");
}
}
}
spowodować okna wyjściowego:
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
mogę odtworzyć problem. Nie mogę jednak odtworzyć go z innego procesu (zgłaszane jest tylko jedno zdarzenie). Może to być błąd, ale UIA została zaprojektowana dla klientów poza procesem, dlaczego chciałbyś zrobić to z tego samego procesu? –
Do użytku z VSTO. Excel API nie daje Ci dostępu do wszystkich kliknięć przycisków. Automatyka sprawdza się dobrze w wykrywaniu, ale kura przychodzi dwa razy. – jan
Ok. Cóż, jedynym złym rozwiązaniem, jakie mogę znaleźć, jest 1) zdarzenia czasowe, 2) określenie, czy źródło jest tym samym obiektem (porównanie GetHashCode źródła zdarzenia - które jest AutomationElement - wydaje się rozsądne tutaj) 3) jeśli dwa zdarzenia zachodzą w ciągu x milisekund, a następnie zadeklaruj, że jest podwojony. –