7

我正在为 Windows 8 编写一个 Windows Store App 玩具应用程序。它只有一个带有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))

真正的问题是我正在更新类 MyTimer 的属性,而不是 GUI 本身,我无法弄清楚,但我认为解决方案应该使用类似这样的东西。

4

3 回答 3

7

是的,您是从线程池线程而不是 UI 线程通知属性更改。您需要在计时器回调中将通知编组回 UI 线程。现在,您的视图模型与您的视图分离(一件好事),因此它与Dispatcher基础设施没有直接链接。因此,您要做的就是交给它SynchronizationContext进行交流的正确方法。为此,您需要SynchronizationContext在构造过程中捕获电流,或者允许将其显式传递给一个有利于测试的构造函数,或者如果您从 UI 线程开始初始化对象。

整个shebang看起来像这样:

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

一种方法是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 回答