11

Używam MVVM Light do zbudowania aplikacji WP7 (Windows Phone 7). Chciałbym, aby cała praca wykonywana przez Model była uruchamiana w wątku tła. Następnie, gdy praca zostanie zakończona, podnieś wydarzenie, aby ViewModel mógł przetworzyć dane.Jak uruchomić funkcję w wątku tła dla Windows Phone 7?

Dowiedziałem się już, że nie mogę się odwoływać do Asystenta asynchronicznie z aplikacji WP7.

Obecnie próbuję użyć ThreadPool.QueueUserWorkItem(), aby uruchomić kod na wątku tła i użyć MVVM Light's DispatcherHelper.CheckBeginInvodeOnUI(), aby podnieść zdarzenie w wątku interfejsu użytkownika, aby zasygnalizować ViewModel, że dane zostały załadowane (to powoduje awarię VS2010 i Blend 4 przy próbie wyświetlenia widoku czasu projektu).

Czy istnieje przykładowy kod do uruchomienia kodu w wątku tła, a następnie wywołania zdarzenia z powrotem do wątku interfejsu użytkownika dla aplikacji WP7?

Z góry dziękuję, Jeff.

Edit - Oto przykład model

public class DataModel 
{ 
    public event EventHandler<DataLoadingEventArgs> DataLoadingComplete; 
    public event EventHandler<DataLoadingErrorEventArgs> DataLoadingError; 
    List<Data> _dataCasch = new List<Data>(); 

    public void GetData() 
    { 
     ThreadPool.QueueUserWorkItem(func => 
     { 
      try 
      { 
       LoadData(); 
       if (DataLoadingComplete != null) 
       { 
        //Dispatch complete event back to the UI thread 
        DispatcherHelper.CheckBeginInvokeOnUI(() => 
        { 
         //raise event 
         DataLoadingComplete(this, new DataLoadingEventArgs(_dataCasch)); 
        }); 
       } 
      } 
      catch (Exception ex) 
      { 
       if (DataLoadingError != null) 
       { 
        //Dispatch error event back to the UI thread 
        DispatcherHelper.CheckBeginInvokeOnUI(() => 
        { 
         //raise error 
         DataLoadingError(this, new DataLoadingErrorEventArgs(ex)); 
        }); 
       } 
      } 
     }); 
    } 

    private void LoadData() 
    { 
     //Do work to load data.... 
    } 
} 

Odpowiedz

16

Oto jak podchodzę do rozwiązania tego problemu.

Twój ViewModel wdraża INotifyPropertyChanged w prawo? Nie ma potrzeby wysyłania zdarzeń. Po prostu podnieś je "bare" w Modelu, a następnie wyślij RaisePropertyChanged w ViewModel.

I tak, powinieneś mieć jakiś rodzaj pojedynczego modelu/bazy danych w swoim kodzie. W końcu, czym jest baza danych SQL, jeśli nie jakimś gigantycznym singletonem? Ponieważ nie mamy bazy danych w WP7, nie wstydź się tworzyć obiektu singleton. Mam jeden o nazwie "Baza danych" :)

Właśnie próbowałem wątku moich dataloads tam i uświadomić sobie, że w rzeczywistości najlepszym podejściem jest po prostu wdrażanie INotifyPropertyChanged w dół na poziomie modelu. There's no shame in this.

Biorąc to pod uwagę, oto, co robię w obiekcie bazy danych singleton, aby załadować i zwrócić moją "tablicę" Tours (zwróć uwagę na thread.sleep, aby załadować ją w widocznym czasie, zwykle jej sub-100ms). Klasa bazy danych implementuje teraz INotifyPropertyChanged i podnosi zdarzenia po zakończeniu ładowania:

public ObservableCollection<Tour> Tours 
{ 
    get 
    { 
    if (_tours == null) 
    { 
     _tours = new ObservableCollection<Tour>(); 
     ThreadPool.QueueUserWorkItem(LoadTours); 
    } 
    return _tours; 
    } 
} 

private void LoadTours(object o) 
{ 
    var start = DateTime.Now; 
    //simlate lots of work 
    Thread.Sleep(5000); 
    _tours = IsoStore.Deserialize<ObservableCollection<Tour>>(ToursFilename) ?? new ObservableCollection<Tour>(); 
    Debug.WriteLine("Deserialize time: " + DateTime.Now.Subtract(start).ToString()); 
    RaisePropertyChanged("Tours"); 
} 

Podążasz za? Deserializuję listę tras w wątku tła, a następnie zgłaszam zdarzenie z propertykowaniem.

Teraz w ViewModel, chcę listę z TourViewModels do związania, które wybieram z kwerendy linq raz widzę, że tabela Tours została zmieniona.Prawdopodobnie trochę taniej jest posłuchać zdarzenia Database w ViewModelu - może być "ładniej" zamknąć to w modelu, ale nie róbmy pracy, której nie potrzebujemy eh?

