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 INotifyPropertyChanged
i 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
, Deck
ale także BlackJackGame
klasę, 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 BlackJackGame
I narazić metod, takich jak „drawcard” i dotarło do mnie, że gdy karta jest rysowany, właściwości, takich jak CardScore
, i IsBust
powinny 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?
OnBust
, a maszyna wirtualna może je subskrybować. Myślę, że możesz również zastosować podejście IEA.Odpowiedzi:
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:
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
EventAggregator
lub MVVM LightMessenger
.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ć
PlayerScoreHasChangedMessage
z jednego obiektu, a inny obiekt może subskrybować, aby nasłuchiwać tego typu wiadomości i aktualizować swojąPlayerScore
wł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:
i miałbyś obiekt ViewModel, taki jak
(Wszystkie powyższe obiekty powinny być implementowane
INotifyPropertyChanged
, ale pominąłem to dla uproszczenia)źródło
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.DrawCardCommand()
byłyby w ViewModel, ale myślę, że możesz miećBlackjackGameModel
obiekt, który zawieraDrawCard()
metodę, którą polecenie wywołało, jeśli chceszKró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:PropertyChanged
zdarzenie modelaDataContext
, właściwości są powiązane itpPropertyChanged
iPropertyChanged
w odpowiedzi podnosi swój własnyZ 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 .
źródło
Twoje wybory:
Jak widzę,
INotifyPropertyChanged
jest to podstawowa część .Net. czyli jest wSystem.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):
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 ).
źródło
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: )
a oto sama klasa:
źródło
-= my_event_handler
), ponieważ jest to łatwiejsze do wyśledzenia niż rzadki + nieprzewidywalny problem z zombie, który może się nigdy nie wydarzyć.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:
źródło
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.
źródło
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.
źródło
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
INotifyPropertyChanged
w 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.PropertyChanged
zdarzenia 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.
źródło
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.
źródło
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
CollectionChanged
zdarzenie 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.źródło
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,
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ć.
źródło