2009-02-26 17 views
35

En una aplicación WPF que estoy escribiendo utilizando el patrón MVVM, tengo un proceso en segundo plano que lo hace, pero necesita obtener actualizaciones de estado de él a la IU.Asegurándose de que OnPropertyChanged() se invoca en el subproceso UI en la aplicación MVVM WPF

Estoy usando el patrón MVVM, por lo que mi ViewModel no conoce prácticamente nada de la vista (IU) que presenta el modelo al usuario.

Decir que tengo el siguiente método en mi modelo de vista:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e) 
{ 
    this.Messages.Add(e.Message); 
    OnPropertyChanged("Messages"); 
} 

En mi opinión, tengo un cuadro de lista con destino a la propiedad de mensajes (un List<string>) del modelo de vista. OnPropertyChanged cumple el rol de la interfaz INotifyPropertyChanged llamando al PropertyChangedEventHandler.

Necesito asegurarme de que OnPropertyChanged se invoca en el hilo de UI, ¿cómo hago esto? He intentado lo siguiente:

public Dispatcher Dispatcher { get; set; } 
public MyViewModel() 
{ 
    this.Dispatcher = Dispatcher.CurrentDispatcher; 
} 

A continuación, añadir el siguiente al método OnPropertyChanged:

if (this.Dispatcher != Dispatcher.CurrentDispatcher) 
{ 
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate 
    { 
     OnPropertyChanged(propertyName); 
    })); 
    return; 
} 

pero esto no funcionó. ¿Algunas ideas?

Respuesta

33

WPF organiza automáticamente los cambios de propiedad en el subproceso de interfaz de usuario. Sin embargo, no coordina los cambios en la recopilación, por lo que sospecho que agregar un mensaje está causando la falla.

Puede ordenar manualmente el complemento usted mismo (vea el ejemplo a continuación), o use algo como this technique que publiqué hace un tiempo.

manual de clasificación:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e) 
{ 
    Dispatcher.Invoke(new Action<string>(AddMessage), e.Message); 
    OnPropertyChanged("Messages"); 
} 

private void AddMessage(string message) 
{ 
    Dispatcher.VerifyAccess(); 
    Messages.Add(message); 
} 
+0

que lo hizo, con otra pequeña modificación - me cambió mi lista a un ObservableCollction y funciona como un encanto. ¡Gracias! –

+1

Si 'Mensajes' es observable, no hay necesidad de llamar a OnPropertyChanged() para ello. – Doug

3

que tenía un escenario similar sólo en esta semana (MVVM aquí también). Tuve una clase aparte haciendo su trabajo, informando sobre el estado en un controlador de eventos. El controlador de eventos se estaba llamando como se esperaba, y pude ver los resultados regresando a tiempo con Debug.WriteLine's.

Pero con WPF, haga lo que haga, la IU no se actualizará hasta que se complete el proceso. Tan pronto como finalice el proceso, la IU se actualizará como se espera. Era como si obtuviera PropertyChanged, pero esperando que se complete el subproceso antes de realizar todas las actualizaciones de UI a la vez.

(Muy a mi pesar, el mismo código en Windows Forms con un DoEvents y .Refresh() funcionó como un hechizo.)

Hasta ahora, he resuelto esto al iniciar el proceso de su propia Tema:

//hook up event handler 
myProcess.MyEvent += new EventHandler<MyEventArgs>(MyEventHandler); 

//start it on a thread ... 
ThreadStart threadStart = new ThreadStart(myProcess.Start); 

Thread thread = new Thread(threadStart); 

thread.Start(); 

y luego en el controlador de eventos:

private void MyEventHandler(object sender, MyEventArgs e) { 
.... 
Application.Current.Dispatcher.Invoke(
       DispatcherPriority.Send, 
       (DispatcherOperationCallback)(arg => 
       { 
     //do UI updating here ... 
     }), null); 

no estoy recomendando este código, ya que todavía estoy tratando de entender el modelo hilo de WPF, cómo funciona el despachador, y WH En mi caso, la IU no se actualizaría hasta que el proceso estuviera completo, incluso cuando el controlador de eventos se llamara como se esperaba (¿por diseño?). Pero esto ha funcionado para mí hasta ahora.

He encontrado estos dos enlaces de ayuda:

http://www.nbdtech.com/blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx

http://srtsolutions.com/blogs/mikewoelmer/archive/2009/04/17/dealing-with-unhandled-exceptions-in-wpf.aspx

0

que controlen el evento BackgroundWorker.ReportProgress fuera de mi modelo de vista y pasar la instancia BackgroundWorker real y el modelo de vista en mi clase que define el (los) método (s) de asincronización.

El método asynch llama a bgWorker.ReportProgress y pasa una clase que envuelve a un delegado como el estado del usuario (como objeto). El delegado lo escribo como un método anónimo.

En el controlador de eventos, lo lanzo desde el objeto al tipo de envoltura y luego invoco al delegado dentro.

Todo esto significa que puedo codificar los cambios de IU directamente desde el código que se ejecuta de forma asíncrona, pero solo tiene este ajuste.

Esto explica con más detalle:

http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html

8

me gusta mucho la respuesta de Jeremy: Dispatching In Silverlight

Resumen:

  • La colocación de Dispatcher en modelo de vista parece poco elegante

  • Creación de una acción < Acción> propiedad, situada a tan sólo ejecute la acción en el constructor VM

  • Cuando se utiliza la máquina virtual de la V, establecer la propiedad Acción para invocar el despachador
0

Esto es más de una extensión de la respuesta aceptada, pero lo hice con mi lanzador evento ...

using System.Threading; 

private void Handler(object sender, RoutedEventArgs e) 
{ 
    if (Thread.CurrentThread == this.Dispatcher.Thread) 
    { 
     //do stuff to this 
    } 
    else 
    { 
     this.Dispatcher.Invoke(
      new Action<object, RoutedEventArgs>(Handler), 
      sender, 
      e); 
    } 
} 
Cuestiones relacionadas