MVVM in WPF - Wie kann ViewModel über Änderungen im Modell informiert werden? Oder sollte ich?

112

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, Decksondern auch die BlackJackGameKlasse , 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 BlackJackGameIch stelle Methoden wie "DrawCard" zur Verfügung und es kam mir der Gedanke, dass beim Ziehen einer Karte Eigenschaften wie CardScoreund IsBustaktualisiert 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?

Dave
quelle
3
Die Interaktion, die Sie beschreiben, klingt wie ein Standard-Ereignismechanismus. Das Modell kann ein aufgerufenes Ereignis OnBustverfügbar machen und die VM kann es abonnieren. Ich denke, Sie könnten auch einen IEA-Ansatz verwenden.
Code4life
Ich bin ehrlich, wenn ich eine echte Blackjack-App erstellen würde, würden sich meine Daten hinter ein paar Schichten von Diensten / Proxys und einer pedantischen Ebene von Unit-Tests verstecken, die A + B = C ähneln. Es wäre der Proxy / Service, der über Änderungen informiert.
Meirion Hughes
1
Danke an alle! Leider kann ich nur eine Antwort auswählen. Ich wähle Rachel's wegen der zusätzlichen Architekturberatung und der Bereinigung der ursprünglichen Frage. Aber es gab viele gute Antworten und ich schätze sie. -Dave
Dave
2
FWIW: Nachdem ich einige Jahre mit der Komplexität der Wartung von VM- und M-pro-Domain-Konzept zu kämpfen hatte, glaube ich jetzt, dass beides DRY fehlschlägt. Die erforderliche Trennung von Bedenken kann einfacher erfolgen, indem zwei SCHNITTSTELLEN für ein einzelnes Objekt vorhanden sind - eine "Domänenschnittstelle" und eine "ViewModel-Schnittstelle". Dieses Objekt kann ohne Verwirrung oder mangelnde Synchronisation sowohl an die Geschäftslogik als auch an die Ansichtslogik übergeben werden. Dieses Objekt ist ein "Identitätsobjekt" - es repräsentiert die Entität eindeutig. Um die Trennung von Domänencode und Ansichtscode aufrechtzuerhalten, sind dann bessere Tools innerhalb einer Klasse erforderlich.
ToolmakerSteve

Antworten:

61

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:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

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 EventAggregatoroder MVVM Light verwenden Messenger.

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 PlayerScoreHasChangedMessageObjekt von einem Objekt senden , und ein anderes Objekt kann abonnieren, um auf diese Nachrichtentypen zu warten und seine PlayerScoreEigenschaft 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:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

und Sie hätten ein ViewModel-Objekt wie

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(Die oben genannten Objekte sollten alle implementiert werden INotifyPropertyChanged, aber ich habe es der Einfachheit halber weggelassen.)

Rachel
quelle
3
Gehen im Allgemeinen alle Geschäftslogiken / -regeln in das Modell? Wohin geht die ganze Logik, die besagt, dass man eine Karte bis zu 21 nehmen kann (aber der Dealer bleibt bei 17), dass man Karten teilen kann usw. Ich nahm an, dass alles in die Modellklasse gehört und aus diesem Grund fühlte ich mich gebraucht eine BlacJackGame-Controller-Klasse im Modell. Ich versuche immer noch, dies zu verstehen und würde mich über Beispiele / Referenzen freuen. Die Idee des Blackjack als Beispiel wurde aus einer iTunes-Klasse für iOS-Programmierung übernommen, in der die Geschäftslogik / -regeln definitiv in der Modellklasse eines MVC-Musters liegen.
Dave
3
@ Dave Ja, die 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.
Rachel
6
Danke Rachel für all die Hilfe. Ich muss das noch etwas recherchieren oder eine andere Frage schreiben. Ich bin immer noch verwirrt über den Ort der Spielelogik. Sie (und andere) befürworten, es in das ViewModel aufzunehmen, andere sagen "Geschäftslogik", was in meinem Fall davon ausgeht, dass Spielregeln und Spielstatus zum Modell gehören (siehe zum Beispiel: msdn.microsoft.com/en-us) /library/gg405484%28v=pandp.40%29.aspx ) und stackoverflow.com/questions/10964003/… ). Ich erkenne, dass es in diesem einfachen Spiel wahrscheinlich nicht viel ausmacht. Wäre aber schön zu wissen. Danke!
Dave
1
@ Dave Ich verwende möglicherweise den Begriff "Geschäftslogik" falsch und verwechsle ihn mit der Anwendungslogik. Um den von Ihnen verlinkten MSDN-Artikel zu zitieren: "Um die Wiederverwendungsmöglichkeiten zu maximieren, sollten Modelle kein anwendungsfall- oder benutzeraufgabenspezifisches Verhalten oder keine Anwendungslogik enthalten" und "In der Regel definiert das Ansichtsmodell Befehle oder Aktionen, die dargestellt werden können in der Benutzeroberfläche und dass der Benutzer aufrufen kann " . Dinge wie a DrawCardCommand()wären also im ViewModel, aber ich denke, Sie könnten ein BlackjackGameModelObjekt haben, das eine DrawCard()Methode enthält, die der Befehl aufgerufen hat, wenn Sie wollten
Rachel
2
Vermeiden Sie Speicherlecks. Verwenden Sie ein WeakEvent-Muster. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
24

