11

Estoy usando MVVM Light para construir una aplicación WP7 (Windows Phone 7). Deseo que todo el trabajo realizado por el Modelo se ejecute en un hilo de fondo. Luego, cuando el trabajo esté terminado, plantee un evento para que ViewModel pueda procesar los datos.¿Cómo ejecutar una función en una cadena de fondo para Windows Phone 7?

Ya he descubierto que no puedo invocar a un delegado de forma asincrónica desde una aplicación WP7.

Actualmente estoy tratando de utilizar ThreadPool.QueueUserWorkItem() para ejecutar un código en un subproceso en segundo plano y usar DispatcherHelper.CheckBeginInvodeOnUI de MVVM Light() para generar un evento en el subproceso de interfaz de usuario para indicar el modelo de vista que los datos han sido cargados (Esto bloquea VS2010 y Blend 4 cuando intentan mostrar una vista en tiempo de diseño).

¿Hay algún código de muestra para ejecutar algún código en una cadena de fondo y luego enviar un evento de vuelta al subproceso de interfaz de usuario para una aplicación WP7?

Gracias de antemano, Jeff.

Editar - He aquí una muestra de Modelo

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

Respuesta

16

Así es como abordaría una solución para esto.

Su ViewModel implementa INotifyPropertyChanged ¿verdad? No hay necesidad de enviar los eventos. Simplemente críalos "desnudos" en el Modelo, luego envíe RaisePropertyChanged en ViewModel.

Y sí, debe tener algún tipo de modelo/base de datos singleton en su código. Después de todo, ¿qué es una base de datos SQL sino un singleton gigante? Debido a que no tenemos una base de datos en WP7, no dude en crear un objeto singleton. Tengo una llamada "Base de datos" :)

Acabo de intentar enhebrar mis cargas de datos allí, y me doy cuenta de que, de hecho, el mejor enfoque es simplemente implementar INotifyPropertyChanged en el nivel del modelo. There's no shame in this.

Por lo tanto, esto es lo que estoy haciendo en el objeto Singleton Database para cargar y devolver mi "tabla" de Tours (tenga en cuenta thread.sleep para que tome una cantidad visible de tiempo para cargar, normalmente su sub 100ms) clase de base de datos ahora implementa INotifyPropertyChanged, y provoca eventos cuando la carga se ha completado:

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

seguir? Deserializo la lista de recorridos en un hilo de fondo, y luego planteo un evento modificado por la propiedad.

Ahora en ViewModel, quiero una lista de TourViewModels para enlazar, que selecciono con una consulta linq una vez que veo que la tabla Tours ha cambiado.Probablemente sea un poco barato escuchar el evento de la base de datos en el modelo ViewModel; podría ser "más agradable" encapsularlo en el modelo, pero no hagamos el trabajo, no necesitamos hacerlo ¿eh?

enlazar el evento de base de datos en el constructor del modelo de vista:

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

escuchar el cambio de la tabla correspondiente (! Amamos cuerdas mágicas ;-)):

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

Seleccione los registros que quiero de la tabla, luego dígale a la vista que hay nuevos datos:

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

    RaisePropertyChanged("AllTours"); 
} 

Y por último, en su ViewMod elBase, lo mejor es verificar si su RaisePropertyChanged necesita ser despachado. Mi método "SafeDispatch" es más o menos la misma que la de MVVMlight:

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

Esto funciona perfectamente en mi código, y creo que es bastante ordenada?

Por último, extra para expertos: en WP7, podría ser bueno agregar un ProgressBar con IsIndeterminate = Verdadero en su página - esto mostrará la barra de progreso "punteada". Entonces, lo que puede hacer es que cuando ViewModel carga por primera vez, puede establecer una propiedad "ProgressBarVisible" en Visible (y aumentar el evento PropertyChanged asociado). Vincula la visibilidad de ProgressBar a esta propiedad de ViewModel. Cuando el evento Database PropertyChanged se activa, establezca la visibilidad en Contraído para hacer que la barra de progreso desaparezca.

De esta manera, el usuario verá la barra de progreso "IsIndeterminate" en la parte superior de la pantalla mientras se ejecuta la deserialización. ¡Bonito!

+0

No olvide comprobar las implicaciones del rendimiento de utilizar barras de progreso indeterminadas: http://www.jeff.wilcox.name/2010/08/progressbarperftips2/ –

+0

Definir definitivamente IsDeterminte = False cuando no estén visibles. – Micah

+0

La fuente de SafeDispatch estaría bien. – Sam

0

no he desarrollado para WP7 antes, pero me encontré con this article that might be useful!

Aquí es el código de ejemplo Filósofo comedor del artículo que debe darle una buena idea sobre cómo criar a un evento para la interfaz de usuario desde otro hilo:

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

espero que sea útil :).

Una cosa que es confusa: dijiste que cuando usas el ayudante para plantear el evento, entonces el VS2010 falla ... ¿qué estás viendo exactamente cuando está fallando? ¿Estás recibiendo una excepción?

+0

Tengo problemas para encontrar el código fuente al que hizo referencia, ¿tiene un enlace? Me interesa ver cómo se implementa el Catálogo.GetDinners(). –

+0

@Jeff, está en el artículo que he vinculado (primera oración de mi respuesta), aquí está la URL de ese artículo: http://chriskoenig.net/series/wp7/ – Kiril

0

Jeff, todavía estoy pensando en esto yo mismo. Publiqué una pregunta similar y terminé respondiéndola yo mismo construyendo una muestra simple. Aquí:

A super-simple MVVM-Light WP7 sample?

El resumen es:

1) que deriva de mi modelo (sí mi modelo) de ViewModelBase. Esto me da la implementación de mensajería de Mvvm-Light y INotifyPropertyChanged que es útil. Podría argumentar que esto no es "puro", pero no creo que importe.

2) Utilicé el asistente de Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI tal como lo hizo (desde mi modelo, NO mi ViewModel).

Espero que esto ayude.