Hak zdarzenie bazy danych w konstruktorze ViewModel za:

public TourViewModel() 
{ 
Database.Instance.PropertyChanged += DatabasePropertyChanged; 
} 

Posłuchaj dla odpowiedniej zmiany tabeli (! Kochamy magiczne ciągi ;-)):

private void DatabasePropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    if(e.PropertyName == "Tours") 
    { 
    LoadTourList(); 
    } 
} 

Wybierz rekordy chcę od tabeli, a następnie powiedzieć widok jest nowych danych:

public void LoadTourList() 
{ 
    AllTours = (from t in Database.Instance.Tours 
    select new TourViewModel(t)).ToList(); 

    RaisePropertyChanged("AllTours"); 
} 

I wreszcie, w swoim ViewMod elBase, najlepiej sprawdź, czy Twoje potrzeby RaisePropertyChanged zostały wysłane. Mój „SafeDispatch” metoda jest prawie taki sam jak ten z MVVMlight:

private void RaisePropertyChanged(string property) 
{ 
    if (PropertyChanged != null) 
    { 
    UiHelper.SafeDispatch(() => 
     PropertyChanged(this, new PropertyChangedEventArgs(property))); 
    } 
} 

Działa to doskonale w moim kodu, i myślę, że jest dość uporządkowane?

Wreszcie, dla ekspertów: w WP7 może być dobrze dodać ProgressBar z IsIndeterminate = True do strony - wyświetli się "przerywany" pasek postępu. Następnie możesz zrobić to, gdy najpierw ładuje się ViewModel, możesz ustawić właściwość "ProgressBarVisible" na Visible (i podnieść powiązane zdarzenie PropertyChanged). Powiąż widoczność paska ProgressBar z tą właściwością ViewModel. Po wywołaniu zdarzenia DatabaseChanged ustaw widoczność na zwiniętą, aby pasek postępu zniknął.

W ten sposób użytkownik zobaczy pasek postępu "IsIteterminate" u góry ekranu, gdy deserializacja jest uruchomiona. Miły!

+0

Nie zapomnij po prostu dokładnie sprawdzić wpływ na wydajność użycia nieokreślonych pasków postępu: http://www.jeff.wilcox.name/2010/08/progressbarperftips2/ –

+0

zdecydowanie ustaw IsDeterminte = Fałsz, gdy nie są widoczne. – Micah

+0

Źródło SafeDispatch byłoby miłe. – Sam

0

nie opracowali dla WP7 wcześniej, ale znalazłem this article that might be useful!

Oto przykładowy kod Posiłki Filozof z artykułu, który powinien dać dobry pomysł, w jaki sposób podnieść zdarzenia do UI z innego wątku:

public DinnersViewModel(IDinnerCatalog catalog) 
{ 
    theCatalog = catalog; 
    theCatalog.DinnerLoadingComplete += 
     new EventHandler<DinnerLoadingEventArgs>(
       Dinners_DinnerLoadingComplete); 
} 

public void LoadDinners() 
{ 
    theCatalog.GetDinners(); 
} 

void Dinners_DinnerLoadingComplete(
    object sender, DinnerLoadingEventArgs e) 
{ 
    // Fire Event on UI Thread 
    View.Dispatcher.BeginInvoke(() => 
     { 
      // Clear the list 
      theDinners.Clear(); 

      // Add the new Dinners 
      foreach (Dinner d in e.Results) 
       theDinners.Add(d); 

      if (LoadComplete != null) 
       LoadComplete(this, null); 
     }); 
} 

Mam nadzieję, że pomocne :).

Jedna rzecz, która jest myląca: powiedziałeś, że kiedy używasz pomocnika do podniesienia zdarzenia, to VS2010 ulega awarii ... co dokładnie widzisz, gdy się zawiesza? Czy otrzymujesz wyjątek?

+0

Mam problem ze znalezieniem kodu źródłowego, do którego się odwołujesz, czy masz link? Interesujące jest to, jak zaimplementowano metodęCatalog.GetDinners(). –

+0

@Jeff, to w artykule, który łączyłem (pierwsze zdanie z mojej odpowiedzi), tutaj jest URL tego artykułu: http://chriskoenig.net/series/wp7/ – Kiril

0

Jeff, wciąż sam sobie to wymyślam. Zamieściłem podobne pytanie, a sam odpowiedziałem, budując prostą próbkę. Tutaj:

A super-simple MVVM-Light WP7 sample?

Podsumowanie jest:

1) I pochodzić mojego modelu (tak moim modelu) od ViewModelBase. Daje mi to implementację wiadomości Mvvm-Light i INotifyPropertyChanged, która jest przydatna. Można argumentować, że nie jest to "czysty", ale nie sądzę, aby miało to znaczenie.

2) Użyłem pomocnika Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI, tak jak zrobiłeś (z mojego Modelu, NIE mojego ViewModel).

Mam nadzieję, że to pomoże.

Powiązane problemy