MVVM w WPF - Jak ostrzec ViewModel o zmianach w modelu… czy powinienem?

112

Przeglądam kilka artykułów MVVM, przede wszystkim to i to .

Moje konkretne pytanie brzmi: Jak komunikować zmiany modelu z modelu do ViewModel?

W artykule Josha nie widzę, żeby to robił. ViewModel zawsze pyta Model o właściwości. W przykładzie Rachel ma implementację modelu INotifyPropertyChangedi wywołuje zdarzenia z modelu, ale są one przeznaczone do konsumpcji przez sam widok (zobacz jej artykuł / kod, aby uzyskać więcej informacji o tym, dlaczego to robi).

Nigdzie nie widzę przykładów, w których model ostrzega ViewModel o zmianach właściwości modelu. Martwi mnie to, że być może z jakiegoś powodu nie jest to zrobione. Czy istnieje wzorzec ostrzegania ViewModel o zmianach w modelu? Wydawałoby się to konieczne, ponieważ (1) możliwe, że istnieje więcej niż 1 ViewModel dla każdego modelu i (2) nawet jeśli istnieje tylko jeden ViewModel, niektóre działania na modelu mogą spowodować zmianę innych właściwości.

Podejrzewam, że mogą pojawić się odpowiedzi / komentarze w formularzu „Dlaczego chcesz to zrobić?” komentarze, więc oto opis mojego programu. Jestem nowy w MVVM, więc być może cały mój projekt jest wadliwy. Pokrótce to opiszę.

Programuję coś ciekawszego (przynajmniej dla mnie!) Niż klasy „Klient” czy „Produkt”. Programuję BlackJack.

Mam widok, który nie ma żadnego kodu i po prostu opiera się na powiązaniu z właściwościami i poleceniami w ViewModel (zobacz artykuł Josha Smitha).

Na dobre lub na złe przyjąłem postawę, że Model powinien zawierać nie tylko takie klasy, jak PlayingCard, Deckale także BlackJackGameklasę, która utrzymuje stan całej gry i wie, kiedy gracz przegrał, krupier musi dobrać karty i jaki jest aktualny wynik gracza i krupiera (mniej niż 21, 21, przegrana itp.).

Od BlackJackGameI narazić metod, takich jak „drawcard” i dotarło do mnie, że gdy karta jest rysowany, właściwości, takich jak CardScore, i IsBustpowinny zostać zaktualizowane i te nowe wartości przekazywane ViewModel. Może to błędne myślenie?

Można przyjąć postawę, że ViewModel nazwał tę DrawCard()metodę, więc powinien wiedzieć, aby poprosić o zaktualizowany wynik i dowiedzieć się, czy jest popiersie, czy nie. Opinie?

W moim ViewModel mam logikę, aby pobrać rzeczywisty obraz karty do gry (na podstawie koloru, rangi) i udostępnić go do widoku. Model nie powinien się tym przejmować (być może inny ViewModel użyłby po prostu liczb zamiast obrazów kart do gry). Oczywiście, być może niektórzy powiedzą mi, że Model nie powinien mieć nawet koncepcji gry w blackjacka, a to powinno być obsługiwane w ViewModel?

Dave
źródło
3
Interakcja, którą opisujesz, brzmi jak standardowy mechanizm zdarzeń, to wszystko, czego potrzebujesz. Model może uwidaczniać zdarzenie wywoływane OnBust, a maszyna wirtualna może je subskrybować. Myślę, że możesz również zastosować podejście IEA.
code4life
Będę szczery, gdybym zrobił prawdziwą „aplikację” do blackjacka, moje dane byłyby ukryte za kilkoma warstwami usług / serwerów proxy i pedantycznym poziomem testów jednostkowych zbliżonym do A + B = C. Byłby to serwer proxy / serwis informujący o zmianach.
Meirion Hughes
1
Dziękuję wszystkim! Niestety mogę wybrać tylko jedną odpowiedź. Wybieram Rachel ze względu na dodatkowe rady dotyczące architektury i porządkowanie pierwotnego pytania. Ale było wiele świetnych odpowiedzi i doceniam je. -Dave
Dave
2
FWIW: Po kilku latach zmagań ze złożonością utrzymania koncepcji maszyn wirtualnych i M na domenę, teraz uważam, że posiadanie obu zawodzi DRY; potrzebne rozdzielenie problemów można wykonać łatwiej, mając dwa INTERFEJSY na jednym obiekcie - „Interfejs domeny” i „Interfejs ViewModel”. Ten obiekt można przekazać zarówno do logiki biznesowej, jak i do logiki widoku, bez zamieszania lub braku synchronizacji. Ten przedmiot jest „obiektem tożsamości” - w unikalny sposób reprezentuje byt. Utrzymanie separacji kodu domeny od kodu wyświetlania wymaga wtedy lepszych narzędzi do tego wewnątrz klasy.
ToolmakerSteve

