2017-01-18 8 views
7

プロセス内からオートメーションイベントを受信するのに問題があります。私は単一のボタンを持つ単純なWPFアプリケーションがある場所の下にサンプルを作成しました。 TreeScope:Descendantsを使用して、Invokeイベントのオートメーションハンドラがウィンドウに追加されます。UIオートメーションイベントが2回発生する

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

私はボタンをクリックすると、これは私が得るものです:

Invoked:20009 
Clicked! 
Invoked:20009 

に呼び出さイベントが二回処理されるのはなぜ?

Task.Runを削除した場合、一度しか表示されませんが、UIスレッド(例:https://msdn.microsoft.com/en-us/library/ms788709(v=vs.110).aspx)からオートメーションコードを呼び出さないでください。実際のコードでそうすることも実用的ではありません。

このサンプルではUIAComWrapperライブラリを使用していますが、UIAutomationClientライブラリの管理バージョンとCOMバージョンの両方で同じ動作が発生します。

+0

私は問題を再現することができます。しかし、私は別のプロセスからそれを再現することはできません(1つのイベントだけが発生します)。これはバグかもしれませんが、UIAはアウトオブプロセスクライアント向けに設計されています。なぜ同じプロセスからこれをやりたいのですか? –

+1

VSTOで使用します。 ExcelのAPIはすべてのボタンのクリックにアクセスするわけではありません。自動化はこれらの検出にはうまくいくが、2回来る。 – jan

+0

さて、私が見つけることができる唯一の貧弱な解決策は、1)時間イベント、2)ソースが同じオブジェクトであるかどうかを判断すること(イベントソースのGetHashCodeを比較する - これはAutomationElementです - ここで合理的です)3) xミリ秒、それが倍増したと宣言します。 –

答えて

1

最初は私たちが見るようなイベントバブリングかもしれないと思っていたので、sという変数がキャッチされたAutomationElementがハンドラのラムダの中に追加されて、ボタンからも2番目の呼び出しが来るかどうかを示します@Simon Mourierの結果:はい値は同一)、その構成ラベルからではなく、ビジュアルツリーの上下にあるものではありません。

これが除外された後、2つのコールバックのコールスタックを詳しく見ると、スレッド関連の仮説をサポートするものが明らかになりました。 gitからUIAComWrapperをダウンロードし、ソースからコンパイルし、ソースサーバーのシンボルとネイティブをデバッグしました。

これは、最初のコールバックのコールスタックである:

call stack in first invokation

それは原点がメッセージポンプであることを、示しています。 WndProcのコアはフレームワークの非常に厚いレイヤーを泡立てて、ほとんどすべてのWindowsバージョンの要約で、ボタンクラスのOnClick()ハンドラで終わるまで左マウスのように慎重にデコードします。サブスクライブされたオートメーションイベントが発生し、私たちのラムダに向けられます。これまで予想外だったことはありません。

そして、これは2番目のコールバックでコールスタックは、次のとおりです。

call stack in second callbacl

これは、2番目のコールバックがUIAutomationCoreの人工物であることが明らかになりました。 UIスレッドではなくユーザースレッドで実行されます。したがって、購読しているすべてのスレッドがコピーを取得し、UIスレッドは常にそうすることを確実にするメカニズムがあるようです。

残念ながら、ラムダで終わるすべての引数は、1回目と2回目の呼び出しで同じです。可能であればコールスタックを比較することは、タイミング/カウントイベントよりもさらに悪い解決策になります。

しかし:あなたがスレッドでイベントをフィルタ、およびそれらの一方のみを消費することができます出力ウィンドウに

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

結果:

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スレッドを呼び出す方法です。 – jan

+0

あなたは正しいです。それはナンセンスでした(賞金にはあまりにも飢えていました)。私は、うまくいけばもっと有用な分析で答えを更新しました。 – dlatikay

+0

素晴らしい発見。スレッドチェックは私のためにそれを解決しました。 Dispatcherのトリックではなく、Apartmentの状態をチェックしました.VSTOだからです。深いダイビングをありがとう – jan

関連する問題