2010-03-01 7 views
60

Ich soll auf die Dispatcher zugreifen können, die zu der Ansicht gehört, die ich an das ViewModel übergeben muss. Aber die View sollte nichts über das ViewModel wissen, also wie passiert man es? Eine Schnittstelle einführen oder anstatt sie an die Instanzen zu übergeben, wird ein globaler Dispatcher-Singleton erstellt, der von View geschrieben wird. Wie lösen Sie das in Ihren MVVM-Anwendungen und -Frameworks?Wie übergebe ich den UI Dispatcher an das ViewModel

BEARBEITEN: Beachten Sie, dass, da meine ViewModels möglicherweise in Hintergrundthreads erstellt werden, ich nicht einfach Dispatcher.Current im Konstruktor des ViewModel tun kann.

Antwort

40

Ich habe den Dispatcher abstrahiert eine Schnittstelle mit IContext:

public interface IContext 
{ 
    bool IsSynchronized { get; } 
    void Invoke(Action action); 
    void BeginInvoke(Action action); 
} 

Dies hat den Vorteil, dass Sie Ihre Viewmodels Unit-Test können leichter.
Ich injiziere die Schnittstelle in meine ViewModels mit dem MEF (Managed Extensibility Framework). Eine andere Möglichkeit wäre ein Konstruktorargument. Allerdings mag ich die Injektion mit MEF mehr.

Update (Beispiel von Pastebin Link in den Kommentaren):

public sealed class WpfContext : IContext 
{ 
    private readonly Dispatcher _dispatcher; 

    public bool IsSynchronized 
    { 
     get 
     { 
      return this._dispatcher.Thread == Thread.CurrentThread; 
     } 
    } 

    public WpfContext() : this(Dispatcher.CurrentDispatcher) 
    { 
    } 

    public WpfContext(Dispatcher dispatcher) 
    { 
     Debug.Assert(dispatcher != null); 

     this._dispatcher = dispatcher; 
    } 

    public void Invoke(Action action) 
    { 
     Debug.Assert(action != null); 

     this._dispatcher.Invoke(action); 
    } 

    public void BeginInvoke(Action action) 
    { 
     Debug.Assert(action != null); 

     this._dispatcher.BeginInvoke(action); 
    } 
} 
+1

Können Sie ein Beispiel für eine Implementierung in einem wpf UserControl bereitstellen? – Femaref

+2

Ja, können Beispiele zur Implementierung bereitgestellt werden? Vielen Dank. – K2so

+2

Besser spät als nie ;-) Versuchen Sie Folgendes: http://pastebin.com/eXTQf9vm – Matthias

12

Ich bekomme das ViewModel, um den aktuellen Dispatcher als Mitglied zu speichern.

Wenn das ViewModel von der Ansicht erstellt wird, wissen Sie, dass der aktuelle Dispatcher zum Zeitpunkt der Erstellung der Dispatcher der View ist.

class MyViewModel 
{ 
    readonly Dispatcher _dispatcher; 
    public MyViewModel() 
    { 
     _dispatcher = Dispatcher.CurrentDispatcher; 
    } 
} 
+0

In meinem Szenario werden die ViewModels in Threads erstellt. Deshalb habe ich an erster Stelle gefragt. – bitbonk

+4

Problematisch für Unit-Tests. Wie schreiben Sie diese Art von Code und Unit-Test Ihre VMs? –

+0

@Roy: Siehe diese Frage http://stackoverflow.com/questions/1106881/using-the-wpf-dispatcher-in-unit-tests/1303762#1303762 –

18

Sie können nicht wirklich brauchen die Dispatcher. Wenn Sie Eigenschaften in Ihrem Ansichtsmodell an GUI-Elemente in Ihrer Sicht binden, marshaliert der WPF-Bindungsmechanismus automatisch die GUI-Aktualisierungen für den GUI-Thread mithilfe des Dispatchers.


EDIT:

Diese Änderung ist in Reaktion auf Isak Savo Kommentar.

Innen Code von Microsoft für den Umgang mit zu Bindungseigenschaften werden Sie den folgenden Code finden:

if (Dispatcher.Thread == Thread.CurrentThread) 
{ 
    PW.OnPropertyChangedAtLevel(level); 
} 
else 
{ 
    // otherwise invoke an operation to do the work on the right context 
    SetTransferIsPending(true); 
    Dispatcher.BeginInvoke(
     DispatcherPriority.DataBind, 
     new DispatcherOperationCallback(ScheduleTransferOperation), 
     new object[]{o, propName}); 
} 