Odpowiedzi:

61

Jeśli chcesz, aby modele ostrzegały ViewModels o zmianach, powinny zaimplementować INotifyPropertyChanged , a ViewModels powinny subskrybować, aby otrzymywać powiadomienia PropertyChange.

Twój kod może wyglądać mniej więcej tak:

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

Ale zazwyczaj jest to potrzebne tylko wtedy, gdy więcej niż jeden obiekt będzie wprowadzał zmiany w danych Modelu, co zwykle nie ma miejsca.

Jeśli kiedykolwiek zdarzy Ci się przypadek, w którym w rzeczywistości nie masz odniesienia do właściwości Model, aby dołączyć do niej zdarzenie PropertyChanged, możesz użyć systemu przesyłania wiadomości, takiego jak Prism EventAggregatorlub MVVM Light Messenger.

Mam krótki przegląd systemów przesyłania wiadomości na moim blogu, jednak podsumowując, każdy obiekt może rozgłaszać wiadomość, a każdy obiekt może subskrybować, aby nasłuchiwać określonych wiadomości. Możesz więc emitować PlayerScoreHasChangedMessagez jednego obiektu, a inny obiekt może subskrybować, aby nasłuchiwać tego typu wiadomości i aktualizować swoją PlayerScorewłaściwość, gdy ją usłyszy.

Ale nie sądzę, by było to potrzebne w opisanym przez ciebie systemie.

W idealnym świecie MVVM Twoja aplikacja składa się z ViewModels, a Twoje modele to tylko bloki używane do tworzenia aplikacji. Zwykle zawierają tylko dane, więc nie mają metod takich jak DrawCard()(które byłyby w ViewModel)

Więc prawdopodobnie miałbyś zwykłe obiekty danych modelu, takie jak te:

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

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

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

i miałbyś obiekt ViewModel, taki jak

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

(Wszystkie powyższe obiekty powinny być implementowane INotifyPropertyChanged, ale pominąłem to dla uproszczenia)

Rachel
źródło
3
Mówiąc bardziej ogólnie, czy cała logika / reguły biznesowe są uwzględniane w modelu? Gdzie idzie cała logika, która mówi, że możesz wziąć kartę do 21 (ale krupier pozostaje przy 17), że możesz podzielić karty itp. Założyłem, że to wszystko należy do klasy modeli i dlatego czułem, że jestem potrzebny klasa kontrolera BlacJackGame w modelu. Nadal próbuję to zrozumieć i byłbym wdzięczny za przykłady / odniesienia. Na przykład idea blackjacka została zaczerpnięta z klasy iTunes w programowaniu na iOS, gdzie logika / reguły biznesowe są zdecydowanie w klasie modelu wzorca MVC.
Dave,
3
@Dave Tak, DrawCard()metoda byłaby w ViewModel, razem z inną logiką gry. W idealnej aplikacji MVVM powinieneś być w stanie uruchomić aplikację całkowicie bez interfejsu użytkownika, po prostu tworząc ViewModels i uruchamiając ich metody, na przykład za pomocą skryptu testowego lub okna wiersza poleceń. Modele to zazwyczaj tylko modele danych zawierające surowe dane i podstawową walidację danych.
Rachel,
6
Dzięki Rachel za wszelką pomoc. Będę musiał to zbadać więcej lub napisać kolejne pytanie; Nadal jestem zdezorientowany co do lokalizacji logiki gry. Ty (i inni) opowiadacie się za umieszczeniem go w ViewModel, inni mówią „logika biznesowa”, co w moim przypadku zakładam, że reguły gry i stan gry należą do modelu (patrz na przykład: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) i stackoverflow.com/questions/10964003/ ... ). Zdaję sobie sprawę, że w tej prostej grze prawdopodobnie nie ma to większego znaczenia. Ale miło byłoby wiedzieć. Dzięki!
Dave,
1
@Dave Być może niepoprawnie używam terminu „logika biznesowa” i mylę go z logiką aplikacji. Cytując artykuł MSDN, do którego utworzyłeś łącze: „Aby zmaksymalizować możliwości ponownego wykorzystania, modele nie powinny zawierać żadnych zachowań lub logiki aplikacji specyficznych dla przypadku użycia lub zadania użytkownika” oraz „Zazwyczaj model widoku definiuje polecenia lub akcje, które można przedstawić w interfejsie użytkownika i że użytkownik może wywołać ” . Więc rzeczy takie jak a DrawCardCommand()byłyby w ViewModel, ale myślę, że możesz mieć BlackjackGameModelobiekt, który zawiera DrawCard()metodę, którą polecenie wywołało, jeśli chcesz
Rachel
2
Unikaj wycieków pamięci. Użyj wzorca WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
24

