7

私はWindows8用のWindowsストアアプリのおもちゃのアプリケーションを書いています。これには、が付いたxamlページが1つだけありますTextBlock。このページには、次のようなクラスMyTimerがありますDataContext

this.DataContext = new MyTimer();

MyTimerを実装INotifyPropertyChangedし、プロパティの更新はTimeタイマーで行われます。

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

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

時間にTextBlockデータバインディングがあります

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

アプリケーションを実行すると、次の例外が発生します。

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

メッセージ付き

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

本当の問題は、GUI自体ではなく、クラスMyTimerのプロパティを更新していることです。それを理解することはできませんが、ソリューションではこのようなものを使用する必要があると思います。

4

3 に答える 3

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"));
                });
        }
    }
}
于 2012-03-29T21:24:08.793 に答える
5

これを行う1つの方法は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では)。

于 2012-03-30T17:16:22.893 に答える
1

このブログのコードはどうですか?

http://metrowindows8.blogspot.in/2011/10/metro-tiles.html

これは私のために働いた。ThreadPoolTimerオブジェクトをデリゲート関数に渡す必要がありました

于 2012-09-17T11:53:13.067 に答える