2012-03-29 39 views
7

Tôi đang viết ứng dụng đồ chơi Windows Store App cho Windows 8. Nó chỉ có một trang xaml với TextBlock. Các trang có MyTimer lớp như DataContext:Cập nhật giao diện người dùng ứng dụng Windows Store

this.DataContext = new MyTimer(); 

MyTimer thực hiện INotifyPropertyChanged và cập nhật tài sản Time được thực hiện với một bộ đếm thời gian:

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

với

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

các TextBlock có dữ liệu về Thời gian

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

Khi tôi chạy các ứng dụng tôi có ngoại lệ sau đây:

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

Với thông điệp

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

Vấn đề thực sự là tôi đang cập nhật các tài sản của MyTimer lớp, không phải là GUI, Tôi không thể tìm ra, nhưng tôi nghĩ rằng giải pháp nên sử dụng một cái gì đó như this one.

Trả lời

7

Có, bạn đang thông báo cho các thay đổi về thuộc tính từ chuỗi chủ đề chuỗi thay vì chuỗi giao diện người dùng. Bạn cần sắp xếp lại thông báo về chuỗi giao diện người dùng trong cuộc gọi lại hẹn giờ. Giờ đây, mô hình chế độ xem của bạn được tách riêng khỏi chế độ xem của bạn (một điều tốt) do đó, mô hình này không có liên kết trực tiếp đến cơ sở hạ tầng Dispatcher. Vì vậy, những gì bạn muốn làm là trao cho nó SynchronizationContext thích hợp để liên lạc. Để thực hiện điều này, bạn cần chụp SynchronizationContext hiện tại trong quá trình xây dựng hoặc cho phép nó được truyền một cách rõ ràng tới một hàm tạo tốt cho các thử nghiệm hoặc nếu bạn đang khởi tạo đối tượng ra khỏi chuỗi giao diện người dùng để bắt đầu.

Toàn bộ công việc sẽ giống như thế này:

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

Cảm ơn rất nhiều. Điều này cho thấy tôi, tôi sẽ cần phải sử dụng SynchronizationContext mỗi khi tôi sử dụng một cuộc gọi không đồng bộ ... Tôi sẽ suy nghĩ về làm một cái gì đó như thế với từ khóa async quá. – Gabber

+0

Vâng, nếu bạn đang sử dụng từ khóa chờ đợi của C# 4.5, bạn sẽ nhận được điều này theo mặc định. –

+0

Cảm ơn bạn đã trả lời!Tôi chỉ muốn thêm một sidenote: nếu bạn muốn gọi lại trong bối cảnh UI, hãy chắc chắn rằng bạn nắm bắt 'SynchronizationContext' không sớm hơn UI đã được xây dựng (tôi sử dụng trình xử lý sự kiện' OnLaunched' để làm điều đó), nếu không ngữ cảnh bạn lấy sẽ không phân phát. Đọc thêm: http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I – Lvsti

5

Một cách để làm điều này đang chờ Task.Delay() trong một vòng lặp thay vì sử dụng một bộ đếm thời gian:

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; } } 
} 

Nếu bạn gọi các nhà xây dựng trên chuỗi giao diện người dùng, nó cũng sẽ gọi số PropertyChanged ở đó. Và điều tốt đẹp là chính xác cùng một mã sẽ làm việc ví dụ trong WPF quá (theo. Net 4.5 và C# 5).

+0

Không nói mã này sẽ không hoạt động, nhưng IME Task.Delay không có độ phân giải rất cao. Trong WPF, Win8 và WP8 kiểm tra, tôi đặt một CancellationTokenSource.CancelAfter đến 8 phút và giao diện người dùng chỉ có đến 7:56 phút. – Stonetip

+0

@Stonetip Chắc chắn nó sẽ có độ phân giải tốt hơn 1 giây, tôi đoán là có điều gì khác đang xảy ra trong mã của bạn. Ngoài ra, 'Task.Delay()' không phải là 'CacelAfter()', mặc dù tôi mong đợi chúng có cùng độ phân giải. – svick

+0

Task.Delay có độ phân giải tốt hơn 1 giây, nhưng bạn mong đợi nó sẽ được phát hiện trên mili giây, sau đó các thử nghiệm của tôi trên WPF, Win8 và Win Phone 8 cho thấy hành vi không nhất quán lên tới 0,5 giây mỗi phút . Tuy nhiên, tôi đã tìm thấy hai môi trường mà lớp ThreadPoolTimer hoạt động rất tốt. – Stonetip

Các vấn đề liên quan