Krótka odpowiedź: zależy to od specyfiki.

W twoim przykładzie modele są aktualizowane „samodzielnie” i te zmiany oczywiście muszą jakoś rozprzestrzenić się na widoki. Ponieważ widoki mają tylko bezpośredni dostęp do modeli widoków, oznacza to, że model musi przekazywać te zmiany do odpowiedniego modelu widoku. Ustalony mechanizm to oczywiście INotifyPropertyChanged, co oznacza, że ​​otrzymasz następujący przepływ pracy:

  1. Viewmodel jest tworzony i otacza model
  2. Viewmodel subskrybuje PropertyChangedzdarzenie modela
  3. Viewmodel jest ustawiony jako widok DataContext, właściwości są powiązane itp
  4. Widok wyzwala akcję w modelu widoku
  5. Viewmodel wywołuje metodę w modelu
  6. Model aktualizuje się sam
  7. Viewmodel obsługuje model PropertyChangedi PropertyChangedw odpowiedzi podnosi swój własny
  8. Widok odzwierciedla zmiany w swoich powiązaniach, zamykając pętlę sprzężenia zwrotnego

Z drugiej strony, jeśli twoje modele zawierały niewiele (lub nie zawierały) logiki biznesowej, lub jeśli z jakiegoś innego powodu (takiego jak uzyskanie możliwości transakcyjnych) zdecydowałeś, że każdy viewmodel "posiada" swój opakowany model, wówczas wszystkie modyfikacje modelu przejdą viewmodel, więc taki układ nie byłby konieczny.

Taki projekt opisuję w innym pytaniu MVVM tutaj .

Jon
źródło
Witam, sporządzona przez Ciebie lista jest genialna. Mam jednak problem z 7. i 8. W szczególności: mam ViewModel, który nie implementuje INotifyPropertyChanged. Zawiera listę elementów podrzędnych, która zawiera listę samych elementów podrzędnych (jest używany jako ViewModel dla kontrolki WPF Treeview). Jak sprawić, aby model widoku UserControl DataContext „nasłuchiwał” zmian właściwości w dowolnym elemencie podrzędnym (TreeviewItems)? Jak dokładnie zasubskrybować wszystkie elementy podrzędne, które implementują INotifyPropertyChanged? A może powinienem zadać osobne pytanie?
Igor
4

Twoje wybory:

  • Zaimplementuj INotifyPropertyChanged
  • Wydarzenia
  • POCO z manipulatorem proxy

Jak widzę, INotifyPropertyChangedjest to podstawowa część .Net. czyli jest w System.dll. Zaimplementowanie go w „Modelu” jest podobne do implementacji struktury zdarzenia.

Jeśli chcesz czystego POCO, musisz efektywnie manipulować swoimi obiektami za pośrednictwem serwerów proxy / usług, a następnie Twój ViewModel jest powiadamiany o zmianach przez nasłuchiwanie proxy.

Osobiście po prostu luźno wdrażam INotifyPropertyChanged, a następnie używam FODY do wykonania brudnej roboty za mnie. Wygląda i czuje się POCO.

Przykład (przy użyciu FODY do IL Weave the PropertyChanged raisers):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

wtedy możesz kazać ViewModel nasłuchiwać PropertyChanged pod kątem wszelkich zmian; lub zmiany dotyczące właściwości.

Piękno trasy INotifyPropertyChanged polega na tym, że łączysz ją z rozszerzoną kolekcją ObservableCollection . Zrzucasz więc obiekty bliskie poco do kolekcji i słuchasz kolekcji ... jeśli coś się zmieni, gdziekolwiek, dowiesz się o tym.

