Wzorzec projektowy MVVM.
Nazwa wzorca MVVM pochodzi od nazw Model-View-ViewModel:
- View – cała warstwa prezentacji, czyli dla nas XAML. Szereg innych języków i technologii może implementować MVVM ale dla nas na razie wyznacznikiem będzie XAML. (Dla Win7 będzie to powoli zachodzący WPF). Ideą przewodnią jest stworzenie takiego widoku, który po stronie Code-Behind będzie miał tylko domyślny kod.
- ViewModel – jest to warstwa środkowa, różne spotkałem definicje od czasu gdy sam zająłem się tym tematem. Jedna z nich stanowiła, że ViewModel jest tylko małym pośrednikiem pomiędzy widokiem i modelem. Inne, że całym mózgiem aplikacji. Obie teorie są trafne, zależy tylko jakie są wymagania.
- Model – ostatnia warstwa, zapewnia dostęp do danych. Tak jak pisałem przy ViewModelu różne teorie podlegają pod Model. Jeśli chodzi o komunikację z ViewModelem ideowo jest to też INotifyPropertyChanged, IObservable itp – oddzielenie warstw, osoba odpowiedzialna za tą część robi swoje nie myśli o czymś innym.
Jak zostało wspomniane wyżej – naszym celem jest stworzenie takiej aplikacji, która w code-behind widoku, którego będziemy używać ma tylko i wyłącznie kod, który został utworzony przez Visual Studio.
W niektórych poradnikach możecie znaleźć informację o tym, żeby dodać w kodzie linijkę ustawiającą DataContext na klasę naszego ViewModelu – jednak nie jest to też do końca poprawne, ponieważ możemy ją równie dobrze, bez większego problemu zdefiniować w XAMLu.
Poniższa ilustracja bardzo dobrzeobrazuje, o co chodzi w tym wzorcu projektowym. W jak największym skrócie – oddzielamy warstwę prezentacji (widoki) od warstwy danych i ich przetwarzania (ViewModele i Modele)
Sposób implementacji
Cały wzorzec MVVM opiera się na implementacji interfejsu INotifyPropertyChanged przez ViewModele w programie.
Aby w całości zaimplementować powyższy interfejs, należy uzupełnić naszą klasę o poniższy kod, który rozwija potrzebne metody.
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Wzorzec projektowy MVVM – zastosowanie w WPF
Przykładowy sposób implementacji możesz zobaczyć na poniższym screenie (został tu pominięty element Modelu ze względu na prostotę przykładu – normalnie korzystalibyśmy tutaj z referencji do obiektu klasy, modelu, danych z np. bazy danych):
Teraz kilka słów wytłumaczenia – każda z właściwości klasy ma dwa pola – prywatne i publiczne. Jest to potrzebne, abyśmy mogli podczas ustawiania nowej wartości pola wywołać zdarzenie OnPropertyChanged(); które to znowu daje znać Frameworkowi WPF o tym, że dana informacja została zmieniona i należy odświeżyć informacje, które wyświetlają się na ekranie.
Zmianę pola HelloMessage w każdej ze zmiennych definiujemy po to, aby później po edycji naszych pól program wiedział, że się zmieniły i mógł wyświetlić aktualną jego wersję.
Utworzenie i odpowiednie opisanie klasy razem z getterami i setterami to dopiero połowa sukcesu. Drugą rzeczą, którą musimy zrobić jest skonfigurowanie widoku w XAML tak, aby korzystał z naszej klasy, jako z ViewModelu.
Implementacja wzorca po stronie widoku jest dużo prostsza niż po stronie (View)Modelu.
Musimy pamiętać tu tylko o dwóch rzeczach:
- Ustawienie DataContext w XAML
- Ustawienie Bindingów tam, gdzie są one potrzebne
Teraz wytłumaczę co i jak: DataContext ustawiamy w sposób dość prosty. Na początku naszego pliku XAML w definicji głównego obietku (w moim przypadku jest to obiekt Window) dodajemy referencję do lokalnej klasy, w tym przypadku jest to:
xmlns:vm=”clr-namespace:MVVMTutorial.Models”
Poniżej dodajemy kolejną linijkę – tym razem już bezpośrednie odwołanie od naszej klasy. Utworzeniem nowego obiektu i ustawieniem wartości odpowiednich pól zajmie się już sam WPF.
W tym miejscu prawdopodobnie Visual Studio powie wam „Hola hola, nie wiem co to jest!” i podświetli całość na niebiesko, podpowiadając, że wg niego taka klasa w tym miejscu nie istnieje. Najszybszym i zawsze działającym dla mnie rozwiązaniem na ten problem, jest po prostu przebudowanie całego projektu – wtedy nasze IDE wie, że dodaliśmy pewne właściwości do tej klasy i potrafi sobie już z nią „poradzić” w widoku designera.
Bindingi zaś ustawiamy już przy odpowiednich kontrolkach. W naszym ViewModelu mamy do dyspozycji cztery pola – imię, nazwisko, wiek oraz wiadomość powitalną.
Wykorzystajmy więc je, aby stworzyć prostą aplikację, która bez wciskania żadnych przycisków, a jedynie po uzupełnieniu wszystkich pól przywita nas ładnym tekstem.
Sposób ustawiania bindingów jest bardzo prosty. Jeżeli mamy już przygotowany ViewModel, który jest przypisany do naszego obiektu, wystarczy użyć nazwy pola we właściwości w kontrolce. Idąc dalej tym tropem, w naszym przypadku mamy 3 pola tekstowe – jedno do każdej zmiennej – bindujemy więc nasze pola do właściwości Text. Czwartym polem jest pole HelloMessage, które bindujemy również do właściwości Text, jednak kontroli TextBlock, która jest odpowiedzialna za wyświetlanie tekstu. Całość będzie więc wyglądać tak jak na screenie:
Teraz nie pozostaje nam nic innego jak uruchomić naszą aplikację i zobaczyć w jaki sposób wszystko działa. Jeżeli wszystko zrobiliśmy jak należy, po wpisaniu danych w każde z pól i przejście do innego, nasza wiadomość na dole będzie się uzupełniać o kolejne zmienne. Jeżeli będziemy mieli ochotę zmienić jedną z wartości – treść komunikatu na dole również się zmieni.