2009-02-26 2 views
35

In einer WPF-Anwendung, die ich mit dem MVVM-Muster schreibe, habe ich einen Hintergrundprozess, der das erledigt, aber Statusaktualisierungen abrufen muss auf die Benutzeroberfläche.Sicherstellen, dass OnPropertyChanged() für den UI-Thread in der MVVM WPF-App aufgerufen wird

Ich verwende das MVVM-Muster, daher kennt mein ViewModel praktisch nichts von der Ansicht (UI), die das Modell dem Benutzer präsentiert.

sagen, dass ich die folgende Methode in meinem Ansichtsmodell haben:

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

Aus meiner Sicht habe ich eine List-Box müssen die Nachrichten Eigenschaft gebunden (a List<string>) des Ansichtsmodell. OnPropertyChanged erfüllt die Rolle der INotifyPropertyChanged Schnittstelle durch Aufruf einer PropertyChangedEventHandler.

Ich muss sicherstellen, dass OnPropertyChanged auf dem UI-Thread aufgerufen wird - wie mache ich das? Ich habe folgendes versucht:

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

dann das folgende an die OnPropertyChanged Methode:

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

aber das hat nicht funktioniert. Irgendwelche Ideen?

Antwort

33

WPF führt automatisch Änderungen der Eigenschaften an den UI-Thread durch. Sammlungsänderungen werden jedoch nicht marshaliert, daher vermute ich, dass das Hinzufügen einer Nachricht den Fehler verursacht.

Sie können die hinzufügen manuell selbst (siehe Beispiel unten), oder verwenden Sie etwas wie this technique Ich bloggte über eine Weile zurück.

manuell Rangierung:

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

, sie hätte mit einer anderen geringfügigen Änderungen - ich meine Liste zu einem ObservableCollction geändert und es funktioniert wie ein Charme. Vielen Dank! –

+1

Wenn "Nachrichten" beobachtbar ist, müssen Sie OnPropertyChanged() nicht aufrufen. – Doug

3

hatte ich ein ähnliches Szenario nur in dieser Woche (MVVM auch hier). Ich hatte eine separate Klasse, die ihre Sache machte, und meldete den Status eines Event-Handlers zurück. Der Ereignishandler wurde wie erwartet aufgerufen, und ich konnte die Ergebnisse mit Debug.WriteLine's pünktlich zurückkommen sehen.

Aber mit WPF, egal was ich tat, würde die Benutzeroberfläche nicht aktualisiert, bis der Prozess abgeschlossen war. Sobald der Prozess abgeschlossen ist, wird die Benutzeroberfläche wie erwartet aktualisiert. Es war so, als würde es PropertyChanged erhalten, aber darauf warten, dass der Thread abgeschlossen wurde, bevor die UI-Aktualisierungen auf einmal durchgeführt wurden.

(Zu meiner Bestürzung, der gleiche Code in Windows.Forms mit DoEvents und .Refresh() arbeitete wie ein Charme.)

Bisher habe ich dieses Problem gelöst, indem das Verfahrens auf seinem eigenen Ausgang Thema:

//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(); 

und dann im Ereignishandler:

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

ich diesen Code nicht zu empfehlen, da ich noch versucht, das WPF-Thread-Modell, wie Dispatcher Werke zu verstehen, und wh y In meinem Fall würde die Benutzeroberfläche nicht aktualisiert werden, bis der Prozess abgeschlossen wurde, selbst wenn der Ereignishandler wie erwartet aufgerufen wurde (vom Entwurf her?). Aber das hat für mich bisher funktioniert.

fand ich diese beiden Links hilfreich:

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

ich das BackgroundWorker.ReportProgress Ereignis behandeln außerhalb meiner Ansicht Modell und die tatsächliche Background Instanz übergeben und das Ansichtsmodell in meine Klasse, die definiert die Asynchronmethode (n).

Die asynch-Methode ruft dann bgWorker.ReportProgress auf und übergibt eine Klasse, die einen Delegaten als Benutzer (als Objekt) umschließt. Der Delegierte schreibe ich als anonyme Methode.

In der Event-Handler, ich es aus dem Objekt zurück in den Wrapper-Typ zu werfen und dann den Delegaten innerhalb aufrufen.

All dies bedeutet, dass ich UI-Änderungen direkt aus dem Code, der asynchron ausgeführt wird, codieren kann, aber es hat nur diese Umhüllung um es.

Dieses näher erläutert:

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

8

Ich mag die Antwort von Jeremy wirklich: Dispatching In Silverlight

Zusammenfassung:

  • Platzierung Dispatcher in Ansichtsmodell unelegant scheint

  • eine Aktion < Aktion> Immobilien anlegen, sie setzen die Aktion in der VM-Konstruktor

  • Bei Verwendung der VM von der V nur laufen, stellen Sie die Action-Eigenschaft des Dispatcher
0

Dies ist mehr aufrufen eine Erweiterung der akzeptierten Antwort, aber ich habe das mit meinem Fall hander ...

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