Będę szczery, to mogłoby dołączyć do dyskusji "Dlaczego INotifyPropertyChanged nie została automatycznie obsłużona przez kompilator", która sprowadza się do: Każdy obiekt w c # powinien mieć możliwość powiadamiania, jeśli jakakolwiek jego część została zmieniona; tj. domyślnie zaimplementuj INotifyPropertyChanged. Ale tak się nie dzieje i najlepszą drogą, która wymaga najmniejszego wysiłku, jest użycie IL Weaving (konkretnie FODY ).

Meirion Hughes
źródło
4

Dość stary wątek, ale po wielu poszukiwaniach wymyśliłem własne rozwiązanie: PropertyChangedProxy

Dzięki tej klasie możesz łatwo zarejestrować się w NotifyPropertyChanged innej osoby i podjąć odpowiednie działania, jeśli zostanie ona zwolniona dla zarejestrowanej nieruchomości.

Oto przykład tego, jak mogłoby to wyglądać, gdy masz właściwość modelu „Status”, która może się zmienić samodzielnie, a następnie powinna automatycznie powiadomić ViewModel o uruchomieniu własnej właściwości PropertyChanged w jego właściwości „Status”, aby również wyświetlić powiadomienie: )

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

a oto sama klasa:

/// <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
źródło
1
Unikaj wycieków pamięci. Użyj wzorca WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
1
@JJS - OTOH, rozważ wzorzec słabego zdarzenia jest niebezpieczny . Osobiście wolałbym zaryzykować wyciek pamięci, jeśli zapomnę wyrejestrować ( -= my_event_handler), ponieważ jest to łatwiejsze do wyśledzenia niż rzadki + nieprzewidywalny problem z zombie, który może się nigdy nie wydarzyć.
ToolmakerSteve
@ToolmakerSteve dzięki za dodanie wyważonej argumentacji. Sugeruję, aby programiści robili to, co najlepsze dla nich, w ich własnej sytuacji. Nie adoptuj na ślepo kodu źródłowego z internetu. Istnieją inne wzorce, takie jak EventAggregator / EventBus, powszechnie używane wiadomości między komponentami (które są również tworzone z własnymi niebezpieczeństwami)
JJS
2

Ten artykuł okazał się pomocny: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

Moje podsumowanie:

Ideą organizacji MVVM jest umożliwienie łatwiejszego ponownego wykorzystania widoków i modeli, a także umożliwienie niezależnego testowania. Twój model widoku to model, który reprezentuje jednostki widoku, model reprezentuje jednostki biznesowe.

A co by było, gdybyś chciał później zagrać w pokera? Znaczna część interfejsu użytkownika powinna być wielokrotnego użytku. Jeśli logika gry jest powiązana z modelem widoku, byłoby bardzo trudno ponownie wykorzystać te elementy bez konieczności przeprogramowywania modelu widoku. Co jeśli chcesz zmienić interfejs użytkownika? Jeśli logika Twojej gry jest połączona z logiką modelu widoku, musisz ponownie sprawdzić, czy gra nadal działa. A co jeśli chcesz stworzyć aplikację komputerową i internetową? Jeśli twój model widoku zawiera logikę gry, próba utrzymania tych dwóch aplikacji obok siebie stałaby się skomplikowana, ponieważ logika aplikacji byłaby nieuchronnie powiązana z logiką biznesową w modelu widoku.

Powiadomienia o zmianie danych i sprawdzanie poprawności danych mają miejsce w każdej warstwie (widoku, modelu widoku i modelu).

Model zawiera reprezentacje danych (jednostki) i logikę biznesową specyficzną dla tych jednostek. Talia kart to logiczna „rzecz” z nieodłącznymi właściwościami. Dobra talia nie może mieć zduplikowanych kart. Musi ujawnić sposób na zdobycie najlepszych kart. Musi wiedzieć, żeby nie rozdawać więcej kart, niż zostało. Takie zachowania talii są częścią modelu, ponieważ są nieodłączne dla talii kart. Będą również modele dealerów, modele graczy, modele rąk itp. Te modele mogą i będą ze sobą współdziałać.

Model widoku składałby się z prezentacji i logiki aplikacji. Cała praca związana z wyświetlaniem gry jest oddzielona od logiki gry. Może to obejmować wyświetlanie rąk jako obrazów, prośby o karty do modelu sprzedawcy, ustawienia wyświetlania użytkownika itp.

Wnętrzności artykułu:

Zasadniczo lubię to wyjaśniać, że logika biznesowa i jednostki tworzą model. To jest to, czego używa Twoja konkretna aplikacja, ale może być współużytkowane przez wiele aplikacji.

Widok to warstwa prezentacji - wszystko, co dotyczy faktycznego bezpośredniego kontaktu z użytkownikiem.

