So übergeben Sie den UI-Dispatcher an das ViewModel

74

Ich soll auf den Dispatcher zugreifen können, der zur Ansicht gehört. Ich muss ihn an das ViewModel übergeben. Die Ansicht sollte jedoch nichts über das ViewModel wissen. Wie können Sie es weitergeben? Führen Sie eine Schnittstelle ein oder erstellen Sie einen globalen Dispatcher-Singleton, der von der Ansicht geschrieben wird, anstatt ihn an die Instanzen zu übergeben. Wie lösen Sie dies in Ihren MVVM-Anwendungen und Frameworks?

BEARBEITEN: Da meine ViewModels möglicherweise in Hintergrundthreads erstellt werden, kann ich dies nicht einfach Dispatcher.Currentim Konstruktor des ViewModels tun .

Bitbonk
quelle

Antworten:

47

Ich habe den Dispatcher über eine Schnittstelle IContext abstrahiert :

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

Dies hat den Vorteil, dass Sie Ihre ViewModels einfacher testen können.
Ich füge die Schnittstelle mithilfe des MEF (Managed Extensibility Framework) in meine ViewModels ein. Eine andere Möglichkeit wäre ein Konstruktorargument. Ich mag jedoch die Injektion mit MEF mehr.

Update (Beispiel vom 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);
    }
}
Matthias
quelle
2
Können Sie ein Beispiel für eine Implementierung in einem wpf UserControl angeben?
Femaref
3
Ja, können Beispiele für die Implementierung angegeben werden? Vielen Dank.
K2so
43

warum würdest du nicht benutzen

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

anstatt den Verweis auf den GUI-Dispatcher beizubehalten.

Vitaliy Markitanov
quelle
Strangly Dispatcher.CurrentDispatcher hat bei mir nicht funktioniert. Application.Current.Dispatcher hat funktioniert.
Abhijeet Nagre
19

Möglicherweise benötigen Sie den Dispatcher nicht. Wenn Sie Eigenschaften Ihres Ansichtsmodells an GUI-Elemente in Ihrer Ansicht binden, führt der WPF-Bindungsmechanismus die GUI-Aktualisierungen mithilfe des Dispatchers automatisch an den GUI-Thread weiter.


BEARBEITEN:

Diese Bearbeitung ist eine Antwort auf Isak Savos Kommentar.

Im Microsoft-Code zur Behandlung der Bindung an Eigenschaften finden Sie den folgenden Code:

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 führt alle UI-Aktualisierungen des Thread-UI-Threads durch, sodass WPF den Aufruf des UI-Threads automatisch serialisiert, selbst wenn Sie die Eigenschaften aktualisieren, die an der Bindung eines anderen Threads beteiligt sind.

Jakob Christensen
quelle
9
Es gibt jedoch viele Situationen. Möglicherweise müssen Sie dies tun. Stellen Sie sich eine ObservableCollection vor, die an die Benutzeroberfläche gebunden ist, und Sie versuchen, _collection.Add () aus einem Arbeitsthread aufzurufen
Jobi Joy
Ich kenne. Natürlich gelten immer noch die üblichen Überlegungen.
Jakob Christensen
3
Derzeit benötigen wir den Dispatcher nur zum Hinzufügen von Elementen zur ObservableCollection.
Bitbonk
3
Hallo Isak. Du verstehst mich richtig, aber du liegst falsch. Wenn Sie den WPF-Bindungscode von Microsoft debuggen (oder ihn mit Reflector anzeigen), werden Sie feststellen, dass der Code prüft, ob Sie sich im GUI-Thread befinden, und wenn nicht, den Dispatcher zum Aktualisieren des GUI-Threads verwendet. Ich weiß nicht, ob es für ObservableCollection funktioniert, aber für "normale" Eigenschaften. Ich habe einen Blogeintrag dazu geschrieben (allerdings auf Dänisch). Am Ende des Blogeintrags wird der Code von Microsoft angezeigt: dotninjas.dk/post/Flere-trade-og-binding-i-WPF.aspx
Jakob Christensen
1
Jakob: Du bist absolut richtig. Ich habe stattdessen meine -1 auf +1 geändert. Ich habe dies sowohl in .net 3.5 als auch in 4.0 versucht und es scheint vollkommen in Ordnung zu sein, PropertyChanged in einem Hintergrund-Thread zu erhöhen.
Isak Savo
15

