2012-03-29 148 views
7

我正在为Windows 8编写Windows应用商店应用玩具应用。 它只有一个xaml页面,其中包含TextBlock。该页面具有类MyTimer为DataContextWindows应用商店应用UI更新

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本身, 我无法弄清楚,但我认为解决方案应该使用类似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 2012-03-29 22:23:16

+0

是的,如果你使用C#4.5的关键字,你会默认得到这个。 – 2012-03-29 23:39:26

+0

感谢您的回答!我只想添加一个旁注:如果你想在UI上下文中回调,确保在UI已经建好的时候捕获'SynchronizationContext'(我使用'OnLaunched'事件处理器来完成它),否则您抓取的上下文将无法使用。进一步阅读:http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I – Lvsti 2012-09-25 12:48:59

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 2013-06-04 19:49:00

+0

@Stonetip它当然应该有比1秒更好的分辨率,我的猜测是你的代码还有其他的东西。此外,'Task.Delay()'不是'CacelAfter()',但我希望它们具有相同的分辨率。 – svick 2013-06-04 23:29:29

+0

Task.Delay具有比1秒更好的分辨率,但您希望它以毫秒为单位进行点亮,然后在WPF,Win8和Win Phone 8上进行的测试显示出每分钟高达0.5秒的不一致行为。然而,我发现ThreadPoolTimer类工作得很好的后两种环境。 – Stonetip 2013-06-07 08:21:34