2012-03-29 16 views
7

Piszę aplikację zabawkową Windows Store App dla Windows 8. Ma tylko jedną stronę xaml z TextBlock. Strona ma MyTimer klasy jako DataContext:Aktualizacja interfejsu Windows Store App

this.DataContext = new MyTimer(); 

MyTimer implementuje INotifyPropertyChanged i uaktualnianie właściwości Time jest wykonany z zegarem:

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

z

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

TextBlock ma databinding na czas

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

Kiedy uruchomić aplikację Mam następujący wyjątek:

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

z komunikatem

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

Prawdziwym problemem jest to, że ja uaktualniania własność MyTimer klasy, nie sam GUI, Nie mogę tego rozgryźć, ale myślę, że rozwiązanie powinno używać czegoś takiego jak this one.

Odpowiedz

7

Tak, powiadamia się o zmianie właściwości z wątku puli wątku zamiast wątku interfejsu użytkownika. Musisz przekazać powiadomienie z powrotem do wątku interfejsu użytkownika w oddzwanianiu timera. Teraz twój model widoku jest oddzielony od widoku (dobra rzecz), dlatego nie ma bezpośredniego połączenia z infrastrukturą Dispatcher. Musisz więc podać mu odpowiednią komunikację. Aby to zrobić, musisz przechwycić bieżący numer SynchronizationContext podczas budowy lub pozwolić, aby został on przekazany jawnie konstruktorowi, który jest dobry do testów lub jeśli chcesz zainicjować obiekt poza wątkiem interfejsu użytkownika.

Cały shebang będzie wyglądać mniej więcej tak:

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

Wielkie dzięki. Sugeruje to, że będę musiał używać kontekstu synchronizacji przy każdym użyciu asynchronicznego wywołania ... Zastanowię się nad robieniem czegoś takiego również za pomocą asynchronicznego słowa kluczowego. – Gabber

+0

Tak, jeśli używasz słowa kluczowego w języku C# 4.5, domyślnie otrzymasz to. –

+0

Dzięki za odpowiedź!Chcę tylko dodać sidenote: jeśli chcesz oddzwonić w kontekście interfejsu użytkownika, upewnij się, że przechwytujesz 'SynchronizationContext 'nie wcześniej niż został zbudowany interfejs użytkownika (do tego celu używam programu obsługi zdarzeń' OnLaunched'), w przeciwnym razie kontekst, który chwytasz, nie będzie służyć. Więcej informacji można znaleźć na stronie: http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I – Lvsti

5

Jednym ze sposobów, aby to zrobić czeka Task.Delay() w pętli zamiast przy użyciu Timer:

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

Jeśli wywołanie konstruktora na wątek interfejsu użytkownika, wywoła on także PropertyChanged. I fajne jest to, że dokładnie ten sam kod będzie działał na przykład w WPF (pod .NET 4.5 i C# 5).

+0

Nie mówienie tego kodu nie działa, ale IME Task.Delay nie ma bardzo wysokiej rozdzielczości. W testach WPF, Win8 i WP8 ustawiam opcję AnulowanieTokenSource.CancelAfter na 8 minut, a interfejs użytkownika tylko na 7:56 minut. – Stonetip

+0

@Stonetip Z pewnością powinien mieć lepszą rozdzielczość niż 1 sekunda, domyślam się, że w twoim kodzie jest coś jeszcze. Ponadto 'Task.Delay()' nie jest 'CacelAfter()', choć spodziewałbym się, że będą miały tę samą rozdzielczość. – svick

+0

Task.Delay ma lepszą rozdzielczość niż 1 sekunda, ale spodziewasz się, że będzie to spot-on w zakresie milisekund, wtedy moje testy w ramach WPF, Win8 i Win Phone 8 wykazują niespójne zachowanie do 0,5 sekundy na minutę . Odkryłem jednak, że dla dwóch ostatnich środowisk klasa ThreadPoolTimer działa świetnie. – Stonetip

Powiązane problemy