2017-01-18 42 views
7

Mam problemy z odsłuchiwaniem zdarzeń automatyzacji od wewnątrz procesu. Poniżej napisałem próbkę, w której mam prostą aplikację WPF za pomocą jednego przycisku. Dodano obsługę automatyzacji dla zdarzenia Invoke w oknie z TreeScope: Descendants.Zdarzenia automatyzacji interfejsu użytkownika są dwukrotnie zgłaszane

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!"); 
} 

Kiedy klikam przycisk, to co mam:

Invoked:20009 
Clicked! 
Invoked:20009 

Dlaczego jest zdarzenie wywoływane obsługiwane dwa razy?

Jeśli usunę zadanie.Run I otrzymam je tylko raz, jak chcę, ale czytałem kilka miejsc, które nie powinny wywoływać kodu automatyzacji z wątku UI (np. https://msdn.microsoft.com/en-us/library/ms788709(v=vs.110).aspx). Jest to również niepraktyczne, ponieważ robię to w prawdziwym kodzie.

Używam biblioteki UIAComWrapper w tym przykładzie, ale to samo zachowanie występuje zarówno w wersji zarządzanej, jak i COM biblioteki UIAutomationClient.

+0

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? –

+1

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

+0

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. –

Odpowiedz

1

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:

call stack in first invokation

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:

call stack in second callbacl

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 
+0

Dispatcher.Invoke to sposób wywoływania wątku interfejsu użytkownika, którego właśnie chcę uniknąć. – jan

+0

Masz rację - to był nonsens (zbyt głodny nagrody). Zaktualizowałem odpowiedź z - mam nadzieję - bardziej użyteczną analizą. – dlatikay

+0

Świetne znalezisko. Wątek sprawdził mi to. Sprawdziłem stan mieszkania zamiast trików Dispatchera, ponieważ jest to VSTO. Dziękuję za głębokie nurkowanie – jan

Powiązane problemy