Dieser Code Marschälle alle UI-Updates auf den Thread Thread UI so dass selbst wenn Sie die Eigenschaften aktualisieren Teil der Bindung unter Aus einem anderen Thread wird WPF automatisch den Aufruf des UI-Threads serialisieren.

+9

aber es gibt viele Situationen, die Sie möglicherweise tun müssen, Stellen Sie sich eine ObservableCollection vor, die an die Benutzeroberfläche gebunden ist, und versuchen, _collection aufzurufen .Add() von einem Worker-Thread –

+0

Ich weiß. Natürlich gelten die üblichen Theading-Überlegungen immer noch. –

+3

Derzeit benötigen wir den Dispatcher nur zum Hinzufügen von Objekten zur ObservableCollection. – bitbonk

-1

Einige meiner WPF-Projekte Ich habe die gleiche Situation konfrontiert. In meinem MainViewModel (Singleton-Instanz) habe ich mit der statischen Methode CreateInstance() den Dispatcher übernommen. Und die create-Instanz wird von der View aus aufgerufen, damit ich den Dispatcher von dort weiterleiten kann. Und das ViewModel-Testmodul ruft CreateInstance() parameterless auf.

Aber in einem komplexen Multithread-Szenario ist es immer gut, eine Interface-Implementierung auf der View-Seite zu haben, um den richtigen Dispatcher des aktuellen Fensters zu erhalten.

1

Wenn Sie nur den Dispatcher für die Notwendigkeit an der SynchronizationContextCollection eine gebundene Sammlung in einem anderen Thread zu sehen, zu modifizieren hier http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

gut funktioniert, nur Problem, das ich gefunden ist, wenn Ansicht Modelle mit SynchronizationContextCollection Eigenschaften mit ASP. NET Synchronisationskontext, aber einfach umgangen.

HTH Sam

+0

wow, interessant, sollte so etwas Teil von WPF sein – bitbonk

0

, wenn Sie verwendet werden uNhAddIns können Sie ein asynchrounous Verhalten leicht machen. werfen Sie einen Blick here

und ich denke, ein paar Änderung muß es auf Schloss Windsor funktioniert (ohne uNhAddIns)

1

hallo vielleicht bin ich zu spät, da es 8 Monate seit Ihrem ersten Beitrag ist ... Ich hatte das gleiche Problem in einer Silverlight MVVM-Anwendung. und ich fand meine Lösung so. für jedes modell und anschauungsmodell, die ich habe, habe ich auch eine klasse namens controller. wie der

public class MainView : UserControl // (because it is a silverlight user controll) 
public class MainViewModel 
public class MainController 

meines MainController ist verantwortlich für die Befehls- und die Verbindung zwischen dem Modell und Viewmodel. Im Konstruktor setze ich die View und ihr Viewmodel ein und setze den Datenkontext der View auf ihr Viewmodel.

mMainView = new MainView(); 
mMainViewModel = new MainViewModel(); 
mMainView.DataContext = mMainViewModel; 

// (in meiner Namenskonvention habe ich einen Präfix m für Membervariablen)

ich habe auch eine öffentliche Eigenschaft in der Art meines Mainview. wie die

public MainView View { get { return mMainView; } } 

(diese mMainView ist eine lokale Variable für die öffentliche Eigenschaft)

und jetzt bin ich fertig. Ich brauche nur meine Dispatcher für meine ui therad wie folgt zu verwenden ...

mMainView.Dispatcher.BeginInvoke(
    () => MessageBox.Show(mSpWeb.CurrentUser.LoginName)); 

(in diesem Beispiel war ich meinen Controller bitten, meinen Sharepoint 2010-Login-Namen, aber Sie können tun, was Ihre Notwendigkeit)

wir sind fast fertig Sie müssen auch Ihre Wurzel visuelle in der app.xaml wie diese

var mainController = new MainController(); 
RootVisual = mainController.View; 

dies durch meine Anwendung hat mir geholfen, definieren. vielleicht kann es dir auch helfen ...

4

Ein weiteres gebräuchliches Muster (das jetzt viel im Rahmen verwendet wird) ist die SynchronizationContext.

Ermöglicht den synchronen und asynchronen Versand. Sie können auch den aktuellen SynchronizationContext für den aktuellen Thread festlegen, was bedeutet, dass er leicht verspottet werden kann. Der DispatcherSynchronizationContext wird von WPF-Apps verwendet. Andere Implementierungen des SynchronizationContext werden von WCF und WF4 verwendet.

+2

Dies ist die beste und einfachste Methode, denke ich. Sie können den SynchronizationContext des ViewModels auf den aktuellen Kontext des Threads setzen, in dem das ViewModel erstellt wurde. Wenn ein UserControl es ändern muss, können Sie es nach Belieben ändern. – ken

