Ich gehe einige MVVM-Artikel durch, hauptsächlich dies und das .
Meine spezielle Frage lautet: Wie kommuniziere ich Modelländerungen vom Modell zum ViewModel?
In Joshs Artikel sehe ich nicht, dass er das tut. Das ViewModel fragt das Modell immer nach Eigenschaften. In Rachels Beispiel lässt sie das Modell implementierenINotifyPropertyChanged
und löst Ereignisse aus dem Modell aus, die jedoch von der Ansicht selbst verwendet werden sollen (weitere Informationen dazu, warum sie dies tut, finden Sie in ihrem Artikel / Code).
Nirgendwo sehe ich Beispiele, in denen das Modell das ViewModel über Änderungen an Modelleigenschaften informiert. Das hat mich beunruhigt, dass es vielleicht aus irgendeinem Grund nicht gemacht wird. Gibt es ein Muster, um das ViewModel über Änderungen im Modell zu informieren? Dies scheint notwendig zu sein, da (1) möglicherweise mehr als 1 ViewModel für jedes Modell vorhanden ist und (2) selbst wenn nur ein ViewModel vorhanden ist, eine Aktion des Modells dazu führen kann, dass andere Eigenschaften geändert werden.
Ich vermute, dass es Antworten / Kommentare des Formulars "Warum sollten Sie das tun wollen?" Gibt. Kommentare, also hier eine Beschreibung meines Programms. Ich bin neu bei MVVM, daher ist mein gesamtes Design möglicherweise fehlerhaft. Ich werde es kurz beschreiben.
Ich programmiere etwas, das (zumindest für mich!) Interessanter ist als die Klassen "Kunde" oder "Produkt". Ich programmiere BlackJack.
Ich habe eine Ansicht, die keinen Code enthält und sich nur auf die Bindung an Eigenschaften und Befehle im ViewModel stützt (siehe Josh Smiths Artikel).
Für besser oder schlechter, nahm ich die Haltung , dass das Modell nicht nur Klassen wie enthalten PlayingCard
, Deck
sondern auch die BlackJackGame
Klasse , den Zustand des ganzen Spiels hält, und weiß , wann der Spieler gegangen Büste hat, die Dealer Karten zu ziehen haben, und Wie hoch ist die aktuelle Punktzahl des Spielers und Dealers (weniger als 21, 21, Fehlschlag usw.)?
Aus BlackJackGame
Ich stelle Methoden wie "DrawCard" zur Verfügung und es kam mir der Gedanke, dass beim Ziehen einer Karte Eigenschaften wie CardScore
und IsBust
aktualisiert werden sollten und diese neuen Werte an das ViewModel übermittelt werden sollten. Vielleicht ist das falsches Denken?
Man könnte die Haltung einnehmen, dass das ViewModel die DrawCard()
Methode aufgerufen hat , also sollte er wissen, ob er nach einer aktualisierten Punktzahl fragen und herausfinden muss, ob er pleite ist oder nicht. Meinungen?
In meinem ViewModel habe ich die Logik, ein tatsächliches Bild einer Spielkarte (basierend auf Farbe, Rang) zu erfassen und für die Ansicht verfügbar zu machen. Das Modell sollte sich nicht damit befassen (möglicherweise würde ein anderes ViewModel nur Zahlen anstelle von Kartenbildern verwenden). Natürlich werden mir vielleicht einige sagen, dass das Modell nicht einmal das Konzept eines BlackJack-Spiels haben sollte und dass dies im ViewModel behandelt werden sollte?
OnBust
verfügbar machen und die VM kann es abonnieren. Ich denke, Sie könnten auch einen IEA-Ansatz verwenden.Antworten:
Wenn Ihre Modelle die ViewModels über Änderungen informieren sollen , sollten sie INotifyPropertyChanged implementieren , und die ViewModels sollten abonnieren, um PropertyChange-Benachrichtigungen zu erhalten.
Ihr Code könnte ungefähr so aussehen:
Dies ist jedoch normalerweise nur erforderlich, wenn mehr als ein Objekt Änderungen an den Daten des Modells vornimmt, was normalerweise nicht der Fall ist.
Wenn Sie jemals einen Fall haben, in dem Sie keinen Verweis auf Ihre Model-Eigenschaft haben, um das PropertyChanged-Ereignis daran anzuhängen, können Sie ein Messaging-System wie das von Prism
EventAggregator
oder MVVM Light verwendenMessenger
.Ich habe einen kurzen Überblick über Messagingsysteme in meinem Blog. Zusammenfassend kann jedoch jedes Objekt eine Nachricht senden und jedes Objekt abonnieren, um auf bestimmte Nachrichten zu warten. Sie können also ein
PlayerScoreHasChangedMessage
Objekt von einem Objekt senden , und ein anderes Objekt kann abonnieren, um auf diese Nachrichtentypen zu warten und seinePlayerScore
Eigenschaft zu aktualisieren, wenn es eines hört.Ich glaube jedoch nicht, dass dies für das von Ihnen beschriebene System erforderlich ist.
In einer idealen MVVM-Welt besteht Ihre Anwendung aus Ihren ViewModels, und Ihre Modelle sind nur die Blöcke, die zum Erstellen Ihrer Anwendung verwendet werden. Sie enthalten normalerweise nur Daten, haben also keine Methoden wie
DrawCard()
(das wäre in einem ViewModel)Sie hätten also wahrscheinlich einfache Modelldatenobjekte wie diese:
und Sie hätten ein ViewModel-Objekt wie
(Die oben genannten Objekte sollten alle implementiert werden
INotifyPropertyChanged
, aber ich habe es der Einfachheit halber weggelassen.)quelle
DrawCard()
Methode befindet sich zusammen mit Ihrer anderen Spielelogik im ViewModel. In einer idealen MVVM-Anwendung sollten Sie in der Lage sein, Ihre Anwendung vollständig ohne Benutzeroberfläche auszuführen, indem Sie einfach ViewModels erstellen und deren Methoden ausführen, z. B. über ein Testskript oder ein Eingabeaufforderungsfenster. Die Modelle sind normalerweise nur Datenmodelle, die Rohdaten und die Validierung grundlegender Daten enthalten.DrawCardCommand()
wären also im ViewModel, aber ich denke, Sie könnten einBlackjackGameModel
Objekt haben, das eineDrawCard()
Methode enthält, die der Befehl aufgerufen hat, wenn Sie wolltenKurze Antwort: Es kommt auf die Besonderheiten an.
In Ihrem Beispiel werden die Modelle "von selbst" aktualisiert, und diese Änderungen müssen sich natürlich irgendwie auf die Ansichten übertragen. Da die Ansichten nur direkt auf die Ansichtsmodelle zugreifen können, muss das Modell diese Änderungen an das entsprechende Ansichtsmodell übermitteln. Der etablierte Mechanismus hierfür ist natürlich
INotifyPropertyChanged
, was bedeutet, dass Sie einen Workflow wie diesen erhalten:PropertyChanged
Ereignis des ModellsDataContext
, die Eigenschaften sind gebunden usw.PropertyChanged
und erhöhtPropertyChanged
als Antwort seine eigenenAuf der anderen Seite, wenn Ihre Modelle wenig (oder keine) Geschäftslogik enthielten oder wenn Sie aus einem anderen Grund (z. B. um Transaktionsfähigkeit zu erlangen) beschlossen haben, jedes Ansichtsmodell sein umschlossenes Modell "besitzen" zu lassen, werden alle Änderungen am Modell durchlaufen das viewmodel wäre also eine solche anordnung nicht notwendig.
Ich beschreibe eine solche Konstruktion in einem anderen MVVM Frage hier .
quelle
Deine Entscheidungen:
Aus meiner
INotifyPropertyChanged
Sicht ist dies ein grundlegender Bestandteil von .Net. dh es ist inSystem.dll
. Die Implementierung in Ihrem "Modell" ähnelt der Implementierung einer Ereignisstruktur.Wenn Sie reines POCO möchten, müssen Sie Ihre Objekte effektiv über Proxys / Services manipulieren. Anschließend wird Ihr ViewModel durch Abhören des Proxys über Änderungen informiert.
Persönlich implementiere ich INotifyPropertyChanged nur lose und verwende dann FODY , um die Drecksarbeit für mich zu erledigen. Es sieht aus und fühlt sich an wie POCO.
Ein Beispiel (Verwenden von FODY zum IL-Weben der PropertyChanged-Raiser):
Anschließend kann Ihr ViewModel PropertyChanged auf Änderungen abhören. oder eigenschaftsspezifische Änderungen.
Das Schöne an der INotifyPropertyChanged-Route ist, dass Sie sie mit einer Extended ObservableCollection verketten . Sie legen also Ihre Near-Poco-Objekte in einer Sammlung ab und hören sich die Sammlung an. Wenn sich irgendwo etwas ändert, erfahren Sie mehr darüber.
Ich bin ehrlich, dies könnte an der Diskussion "Warum wurde INotifyPropertyChanged nicht automatisch vom Compiler behandelt" teilnehmen, die sich auf Folgendes konzentriert: Jedes Objekt in c # sollte die Möglichkeit haben, zu benachrichtigen, wenn ein Teil davon geändert wurde; dh implementieren Sie standardmäßig INotifyPropertyChanged. Dies ist jedoch nicht der Fall, und der beste Weg, der den geringsten Aufwand erfordert, ist die Verwendung von IL Weaving (speziell FODY ).
quelle
Ziemlich alter Thread, aber nach langem Suchen habe ich meine eigene Lösung gefunden: A PropertyChangedProxy
Mit dieser Klasse können Sie sich einfach bei NotifyPropertyChanged einer anderen Person registrieren und entsprechende Maßnahmen ergreifen, wenn diese für die registrierte Eigenschaft ausgelöst wird.
Hier ist ein Beispiel, wie dies aussehen könnte, wenn Sie eine Modelleigenschaft "Status" haben, die sich selbst ändern kann und dann das ViewModel automatisch benachrichtigen sollte, um sein eigenes PropertyChanged für seine "Status" -Eigenschaft auszulösen, damit die Ansicht auch benachrichtigt wird: )
und hier ist die Klasse selbst:
quelle
-= my_event_handler
), da dies einfacher zu finden ist als ein seltenes + unvorhersehbares Zombie-Problem, das möglicherweise oder möglicherweise nie auftritt.Ich fand diesen Artikel hilfreich: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf
Meine Zusammenfassung:
Die Idee hinter der MVVM-Organisation besteht darin, die Wiederverwendung von Ansichten und Modellen zu vereinfachen und auch entkoppelte Tests zu ermöglichen. Ihr Ansichtsmodell ist ein Modell, das die Ansichtsentitäten darstellt, Ihr Modell repräsentiert die Geschäftsentitäten.
Was wäre, wenn Sie später ein Pokerspiel machen wollten? Ein Großteil der Benutzeroberfläche sollte wiederverwendbar sein. Wenn Ihre Spielelogik in Ihrem Ansichtsmodell gebunden ist, ist es sehr schwierig, diese Elemente wiederzuverwenden, ohne das Ansichtsmodell neu programmieren zu müssen. Was ist, wenn Sie Ihre Benutzeroberfläche ändern möchten? Wenn Ihre Spielelogik mit Ihrer Ansichtsmodelllogik gekoppelt ist, müssen Sie erneut überprüfen, ob Ihr Spiel noch funktioniert. Was ist, wenn Sie einen Desktop und eine Web-App erstellen möchten? Wenn Ihr Ansichtsmodell die Spielelogik enthält, wird es kompliziert, diese beiden Anwendungen nebeneinander zu verwalten, da die Anwendungslogik unweigerlich mit der Geschäftslogik im Ansichtsmodell verknüpft ist.
Datenänderungsbenachrichtigungen und Datenvalidierung erfolgen in jeder Ebene (der Ansicht, dem Ansichtsmodell und dem Modell).
Das Modell enthält Ihre für diese Entitäten spezifischen Datendarstellungen (Entitäten) und Geschäftslogik. Ein Kartenspiel ist eine logische Sache mit inhärenten Eigenschaften. In ein gutes Deck können keine doppelten Karten eingelegt werden. Es muss einen Weg aufzeigen, um die oberste (n) Karte (n) zu erhalten. Es muss wissen, nicht mehr Karten auszugeben, als es übrig hat. Solche Deck-Verhaltensweisen sind Teil des Modells, da sie einem Kartenspiel inhärent sind. Es wird auch Dealer-Modelle, Player-Modelle, Hand-Modelle usw. geben. Diese Modelle können und werden interagieren.
Das Ansichtsmodell würde aus der Präsentations- und Anwendungslogik bestehen. Alle Arbeiten, die mit der Anzeige des Spiels verbunden sind, sind von der Logik des Spiels getrennt. Dies kann das Anzeigen von Händen als Bilder, das Anfordern von Karten an das Händlermodell, Benutzeranzeigeeinstellungen usw. umfassen.
Der Mut des Artikels:
quelle
Benachrichtigung basierend auf INotifyPropertyChanged und INotifyCollectionChanged ist genau das, was Sie brauchen. Um Ihr Leben mit dem Abonnement von Eigenschaftsänderungen, der Validierung des Eigenschaftsnamens zur Kompilierungszeit und der Vermeidung von Speicherlecks zu vereinfachen, würde ich Ihnen empfehlen, PropertyObserver von Josh Smiths MVVM Foundation zu verwenden . Da es sich bei diesem Projekt um Open Source handelt, können Sie Ihrem Projekt genau diese Klasse aus Quellen hinzufügen.
Lesen Sie diesen Artikel, um zu verstehen, wie PropertyObserver verwendet wird .
Schauen Sie sich auch Reactive Extensions (Rx) genauer an . Sie können IObserver <T> aus Ihrem Modell verfügbar machen und es im Ansichtsmodell abonnieren.
quelle
Die Jungs haben großartige Arbeit geleistet, um dies zu beantworten, aber in solchen Situationen habe ich wirklich das Gefühl, dass das MVVM-Muster ein Schmerz ist, also würde ich einen Supervising Controller oder einen Passive View-Ansatz verwenden und das Bindungssystem zumindest für Modellobjekte loslassen, die generieren Änderungen selbstständig.
quelle
Ich befürworte seit langem das Richtungsmodell -> Modell anzeigen -> Änderungsfluss anzeigen, wie Sie im Abschnitt Änderungsfluss meines MVVM-Artikels aus dem Jahr 2008 sehen können. Dies erfordert eine Implementierung
INotifyPropertyChanged
auf dem Modell. Soweit ich das beurteilen kann, ist dies mittlerweile gängige Praxis.Da Sie Josh Smith erwähnt haben, werfen Sie einen Blick auf seine PropertyChanged-Klasse . Es ist eine Hilfsklasse zum Abonnieren des Modells
INotifyPropertyChanged.PropertyChanged
Ereignisses .Sie können diesen Ansatz tatsächlich viel weiter verfolgen, da ich kürzlich meine PropertiesUpdater-Klasse erstellt habe . Eigenschaften im Ansichtsmodell werden als komplexe Ausdrücke berechnet, die eine oder mehrere Eigenschaften im Modell enthalten.
quelle
Es ist nichts Falsches daran, INotifyPropertyChanged in Model zu implementieren und in ViewModel anzuhören. Tatsächlich können Sie sogar in XAML direkt in die Eigenschaft des Modells punkten: {Binding Model.ModelProperty}
Was abhängige / berechnete schreibgeschützte Eigenschaften betrifft, habe ich bei weitem nichts Besseres und Einfacheres gesehen: https://github.com/StephenCleary/CalculatedProperties . Es ist sehr einfach, aber unglaublich nützlich. Es ist wirklich "Excel-Formeln für MVVM" - funktioniert genauso wie Excel, das Änderungen an Formelzellen ohne zusätzlichen Aufwand von Ihrer Seite weitergibt.
quelle
Sie können Ereignisse aus dem Modell auslösen, die das Ansichtsmodell abonnieren müsste.
Zum Beispiel habe ich kürzlich an einem Projekt gearbeitet, für das ich eine Baumansicht generieren musste (natürlich hatte das Modell einen hierarchischen Charakter). Im Modell hatte ich eine beobachtbare Sammlung namens
ChildElements
.Im Ansichtsmodell hatte ich einen Verweis auf das Objekt im Modell gespeichert und das
CollectionChanged
Ereignis der beobachtbaren Sammlung wie folgt abonniert :ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)
...Dann wird Ihr Ansichtsmodell automatisch benachrichtigt, sobald eine Änderung im Modell erfolgt. Sie können dasselbe Konzept mit verwenden
PropertyChanged
, müssen jedoch explizit Eigenschaftsänderungsereignisse aus Ihrem Modell auslösen, damit dies funktioniert.quelle
Dies scheint mir eine wirklich wichtige Frage zu sein - auch wenn kein Druck besteht, dies zu tun. Ich arbeite an einem Testprojekt, das eine TreeView beinhaltet. Es gibt Menüelemente und solche, die Befehlen zugeordnet sind, z. B. Löschen. Derzeit aktualisiere ich sowohl das Modell als auch das Ansichtsmodell aus dem Ansichtsmodell heraus.
Beispielsweise,
Dies ist einfach, scheint aber einen sehr grundlegenden Fehler zu haben. Ein typischer Komponententest würde den Befehl ausführen und dann das Ergebnis im Ansichtsmodell überprüfen. Dies testet jedoch nicht, ob die Modellaktualisierung korrekt war, da beide gleichzeitig aktualisiert werden.
Daher ist es möglicherweise besser, Techniken wie PropertyObserver zu verwenden, damit die Modellaktualisierung eine Ansichtsmodellaktualisierung auslöst. Der gleiche Komponententest würde jetzt nur funktionieren, wenn beide Aktionen erfolgreich wären.
Ich weiß, dass dies keine mögliche Antwort ist, aber es scheint sich zu lohnen, sie herauszubringen.
quelle