ViewModel to po prostu „klej” specyficzny dla twojej aplikacji, który łączy te dwa elementy razem.

Mam tutaj ładny diagram, który pokazuje, jak się komunikują:

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

W twoim przypadku - zajmijmy się niektórymi szczegółami ...

Walidacja: zazwyczaj występuje w 2 formach. Walidacja związana z wprowadzaniem danych przez użytkownika miałaby miejsce w ViewModel (głównie) i View (tj .: Pole tekstowe „Numeric” uniemożliwiające wprowadzenie tekstu jest obsługiwane w widoku itp.). W związku z tym walidacja danych wejściowych od użytkownika jest zwykle problemem maszyny wirtualnej. Mając to na uwadze, często istnieje druga „warstwa” walidacji - jest to sprawdzenie, czy używane dane są zgodne z regułami biznesowymi. Często jest to część samego modelu - wypychanie danych do modelu może powodować błędy walidacji. Maszyna wirtualna będzie następnie musiała ponownie odwzorować te informacje w widoku.

Operacje „za kulisami bez widoku, takie jak pisanie do bazy danych, wysyłanie e-maili itp.”: To jest naprawdę część „Operacji specyficznych dla domeny” na moim diagramie i jest tak naprawdę czystą częścią Modelu. To jest to, co próbujesz ujawnić za pośrednictwem aplikacji. ViewModel działa jako pomost do ujawnienia tych informacji, ale operacje są oparte na czystym modelu.

Operacje dla ViewModel: ViewModel potrzebuje czegoś więcej niż tylko INPC - potrzebuje także wszelkich operacji, które są specyficzne dla twojej aplikacji (nie logiki biznesowej), takich jak zapisywanie preferencji i stanu użytkownika, itp. To będzie się zmieniać w aplikacji. przez aplikację, nawet jeśli łączysz się z tym samym „modelem”.

Dobry sposób, aby o tym pomyśleć - Załóżmy, że chcesz stworzyć 2 wersje swojego systemu zamówień. Pierwsza jest w WPF, a druga to interfejs sieciowy.

Wspólną logiką, która zajmuje się samymi zamówieniami (wysyłaniem e-maili, wprowadzaniem do DB, itp.) Jest Model. Twoja aplikacja udostępnia te operacje i dane użytkownikowi, ale robi to na 2 sposoby.

W aplikacji WPF interfejs użytkownika (z którym współdziała przeglądarka) to „widok” - w aplikacji internetowej jest to w zasadzie kod, który (przynajmniej ostatecznie) jest zamieniany na kliencie w javascript + html + css.

ViewModel to pozostała część „kleju”, która jest wymagana do dostosowania modelu (te operacje związane z porządkowaniem), aby działał z określoną technologią / warstwą widoku, której używasz.

VoteCoffee
źródło
Może prostym przykładem jest odtwarzacz muzyki. Twoje modele będą zawierały biblioteki i aktywny plik dźwiękowy i kodeki, logikę odtwarzacza i kod cyfrowego przetwarzania sygnału. Modele widoków zawierałyby twoje kontrolki i wizualizacje oraz przeglądarkę bibliotek. potrzeba dużo logiki interfejsu użytkownika, aby wyświetlić wszystkie te informacje i byłoby miło pozwolić jednemu programiście skupić się na odtwarzaniu muzyki, podczas gdy inny programista może skupić się na uczynieniu interfejsu użytkownika intuicyjnym i przyjemnym. Model widoku i model powinny pozwolić tym dwóm programistom uzgodnić zestaw interfejsów i pracować oddzielnie.
VoteCoffee
Innym dobrym przykładem jest strona internetowa. Logika po stronie serwera jest generalnie odpowiednikiem modelu. Logika po stronie klienta jest generalnie odpowiednikiem modelu widoku. Łatwo bym sobie wyobraził, że logika gry będzie znajdować się na serwerze i nie zostanie powierzona klientowi.
VoteCoffee
2

Powiadomienie oparte na INotifyPropertyChanged i INotifyCollectionChanged jest dokładnie tym, czego potrzebujesz. Aby uprościć swoje życie dzięki subskrypcji zmian nieruchomości, sprawdzaniu poprawności nazwy nieruchomości w czasie kompilacji, unikaniu wycieków pamięci, radzę skorzystać z PropertyObserver z Fundacji MVVM Josha Smitha . Ponieważ ten projekt jest open source, możesz dodać tylko tę klasę do swojego projektu ze źródeł.