0

Ich habe eine anderen (einfachste) Weg finden:

Add Modell Aktion zu sehen, die in Dispatcher werden rufen sollten sind:

public class MyViewModel 
{ 
    public Action<Action> CallWithDispatcher; 

    public void SomeMultithreadMethod() 
    { 
     if(CallWithDispatcher != null) 
      CallWithDispatcher(() => DoSomethingMetod(SomeParameters)); 
    } 
} 

und fügen Sie diesen Action-Handler im Hinblick Konstruktor:

public View() 
    { 
     var model = new MyViewModel(); 

     DataContext = model; 
     InitializeComponent(); 

     // Here 
     model.CallWithDispatcher += act => _taskbarIcon.Dispatcher 
      .BeginInvoke(DispatcherPriority.Normal, act) ; 
    } 

Jetzt haben Sie kein Problem mit Tests, und es ist einfach zu implementieren. Ich habe es zu meinem site

1

hinzugefügt. Sie müssen den UI Dispatcher nicht an das ViewModel übergeben. Der UI-Dispatcher ist in der aktuellen Anwendung singleton verfügbar.

App.Current.MainWindow.Dispatcher 

Dies wird Ihr ViewModel von der Ansicht abhängig machen. Abhängig von Ihrer Anwendung kann das gut oder schlecht sein.

+0

MainWindow kann null sein. – Cologler

26

warum sollten Sie nicht verwenden

System.Windows.Application.Current.Dispatcher.Invoke(
         (Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); })); 

stattdessen Bezug auf GUI-Dispatcher zu halten.

1

für WPF und Windows Store-Apps nutzen: -

 System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); })); 

keeping Referenz Dispatcher GUI ist nicht wirklich der richtige Weg.

wenn das nicht funktioniert (wie im Fall von Windows Phone 8-Anwendungen), dann verwenden: -

 Deployment.Current.Dispatcher 
6

Ab MVVM Licht 5.2, jetzt die Bibliothek enthält ein DispatcherHelper Klasse in GalaSoft.MvvmLight.Threading Namespace, aussetzt Eine Funktion CheckBeginInvokeOnUI(), die einen Delegaten akzeptiert und auf dem UI-Thread ausführt. Das ist sehr praktisch, wenn Ihr ViewModel einige Arbeitsthreads ausführt, die sich auf VM-Eigenschaften auswirken, an die Ihre UI-Elemente gebunden sind.

DispatcherHelper muss durch Aufruf von DispatcherHelper.Initialize() in einem frühen Stadium der Lebensdauer Ihrer Anwendung initialisiert werden (z. B. App_Startup). Sie können dann eine beliebige delegieren (oder Lambda) den folgenden Aufruf verwenden:

DispatcherHelper.CheckBeginInvokeOnUI(
     () => 
     { 
      //Your code here 
     }); 

Beachten Sie, dass die Klasse in GalaSoft.MvvmLight.Platform Bibliothek definiert ist, die standardmäßig nicht referenziert wird, wenn Sie es durch NuGet hinzufügen. Sie müssen manuell einen Verweis auf diese Lib hinzufügen.

0

Ab WPF Version 4.5 kann man verwenden CurrentDispatcher

Dispatcher.CurrentDispatcher.Invoke(() => 
{ 
    // Do GUI related operations here 

}, DispatcherPriority.Normal); 
+0

Dieser Singleton funktioniert in Multithread-Szenarios nicht sehr gut. – bitbonk

0

Vielleicht bin ich ein bisschen spät zu dieser Diskussion, aber ich fand ein schöner Artikel https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Es gibt 1 Absatz

Darüber hinaus sind alle Code außerhalb der Ansicht Layer (dh die ViewModel-Ebenen und Model, Dienste usw.) sollten nicht von einem an eine bestimmte UI-Plattform gebundenen Typ abhängen. Jede direkte Verwendung von Dispatcher (WPF/Xamarin/Windows Phone/Silverlight), CoreDispatcher (Windows Store) oder ISynchronizeInvoke (Windows Forms) ist eine schlechte Idee. (SynchronizationContext ist marginal besser, aber kaum.) Für Beispiel gibt es eine Menge Code im Internet, die einige asynchrone Arbeit und verwendet dann Dispatcher, um die Benutzeroberfläche zu aktualisieren; eine mehr portable und weniger umständliche Lösung ist zu erwarten, für asynchrone Arbeit und aktualisieren Sie die Benutzeroberfläche ohne Dispatcher verwenden.

Angenommen, Sie können async/erwarten richtig verwenden, das ist kein Problem.

Verwandte Themen