2017-01-18 25 views
7

Bir sürecin içinden otomasyon olaylarını dinlerken sorun yaşıyorum. Aşağıda tek bir düğmeyle basit bir WPF uygulamasının olduğu bir örnek yazdım. TreeScope: Descendants ile pencerede Invoke olayı için bir otomasyon işleyicisi eklenir. UI Otomasyon olayları iki kez yükseltildi

Invoked:20009 
Clicked! 
Invoked:20009 

Neden çağrılan olay iki kez işlenir: Ben düğmesini tıkladığında

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

, bu ne alıyorum?

Görevlerimi kaldırırsam, yalnızca istediğim gibi alıyorum, ancak UI iş parçacığından otomasyon kodunu çağırmamanız gereken birkaç yeri okudum (ör. https://msdn.microsoft.com/en-us/library/ms788709(v=vs.110).aspx). Gerçek kodda bunu yapmak benim için de pratik değildir.

Bu örnekte UIAComWrapper kitaplığını kullanıyorum, ancak aynı davranışı UIAutomationClient kitaplığının hem yönetilen hem de COM sürümüyle elde ediyorum.

+0

Sorunu üretebilir. Ancak, başka bir süreçten üretemiyorum (sadece bir olay ortaya çıktı). Bir hata olabilir, ancak UIA işlem dışı müşteriler için tasarlandı, bunu neden aynı işlemden yapmak istersiniz? –

+1

VSTO ile kullanım için. Excel API, tüm buton tıklamalarına erişim sağlamaz. Otomasyon bunları tespit etmek için iyi çalışır, ancak iki kez gelirler. – jan

+0

Tamam. Pekala, bulabildiğim tek kötü çözüm 1) zaman olaylarıdır, 2) kaynağın aynı nesne olup olmadığını belirleme (olay kaynağının GetHashCode'unu karşılaştıran - bir OtomasyonElementi - burada mantıklı görünüyor) 3) Eğer iki olay gerçekleşirse x milisaniye sonra ikiye katlandığını beyan ederiz. –

cevap

1

İlk başta gördüğümüz bir olay bulan köpürme olabileceğini düşündüm, bu yüzden olarakdökülmüş bir değişken, ikinci çağrının düğmeden gelip gelmediğini göstermek için işleyici lambdaya eklenmiştir. @Simon Mourier, sonuç: evet değerler aynıdır) ve onun kurucu etiketinden veya görsel ağacın yukarı ya da aşağıdan başka bir şeyden değil.

Bundan sonra karar verildiğinde, iki geri bildirimin çağrı yığınlarına daha yakından bakıldığında, iş parçacığıyla ilgili hipotezi destekleyen bir şey ortaya çıkar. UIAComWrapper'ı kaynaktan derledim, kaynaktan derledim ve kaynak sunucu sembolleri ile debug edilmiş ve yerel olarak.

bu ilk geri arama çağrı yığını: Bu çıkış noktası mesajı pompa gösterir,

call stack in first invokation

. Çekirdek WndProc, neredeyse tüm Windows sürümlerinin bir özetinde, neredeyse bir sol fare olarak kod çözme, düğme sınıfının OnClick() işleyicide sona ermesine kadar, inanılmaz derecede kalın bir çerçeve nesnesi tabakası ile bu kabarcıklar arasında abone otomasyon olayı yükseltildi ve lamdamıza yönlendirildi. Şimdiye kadar beklenmedik bir şey yok.

ve bu ikinci geri arama çağrı yığını:

call stack in second callbacl

Bu ikinci geri UIAutomationCore bir yapay olduğunu ortaya koymaktadır. Ve: kullanıcı iş parçacığı üzerinde değil, kullanıcı iş parçacığı üzerinde çalışır. Bu nedenle, abone olan her parçanın bir kopyasını almasını ve UI iş parçacığının her zaman olmasını sağlayan bir mekanizma vardır.

Ne yazık ki, lambda'da ortaya çıkan tüm argümanlar birinci ve ikinci aramalar için aynıdır. Çağrı yığınlarını karşılaştırmak mümkün olsa da, zamanlama/sayma olaylarından bile daha kötü bir çözüm olacaktır.

Ama: Sen ipliğine olayları filtrelemek ve bunlardan yalnızca bir tüketebilir: çıktı penceresinde

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

Sonucu:

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, UI iş parçacığını çağırmak için bir yöntemdir; – jan

+0

Haklısınız - bu saçmalıktı (lütuf için çok aç). Cevabı bir - umarım - daha yararlı bir analizle güncelledim. – dlatikay

+0

Harika bul. Konu kontrolü benim için çözdü. Daire durumunu Dispatcher hilesi yerine kontrol ettim, çünkü bir VSTO. Derin dalış için teşekkürler – jan