Kurze 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:

  1. Viewmodel wird erstellt und umschließt das Modell
  2. Viewmodel abonniert das PropertyChangedEreignis des Modells
  3. Das Ansichtsmodell wird als Ansicht festgelegt DataContext, die Eigenschaften sind gebunden usw.
  4. Ansicht löst eine Aktion für das Ansichtsmodell aus
  5. Viewmodel ruft die Methode für das Modell auf
  6. Modell aktualisiert sich selbst
  7. Viewmodel verarbeitet Modelle PropertyChangedund erhöht PropertyChangedals Antwort seine eigenen
  8. Die Ansicht spiegelt die Änderungen in den Bindungen wider und schließt die Rückkopplungsschleife

Auf 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 .

Jon
quelle
Hallo, die Liste, die Sie erstellt haben, ist brillant. Ich habe jedoch ein Problem mit 7. und 8. Insbesondere: Ich habe ein ViewModel, das INotifyPropertyChanged nicht implementiert. Es enthält eine Liste von untergeordneten Elementen, die eine Liste von untergeordneten Elementen selbst enthält (es wird als ViewModel für ein WPF-Treeview-Steuerelement verwendet). Wie kann ich das UserControl DataContext ViewModel dazu bringen, Eigenschaftsänderungen in einem der untergeordneten Elemente (TreeviewItems) "abzuhören"? Wie genau abonniere ich alle untergeordneten Elemente, die INotifyPropertyChanged implementieren? Oder sollte ich eine separate Frage stellen?
Igor
4

Deine Entscheidungen:

  • Implementieren Sie INotifyPropertyChanged
  • Veranstaltungen
  • POCO mit Proxy-Manipulator

Aus meiner INotifyPropertyChangedSicht ist dies ein grundlegender Bestandteil von .Net. dh es ist in System.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):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

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 ).

Meirion Hughes
quelle
4

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: )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

und hier ist die Klasse selbst:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}
Roemer
quelle
1
Vermeiden Sie Speicherlecks. Verwenden Sie ein WeakEvent-Muster. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
1
@JJS - OTOH, betrachten Sie das schwache Ereignismuster als gefährlich . Persönlich würde ich lieber einen Speicherverlust riskieren, wenn ich vergesse, die Registrierung aufzuheben ( -= my_event_handler), da dies einfacher zu finden ist als ein seltenes + unvorhersehbares Zombie-Problem, das möglicherweise oder möglicherweise nie auftritt.
ToolmakerSteve
@ToolmakerSteve danke für das Hinzufügen eines ausgewogenen Arguments. Ich schlage vor, dass Entwickler in ihrer eigenen Situation das tun, was für sie am besten ist. Übernehmen Sie den Quellcode nicht blind aus dem Internet. Es gibt andere Muster wie das EventAggregator / EventBus, das häufig für komponentenübergreifende Nachrichten verwendet wird (die ebenfalls mit eigenen Gefahren verbunden sind)
JJS
2

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:

Grundsätzlich möchte ich dies so erklären, dass Ihre Geschäftslogik und Entitäten das Modell umfassen. Dies ist, was Ihre spezifische Anwendung verwendet, kann aber von vielen Anwendungen gemeinsam genutzt werden.

Die Ansicht ist die Präsentationsebene - alles, was sich auf die direkte Verbindung mit dem Benutzer bezieht.

Das ViewModel ist im Grunde der "Klebstoff", der für Ihre Anwendung spezifisch ist und die beiden miteinander verbindet.

Ich habe hier ein schönes Diagramm, das zeigt, wie sie sich verbinden:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

In Ihrem Fall - lassen Sie uns einige der Besonderheiten angehen ...