Ich erhalte 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 Ansicht ist.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
Andrew Shepherd
quelle
In meinem Szenario werden die ViewModels in Threads erstellt. Deshalb habe ich zuerst gefragt.
Bitbonk
5
Aber problematisch für Unit-Tests. Wie schreiben Sie diese Art von Code und testen Ihre VMs?
Ray Booysen
@ Roy: Siehe diese Frage stackoverflow.com/questions/1106881/…
Andrew Shepherd
Ja, DispatcherFrame macht Ihre Unit-Tests klobig und schrecklich zu schreiben. Warum nicht einfach weg abstrahieren?
Ray Booysen
7

Ab MVVM Light 5.2 enthält die Bibliothek jetzt eine DispatcherHelperKlasse im GalaSoft.MvvmLight.ThreadingNamespace, die eine Funktion verfügbar macht CheckBeginInvokeOnUI(), die einen Delegaten akzeptiert und auf dem UI-Thread ausführt. Dies ist sehr praktisch, wenn in Ihrem ViewModel einige Arbeitsthreads ausgeführt werden, die sich auf die VM-Eigenschaften auswirken, an die Ihre UI-Elemente gebunden sind.

DispatcherHelpermuss initialisiert werden, indem Sie DispatcherHelper.Initialize()frühzeitig im Leben Ihrer Anwendung anrufen (z App_Startup. B. ). Sie können dann einen beliebigen Delegaten (oder Lambda) mit dem folgenden Aufruf ausführen:

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

Beachten Sie, dass die Klasse in einer GalaSoft.MvvmLight.PlatformBibliothek definiert ist, auf die standardmäßig nicht verwiesen wird, wenn Sie sie über NuGet hinzufügen. Sie müssen manuell einen Verweis auf diese Bibliothek hinzufügen.

Punkt net
quelle
5

Ein weiteres gängiges Muster (das derzeit im Framework häufig verwendet wird) ist der SynchronizationContext .

Sie können damit synchron und asynchron versenden. Sie können auch den aktuellen SynchronizationContext für den aktuellen Thread festlegen, sodass er leicht verspottet werden kann. Der DispatcherSynchronizationContext wird von WPF-Apps verwendet. Andere Implementierungen des SynchronizationContext werden von WCF und WF4 verwendet.


quelle
2
Dies ist die beste und einfachste Methode, denke ich. Sie können den SynchronizationContext des ViewModel standardmäßig auf den aktuellen Kontext des Threads einstellen, in dem das ViewModel erstellt wurde. Wenn ein UserControl es ändern muss, kann es nach Belieben geändert werden.
Ken
3

Ab WPF Version 4.5 kann CurrentDispatcher verwendet werden

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

}, DispatcherPriority.Normal); 
ΩmegaMan
quelle
1
Dieser Singleton funktioniert in Multithread-Szenarien nicht sehr gut.
Bitbonk
1

Wenn Sie den Dispatcher nur zum Ändern einer gebundenen Sammlung in einem anderen Thread benötigen, sehen Sie sich die SynchronizationContextCollection hier an: http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Funktioniert gut. Das einzige Problem, das ich festgestellt habe, ist die Verwendung von Ansichtsmodellen mit SynchronizationContextCollection-Eigenschaften mit ASP.NET-Synchronisierungskontext.

HTH Sam

Sambomartin
quelle
1

Hallo, vielleicht bin ich zu spät, da seit deinem ersten Beitrag 8 Monate vergangen sind ... Ich hatte das gleiche Problem in einer Silverlight-MVVM-Anwendung. und ich fand meine Lösung so. Für jedes Modell und Ansichtsmodell, das ich habe, habe ich auch eine Klasse namens Controller. so wie das

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

Mein MainController ist für den Befehl und die Verbindung zwischen Modell und Ansichtsmodell verantwortlich. Im Konstruktor instanziiere ich die Ansicht und ihr Ansichtsmodell und setze den Datenkontext der Ansicht auf ihr Ansichtsmodell.

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

// (in meiner Namenskonvention habe ich ein Präfix m für Mitgliedsvariablen)

Ich habe auch eine öffentliche Eigenschaft in der Art meiner MainView. so wie das

public MainView View { get { return mMainView; } }

(Diese mMainView ist eine lokale Variable für das öffentliche Eigentum.)

