2012-03-29 10 views
7

私は、Windows Store App ToyアプリケーションをWindows 8用に作成しています。 TextBlockのxamlページが1つだけあります。ページはDataContextとしてクラスMyTimerがありますWindowsストアのApp UIの更新

this.DataContext = new MyTimer(); 

MyTimerINotifyPropertyChangedを実装し、プロパティTimeの更新はタイマーで作られています:

private void NotifyTimeChanged(){ 
    if (this.PropertyChanged != null){ 
     this.PropertyChanged(this, new PropertyChangedEventArgs("Time")); 
    } 
} 

public MyTimer(){ 
    TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged); 
    TimeSpan period = new TimeSpan(0, 0, 1); 
    ThreadPoolTimer.CreatePeriodicTimer(f, period); 
} 

TextBlockあり時間のデータバインディング

<TextBlock Text="{Binding Time}" /> 

私は次の例外を持っているアプリケーションを実行する場合:メッセージ

The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

System.Runtime.InteropServices.COMException was unhandled by user code 

を本当の問題は、私はクラスMyTimerのプロパティを更新していますということですGUI自体ではなく、 私はそれを理解することはできませんが、解決策はthis oneのようなものを使うべきだと私は思う。

答えて

7

はい、UIスレッドではなくスレッドプールスレッドからプロパティの変更を通知しています。タイマーコールバックで通知をUIスレッドにマーシャリングする必要があります。さて、あなたのビューモデルはあなたのビューから分離されています(良いことです)ので、Dispatcherインフラストラクチャへの直接リンクはありません。だからあなたがしたいことは、通信する適切なSynchronizationContextを手渡すことです。これを行うには、構築中に現在のSynchronizationContextをキャプチャするか、テストに適したコンストラクタに明示的に渡すか、またはUIスレッドからオブジェクトを初期化する場合は、明示的に渡す必要があります。

全体シェバングは次のようになります。これを行うには

public class MyTimer 
{ 
    private SynchronizationContext synchronizationContext; 

    public MyTimer() : this(SynchronizationContext.Current) 
    { 
    } 

    public MyTimer(SynchronizationContext synchronizationContext) 
    { 
     if(this.synchronizationContext == null) 
     { 
      throw new ArgumentNullException("No synchronization context was specified and no default synchronization context was found.") 
     } 

     TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged); 
     TimeSpan period = new TimeSpan(0, 0, 1); 
     ThreadPoolTimer.CreatePeriodicTimer(f, period); 
    } 

    private void NotifyTimeChanged() 
    { 
     if(this.PropertyChanged != null) 
     { 
      this.synchronizationContext.Post(() => 
       { 
        this.PropertyChanged(this, new PropertyChangedEventArgs("Time")); 
       }); 
     } 
    } 
} 
+0

ありがとうございます。これは私が非同期呼び出しを使うたびにSynchronizationContextを使う必要があることを示唆しています...私はasyncキーワードでもそういうことをするつもりです。 – Gabber

+0

C#4.5のawaitキーワードを使用している場合は、デフォルトでこれを取得します。 –

+0

答えをありがとう!私はちょうどサイドローを追加したい:あなたがUIのコンテキストでコールバックしたい場合は、UIがビルドされたときにすぐに 'SynchronizationContext'をキャプチャしてください(私は' OnLaunched'イベントハンドラを使って行います)。そうでなければあなたがつかむコンテキストは役立たないでしょう。さらに読む:http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I – Lvsti

5

一つの方法ではなく、タイマーを使用してのループでTask.Delay()を待っている:

class MyTimer : INotifyPropertyChanged 
{ 
    public MyTimer() 
    { 
     Start(); 
    } 

    private async void Start() 
    { 
     while (true) 
     { 
      await Task.Delay(TimeSpan.FromSeconds(1)); 
      PropertyChanged(this, new PropertyChangedEventArgs("Time")); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged = delegate { }; 

    public DateTime Time { get { return DateTime.Now; } } 
} 

あなたがコンストラクタを呼び出す場合UIスレッドでは、PropertyChangedも呼び出されます。そして素晴らしいことは、まったく同じコードがWPFでも同様に動作することです(.Net 4.5とC#5の下で)。

+0

このコードは、動作しませんが、IME Task.Delayはあまり高い解像度を持っていません。 WPF、Win8、WP8テストでは、CancellationTokenSource.CancelAfterを8分に設定し、UIは7分56秒にしかなりませんでした。 – Stonetip

+0

@Stonetip確かに1秒以上の分解能があるはずです。私の推測では、あなたのコードに何か他のことが起こっていると思います。また、 'Task.Delay()'は 'CacelAfter()'ではありませんが、同じ解決策があると思います。 – svick

+0

Task.Delayは1秒よりも優れた解像度を持っていますが、それはミリ秒単位でスポット・オンになると予想します。WPF、Win8、Win Phone 8のテストでは、毎分0.5秒。しかし、後者の2つの環境でThreadPoolTimerクラスがうまく動作することがわかりました。 – Stonetip