Validierung: Dies erfolgt normalerweise in zwei Formen. Die Validierung in Bezug auf Benutzereingaben erfolgt im ViewModel (hauptsächlich) und in der Ansicht (dh: Die Textbox "Numeric", die die Eingabe von Text verhindert, wird in der Ansicht für Sie behandelt usw.). Daher ist die Validierung der Eingaben des Benutzers in der Regel ein VM-Problem. Davon abgesehen gibt es häufig eine zweite "Validierungsschicht" - dies ist die Validierung, dass die verwendeten Daten den Geschäftsregeln entsprechen. Dies ist häufig Teil des Modells selbst. Wenn Sie Daten an Ihr Modell senden, kann dies zu Validierungsfehlern führen. Die VM muss diese Informationen dann wieder der Ansicht zuordnen.

Operationen "hinter den Kulissen ohne Ansicht, wie Schreiben in die Datenbank, Senden von E-Mails usw.": Dies ist wirklich Teil der "domänenspezifischen Operationen" in meinem Diagramm und wirklich nur Teil des Modells. Dies ist, was Sie versuchen, über die Anwendung verfügbar zu machen. Das ViewModel fungiert als Brücke, um diese Informationen verfügbar zu machen, aber die Operationen sind reine Modelle.

Vorgänge für das ViewModel: Das ViewModel benötigt mehr als nur INPC - es benötigt auch alle Vorgänge, die für Ihre Anwendung spezifisch sind (nicht für Ihre Geschäftslogik), z. B. Speichern von Einstellungen und Benutzerstatus usw. Dies wird die App variieren. per App., auch wenn das gleiche "Modell" angeschlossen wird.

Eine gute Möglichkeit, darüber nachzudenken - Angenommen, Sie möchten zwei Versionen Ihres Bestellsystems erstellen. Der erste ist in WPF und der zweite ist eine Webschnittstelle.

Die gemeinsame Logik, die sich mit den Bestellungen selbst befasst (Senden von E-Mails, Eingeben in die Datenbank usw.), ist das Modell. Ihre Anwendung stellt diese Vorgänge und Daten dem Benutzer zur Verfügung, jedoch auf zwei Arten.

In der WPF-Anwendung ist die Benutzeroberfläche (mit der der Betrachter interagiert) die "Ansicht" - in der Webanwendung ist dies im Grunde der Code, der (zumindest eventuell) auf dem Client in Javascript + HTML + CSS umgewandelt wird.

Das ViewModel ist der Rest des "Klebers", der erforderlich ist, um Ihr Modell anzupassen (diese Vorgänge beziehen sich auf die Bestellung), damit es mit der spezifischen Ansichtstechnologie / Ebene funktioniert, die Sie verwenden.

VoteCoffee
quelle
Vielleicht ist ein einfaches Beispiel ein Musikplayer. Ihre Modelle enthalten die Bibliotheken und die aktiven Sounddateien und Codecs sowie die Player-Logik und den Code für die digitale Signalverarbeitung. Die Ansichtsmodelle würden Ihre Steuerelemente und Visualisierungen sowie den Bibliotheksbrowser enthalten. Es ist eine Menge UI-Logik erforderlich, um all diese Informationen anzuzeigen, und es wäre schön, wenn sich ein Programmierer darauf konzentrieren könnte, die Musik abzuspielen, während sich ein anderer Programmierer darauf konzentrieren könnte, die UI intuitiv und unterhaltsam zu gestalten. Ansichtsmodell und Modell sollten es diesen beiden Programmierern ermöglichen, sich auf eine Reihe von Schnittstellen zu einigen und getrennt zu arbeiten.
VoteCoffee
Ein weiteres gutes Beispiel ist eine Webseite. Die serverseitige Logik entspricht im Allgemeinen einem Modell. Die clientseitige Logik entspricht im Allgemeinen einem Ansichtsmodell. Ich würde mir leicht vorstellen, dass die Spielelogik auf den Server gehört und nicht dem Client anvertraut wird.
VoteCoffee
2

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.

Vladimir Dorokhov
quelle
Vielen Dank, dass Sie auf Josh Smiths hervorragenden Artikel verwiesen und über schwache Ereignisse berichtet haben!
JJS
1

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.

Ibrahim Najjar
quelle
1

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 ImplementierungINotifyPropertyChanged 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 ModellsINotifyPropertyChanged.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.

HappyNomad
quelle
1

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.

KolA
quelle
0

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 CollectionChangedEreignis 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.

Maische
quelle
Wenn mit hierarchischen Daten zu tun, sollten Sie betrachten Demo 2 von meinem MVVM Artikel .
HappyNomad
0

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,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

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.

Kunst
quelle