und jetzt bin ich fertig. Ich muss nur meinen Dispatcher für meine Benutzeroberfläche verwenden ...

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

(In diesem Beispiel habe ich meinen Controller gebeten, meinen Sharepoint 2010-Loginnamen zu erhalten, aber Sie können tun, was Sie brauchen.)

Wir sind fast fertig. Sie müssen auch Ihr Root-Visual in der app.xaml so definieren

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

Das hat mir durch meine Bewerbung geholfen. Vielleicht kann es dir auch helfen ...

arborinfelix
quelle
1

Sie müssen den UI-Dispatcher nicht an das ViewModel übergeben. Der UI-Dispatcher ist im aktuellen Anwendungs-Singleton verfügbar.

App.Current.MainWindow.Dispatcher

Dadurch wird Ihr ViewModel von der Ansicht abhängig. Abhängig von Ihrer Anwendung kann dies in Ordnung sein oder auch nicht.

Rana Ian
quelle
MainWindow kann null sein.
Cologler
1

Verwenden Sie für WPF- und Windows Store-Apps: -

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

Es ist nicht wirklich der richtige Weg, auf den GUI-Dispatcher zu verweisen.

Wenn dies nicht funktioniert (z. B. bei Windows Phone 8-Apps), verwenden Sie: -

       Deployment.Current.Dispatcher
Parth Sehgal
quelle
0

Wenn Sie uNhAddIns verwenden, können Sie leicht ein asynchrones Verhalten erzeugen . Schauen Sie hier

Und ich denke, ich brauche ein paar Modifikationen, damit es auf Castle Windsor funktioniert (ohne uNhAddIns).

ktutnik
quelle
0

Ich habe einen anderen (einfachsten) Weg gefunden:

Hinzufügen, um Modellaktion anzuzeigen, die im Dispatcher aufgerufen werden soll:

public class MyViewModel
{
    public Action<Action> CallWithDispatcher;

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

Fügen Sie diesen Aktionshandler im Ansichtskonstruktor hinzu:

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

        DataContext = model;
        InitializeComponent();

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

Jetzt haben Sie keine Probleme mit dem Testen und es ist einfach zu implementieren. Ich habe es meiner Seite hinzugefügt

Alexander Molodih
quelle
0

Sie müssen den Dispatcher nicht übergeben, wenn Sie mit auf den Anwendungs-Dispatcher zugreifen können

Dispatcher dis = Application.Current.Dispatcher 
Hemant
quelle
-1

Bei einigen meiner WPF-Projekte war ich mit der gleichen Situation konfrontiert. In meinem MainViewModel (Singleton-Instanz) habe ich meine statische CreateInstance () -Methode erhalten, die den Dispatcher übernimmt. Und die create-Instanz wird aus der Ansicht aufgerufen, damit ich den Dispatcher von dort aus übergeben kann. Das ViewModel-Testmodul ruft CreateInstance () parameterlos auf.

In einem komplexen Multithread-Szenario ist es jedoch immer gut, eine Schnittstellenimplementierung auf der Ansichtsseite zu haben, um den richtigen Dispatcher für das aktuelle Fenster zu erhalten.

Jobi Joy
quelle
-1

Vielleicht komme ich etwas spät zu dieser Diskussion, aber ich habe 1 schönen Artikel gefunden: https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Es gibt 1 Absatz

Darüber hinaus sollte der gesamte Code außerhalb der Ansichtsebene (dh der ViewModel- und Model-Ebenen, der Dienste usw.) nicht von einem Typ abhängen, der an eine bestimmte UI-Plattform gebunden ist. Jede direkte Verwendung von Dispatcher (WPF / Xamarin / Windows Phone / Silverlight), CoreDispatcher (Windows Store) oder ISynchronizeInvoke (Windows Forms) ist eine schlechte Idee. (SynchronizationContext ist geringfügig besser, aber kaum.) Zum Beispiel gibt es im Internet viel Code, der asynchrone Arbeit leistet und dann Dispatcher verwendet, um die Benutzeroberfläche zu aktualisieren. Eine portablere und weniger umständliche Lösung besteht darin, auf asynchrone Arbeit zu warten und die Benutzeroberfläche zu aktualisieren, ohne Dispatcher zu verwenden.

Angenommen, Sie können async / await ordnungsgemäß verwenden, ist dies kein Problem.

Hardywang
quelle