Wpis z mikrobloga

#programowanie #csharp #mvvm

Mam pewien problem i za cholerę w internetach nie mogę znaleźć poprawnej implementacji jego rozwiązania. Rzecz dotyczy wzorca MVVM.

Zdecydowana większość prostych przykładów przedstawia:
* jeden widok, załóżmy wyświetlający wiek w textboxie (bindowanie OneWay),
* jeden viewmodel zawierający model,
* jeden model zawierający omawiane pole 'Wiek'

Jak wiemy, główną ideą wzorca MVVM jest odesparowanie widoku od modelu poprzez ViewModel. Powiedzmy że jeśli chodzi o aktualizowanie pola 'Wiek' z widoku, możemy to zrealizować tak, że w ViewModel stworzymy właściwość 'Wiek', a widok będzie miał przycisk do którego będzie podpięta komenda która będzie aktualizowała właściwość 'Wiek' w modelu właściwością 'Wiek' z ViewModelu. W ten sposób unikniemy bindowania bezpośrednio do modelu.

Problem pojawia się jeśli cokolwiek z zewnątrz aktualizuje nam pole 'Wiek' i chcemy to zaktualizować w widoku. Przy rozwiązaniu przedstawionym w poprzednim akapicie... nie da się. Co z tego że w modelu zaimplementujemy INotifyPropertyChanged, skoro nie wywoła to OnPropertyChanged() już w odpowiadającej właściwości samym ViewModelu? Rozwiązanie to bindowanie prosto do modelu, ale przecież mieliśmy odseparować widok od modelu... Kolejną opcją jaka przychodzi mi do głowy jest zasubskrybowanie PropertyChanged z modelu w ViewModelu i po wywołaniu tego eventu, zaktualizowanie odpowiadającej właściwości w ViewModelu.

Wiem że zakręciłem, ale jak przeczytacie trzy razy to w końcu zrozumiecie. ;) Pytania brzmią:

1. Jak powinno być zaimplementowane rozwiązanie podanego problemu (jedna właściwość, twoway binding, MVVM)?
3. W jaki sposób ViewModel powinien być pośrednikiem między widokiem a modelem w bindowaniu dwukierunkowym?
  • 15
  • Odpowiedz
  • Otrzymuj powiadomienia
    o nowych komentarzach

@bartoneczek: W pracy robimy coś takiego:
ViewModel (implementuje INotifyPropertyChanged):
Wiek { get{ return model.wiek;} set{ if(model.wiek != value){_model.wiek = value; OnPropertyChanged();}}
model nie implementuje INotifyPropertyChanged

edit: komendy aktualizują ViewModel
  • Odpowiedz
@bartoneczek:

W jaki sposób ViewModel powinien być pośrednikiem między widokiem a modelem w bindowaniu dwukierunkowym?


Nie do końca rozumiem Twoje pytanie. View-Model ma przetwarzać dane reprezentowane przez Model i udostępniać je dla View poprzez upublicznianie swoich właściwości
  • Odpowiedz
@bartoneczek: Powinieneś implementować INotifyPropertyChanged w klasach, które są obserwowane przez inne obiekty. Po co implementujesz ten interfejs w klasie modelu, skoro (jak się domyślam) nikt go nie obserwuje? Skoro View obserwuje View-Model w celu aktualizacji UI, to niech View-Model będzie INotifyPropertyChanged, a nie Model.
  • Odpowiedz
@lavsprat: to jeszcze raz: jak poinformować viewmodel a co za tym idzie widok, o zmianach w modelu?

Prawdopodobnie w końcu zadałem to pytanie w googlu poprawnie i znalazłem taką oto odpowiedź:

http://stackoverflow.com/questions/15439841/mvvm-in-wpf-how-to-alert-viewmodel-of-changes-in-model-or-should-i

Jak widać w najlepszej odpowiedzi, INotifyPropertyChanged w modelu i subskrybowanie PropertyChanged w ViewModelu to chyba jedyna opcja...
  • Odpowiedz
@bartoneczek: Ten przypadek zakłada, że Model jest typem złożonym mającym po kilka pól, a ViewModel udostępnia widokowi tylko pojedyncze właściwości. Wtedy rzeczywiście trzeba jakoś pośredniczyć. W Twoim przypadku udostępniany jest cały model.

Zadam Ci inne pytanie: czy Wiek to tylko opakowana w klasę liczba całkowita? Jeżeli tak, to jakiego typu jest właściwość do której bindujesz we View-Model-u?
  • Odpowiedz
@lavsprat: Wiek to tylko przykład modelu, może to być równie dobrze Imię, Nazwisko i Stanowisko będące modelem pracownika.

Odchodząc od przykładu z aktualizowaniem danych po sockecie: powiedzmy że przypadek dotyczy modelu wyświetlanego przez widok, ale zmienianego przez zupełnie inny obiekt niż ViewModel. Cytując fragment najlepszej odpowiedzi ze stacka:

"But typically this is only needed if more than one object will be making changes to the Model's data, which is not
  • Odpowiedz
@lavsprat: klasa modelu i właściwość we ViewModelu nie są tym samym typem. Poszczególne właściwości Modelu natomiast są odwzorowane w ViewModelu (te same typy). Rozwiązanie @boo007 jest jak najbardziej częścią rozwiązania mojego problemu.
  • Odpowiedz
@bartoneczek: Poprawne rozwiązanie zależy od tego jaki obiekt świata rzeczywistego opisuje model i co powinno być udostępniane widokowi. Jeżeli sytuacja pytającego na SO jest analogiczna do Twojej, to powinieneś zrobić tak jak mu napisali.

Nie ma jednego uniwersalnego najlepszego rozwiązania. Dla małych modeli powinno się bindować całe obiekty i użyć atrybutu PropertyPath w widoku - stąd też pytałem o charakter klasy Wiek.
  • Odpowiedz
Nie ma jednego uniwersalnego najlepszego rozwiązania. Dla małych modeli powinno się bindować całe obiekty i użyć PropertyPath w widoku - stąd też pytałem o charakter klasy Wiek.


@lavsprat: tak, są różne szkoły. Ale czy to rozwiązanie przypadkiem nie zaprzecza głównej idei MVVM o której wspomniałem na początku, czyli absolutnego odseparowania widoku od modelu? Tak, rozumiem, mały model, ale mimo wszystko. :)
  • Odpowiedz
@bartoneczek: Ani Model ani View nie mają do siebie żadnych referencji, więc wszystko jest okej (pośredniczy Binder). Bindowanie właściwości o typie modelu to najnormalniejsza praktyka w MVVM (pokuszę się nawet o stwierdzenie, że najczysztsza). Po to zresztą jest atrybut PropertyPath, żeby można było sobie wyłuskać interesujące pole.
  • Odpowiedz