Aby zrozumieć, jak korzystać z PropertyObserver, przeczytaj ten artykuł .

Przyjrzyj się też dokładniej rozszerzeniom reaktywnym (Rx) . Możesz uwidocznić IObserver <T> z modelu i zasubskrybować go w widoku modelu.

Vladimir Dorokhov
źródło
Dziękuję bardzo za odniesienie się do świetnego artykułu Josha Smitha i opisanie słabych wydarzeń!
JJS
1

Chłopaki wykonali niesamowitą robotę, odpowiadając na to, ale w sytuacjach takich jak ta naprawdę czuję, że wzorzec MVVM jest uciążliwy, więc skorzystałbym z kontrolera nadzorującego lub podejścia pasywnego widoku i puścił system wiązania przynajmniej dla obiektów modeli, które same generują zmiany.

Ibrahim Najjar
źródło
1

Od dłuższego czasu jestem zwolennikiem modelu kierunkowego -> Wyświetl model -> Wyświetl przepływ zmian, jak widać w sekcji Przepływ zmian w moim artykule MVVM z 2008 roku. Wymaga to implementacji INotifyPropertyChangedw modelu. O ile wiem, od tego czasu stało się to powszechną praktyką.

Ponieważ wspomniałeś o Joshu Smithie, spójrz na jego klasę PropertyChanged . Jest to klasa pomocnicza do subskrybowania INotifyPropertyChanged.PropertyChangedzdarzenia modelu .

Tak naprawdę możesz pójść o wiele dalej, tak jak ostatnio, tworząc moją klasę PropertiesUpdater . Właściwości modelu widoku są obliczane jako złożone wyrażenia, które zawierają co najmniej jedną właściwość modelu.

HappyNomad
źródło
1

Nie ma nic złego w implementacji INotifyPropertyChanged w modelu i słuchaniu go w ViewModel. W rzeczywistości możesz nawet dotknąć właściwości modelu w XAML: {Binding Model.ModelProperty}

Jeśli chodzi o zależne / obliczone właściwości tylko do odczytu, zdecydowanie nie widziałem nic lepszego i prostszego niż to: https://github.com/StephenCleary/CalculatedProperties . To bardzo proste, ale niezwykle przydatne, to naprawdę „formuły Excela dla MVVM” - działa tak samo, jak Excel propaguje zmiany w komórkach formuł bez dodatkowego wysiłku z Twojej strony.

KolA
źródło
0

Możesz podnosić zdarzenia z modelu, który Viewmodel musiałby subskrybować.

Na przykład ostatnio pracowałem nad projektem, dla którego musiałem wygenerować widok drzewa (oczywiście model miał charakter hierarchiczny). W modelu miałem obserwowalną kolekcję o nazwie ChildElements.

W viewmodelu zapisałem odniesienie do obiektu w modelu i zasubskrybowałem CollectionChangedzdarzenie obserwowalnej kolekcji, na przykład: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Następnie model widoku jest automatycznie powiadamiany o zmianie w modelu. Możesz postępować zgodnie z tą samą koncepcją używając PropertyChanged, ale będziesz musiał jawnie wywołać zdarzenia zmiany właściwości z modelu, aby to zadziałało.

Zacier
źródło
Jeśli do czynienia z danych hierarchicznych, warto spojrzeć na Demo 2 z moim MVVM art .
HappyNomad
0

Wydaje mi się, że to naprawdę ważne pytanie - nawet jeśli nie ma na to presji. Pracuję nad projektem testowym, który obejmuje TreeView. Istnieją elementy menu i takie, które są przypisane do poleceń, na przykład Usuń. Obecnie aktualizuję zarówno model, jak i model widoku z poziomu modelu widoku.

Na przykład,

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
}

Jest to proste, ale wydaje się mieć bardzo podstawową wadę. Typowy test jednostkowy wykonywałby polecenie, a następnie sprawdzał wynik w modelu widoku. Ale to nie sprawdza, czy aktualizacja modelu była poprawna, ponieważ oba są aktualizowane jednocześnie.

Być może więc lepiej jest użyć technik takich jak PropertyObserver, aby pozwolić aktualizacji modelu wyzwolić aktualizację modelu widoku. Ten sam test jednostkowy działałby teraz tylko wtedy, gdyby obie akcje zakończyły się pomyślnie.

Zdaję sobie sprawę, że nie jest to potencjalna odpowiedź, ale wydaje się, że warto ją tam przedstawić.

Sztuka
źródło