11

Utilizzo MVVM Light per creare un'applicazione WP7 (Windows Phone 7). Desidero che tutto il lavoro eseguito dal modello sia eseguito su un thread in background. Quindi, quando il lavoro è terminato, genera un evento in modo che ViewModel possa elaborare i dati.Come eseguire una funzione su un thread in background per Windows Phone 7?

Ho già scoperto che non posso richiamare un delegato in modo asincrono da un'app WP7.

Attualmente sto cercando di utilizzare ThreadPool.QueueUserWorkItem() per eseguire codice su un thread in background e utilizzare DispatcherHelper.CheckBeginInvodeOnUI() di MVVM Light per generare un evento sul thread dell'interfaccia utente per segnalare a ViewModel che i dati sono stati caricati (questo blocca VS2010 e Blend 4 quando provano a visualizzare una vista in fase di progettazione).

Esiste un codice di esempio per eseguire codice su un thread in background e quindi inviare nuovamente un evento al thread dell'interfaccia utente per un'app WP7?

Grazie in anticipo, Jeff.

Edit - Ecco un modello di esempio

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

risposta

16

Ecco come mi avvicinerei a una soluzione a questo.

ViewModel implementa INotifyPropertyChanged? Non è necessario inviare gli eventi. Basta sollevarli "nudi" nel modello, quindi inviare RaisePropertyChanged nel ViewModel.

E sì, si dovrebbe avere una sorta di modello/database singleton nel codice. Dopo tutto, cos'è un database SQL se non un gigantesco singleton? Dal momento che non abbiamo un database in WP7, non essere timido creando un oggetto singleton. Ne ho uno chiamato "Database" :)

Ho appena provato a collegare i miei dataload e ci siamo resi conto che in effetti l'approccio migliore è semplicemente implementare INotifyPropertyChanged proprio al livello del modello. There's no shame in this.

Quindi dato che, ecco cosa sto facendo nell'oggetto Singleton Database per caricare e restituire la mia tabella "Tours" (nota il thread.sleep per far sì che ci voglia un tempo di caricamento visibile, normalmente i suoi sotto 100ms). classe Database ora implementa INotifyPropertyChanged, e genera eventi quando il caricamento è completato:

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

Si segue? Sto deserializzando la lista Tour su un thread in background, quindi sollevando un evento propertychanged.

Ora nel ViewModel, voglio un elenco di TourViewModels a cui collegarsi, che seleziono con una query di linq una volta che vedo che la tabella di Tours è cambiata.Probabilmente è un po 'economico ascoltare l'evento Database nel ViewModel - potrebbe essere "più bello" incapsulare quello nel modello, ma non facciamo il lavoro che non abbiamo bisogno di eh?

associare l'evento database nel costruttore del ViewModel:

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

ascolto per il cambio tabella appropriata (! Amiamo corde magiche ;-)):

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

Selezionare i record che voglio da la tabella, quindi dire alla vista ci sono nuovi dati:

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

    RaisePropertyChanged("AllTours"); 
} 

E infine, nel ViewMod elBase, è meglio controllare se il tuo RaisePropertyChanged ha bisogno di essere spedito. Il mio metodo "SafeDispatch" è praticamente la stessa di quella da MVVMlight:

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

Questo funziona perfettamente nel mio codice, e penso che è abbastanza in ordine?

Infine, extra per esperti: nel WP7, potrebbe essere utile aggiungere un ProgressBar con IsIndeterminate = True alla pagina: verrà visualizzata la barra di avanzamento "punteggiata". Quindi ciò che puoi fare è quando il ViewModel prima carica potresti impostare una proprietà "ProgressBarVisible" su Visible (e aumentare l'evento PropertyChanged associato). Associare la visibilità di ProgressBar a questa proprietà ViewModel. Quando si attiva l'evento PropertyChanged del database, impostare la visibilità su Collapsed per disattivare la barra di avanzamento.

In questo modo, l'utente visualizzerà la barra di avanzamento "IsIndeterminate" nella parte superiore dello schermo mentre è attiva la deserializzazione. Bello!

+0

Non dimenticarti di ricontrollare le implicazioni sulle prestazioni di utilizzare barre di avanzamento indeterminate: http://www.jeff.wilcox.name/2010/08/progressbarperftips2/ –

+0

imposta definitivamente IsDeterminte = False quando non sono visibili. – Micah

+0

La fonte di SafeDispatch sarebbe carina. – Sam

0

non ho sviluppato per WP7 prima, ma ho trovato this article that might be useful!

Ecco il codice di esempio Filosofo pranzo dalla articolo che dovrebbe darvi una buona idea su come allevare un evento per l'interfaccia utente da un altro thread:

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

Spero che sia utile :).

Una cosa che è fonte di confusione: hai detto che quando si utilizza l'helper per sollevare l'evento, VS2010 si arresta in modo anomalo ... cosa si vede esattamente quando si blocca? Stai ricevendo un'eccezione?

+0

Non riesco a trovare il codice sorgente a cui si fa riferimento, si dispone di un collegamento? Sono interessante vedere come viene implementato theCatalog.GetDinners(). –

+0

@Jeff, è nell'articolo che ho collegato (prima frase della mia risposta), ecco l'URL per quell'articolo: http://chriskoenig.net/series/wp7/ – Kiril

0

Jeff, sto ancora immaginando queste cose da solo. Ho postato una domanda simile e ho finito per rispondere io stesso costruendo un semplice esempio. Qui:

A super-simple MVVM-Light WP7 sample?

La sintesi è:

1) I derivati ​​mio modello (sì il mio modello) da ViewModelBase. Questo mi dà l'implementazione di messaggistica di Mvvm-Light e INotifyPropertyChanged che è a portata di mano. Si potrebbe sostenere che questo non è "puro", ma non penso che importi.

2) Ho utilizzato l'helper Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI proprio come hai fatto tu (dal mio modello, NON dal mio ViewModel).

Spero che questo aiuti.