Szukaj na tym blogu

wtorek, 17 sierpnia 2010

Dziennik Zdrowej Diety – Wzorzec MVVM – Koncepcja

Aplikacja Dziennik Zdrowej Diety jest udostępniona na codeplex, przez co nad rozwojem projektu może pracować wiele osób. Wśród tych osób znajdą się zapewne programiści oraz osoby odpowiedzialne za projektowanie interfejsu użytkownika. By usprawnić pracę pomiędzy te dwie profesje, zastosowałem wzorzec projektowy MVVM (Model-View-ViewModel).
Czym jest wzorzec MVVM ? To wzorzec, stworzony na potrzeby technologii WPF oraz Silverlight, którego zadaniem jest odspearowanie od siebie kolejnych warstwy aplikacji tj.



View – warstwa prezentacji, czyli plik xaml przedstawiający widok formatki z umieszczonymi na niej kontrolkami. Widokiem może być również pojedyńcza kontrolka użytkownika (UserControl).
Rzeczą charakterystyczną takiej formatki w porównaniu do zwykłaej aplikacji nie wykorzystującej wzorca MVVM (lub pokrewnych) jest fakt, że w code behind formatki nie znajduje się żaden kod związany z logiką biznesową. Dopuszczalne jest posiadanie w code behind kodu operującego na interfejsie użytkownika (nie mającego nic wspólnego z logiką biznesową).
Model – model opisuje naszą logikę biznesową. Składa się on obiektów biznesowych przybierających najczęściej postać obiektów POCO ang. Plain Old CLR Object, czyli klas zawierających dane (w postaci property) oraz logikę biznesową (walidatory danych). Model jak i View nie wiedzą o swoim istnieniu.
ViewModel – to obiekt będący łącznikiem pomiędzy modelem (Model), a widokiem (View). To właśnie ten obiekt odpowiada za pobranie właściwych informacji ze źródła danych, udostępnienia ich w odpowiedniej postaci do widoku (Converter) oraz przekazania zaktualizowanych informacji spowrotem do źródła danych. Interakcja widoku z ViewModel odbywa się za pomocą funkcjonalności Bindowania oraz wsparcia wzorca projektowego Command.


Przykład:

Na potrzeby przykładu stworzyłem prosty model zawierający 2 właściwości i jedną walidację.
namespace SimpleMVVM
{
    public class MyModel
    {
        public string NickName { get; set; }
        public string Description { get; set; }
        
        public bool ValidateNickName(string nickname)
        {
            return string.IsNullOrWhiteSpace(nickname);
        }
    }
}

Kolejnym elementem odgrywającym ważną rolę jest ViewModel, który pobiera dane z naszego modelu i udostępnia je na zewnątrz

public class MyViewModel : INotifyPropertyChanged  
    {
        private readonly MyModel myModel;

        public MyViewModel()
        {
            myModel = new MyModel();
        }

        public string Nickname
        {
            get { return myModel.NickName ?? string.Empty; }
            set 
            {
                if (myModel.ValidateNickName(value))
                {
                    myModel.NickName = value;
                    FirePropertyChanged("Nickname");
                }
            }
        }

        public string Description
        {
            get { return myModel.Description; }
            set
            {
                myModel.Description = value;
                myModel.NickName += " Hello!";
                FirePropertyChanged("Nickname");
                FirePropertyChanged("Description");
            }
        }

        protected void FirePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

W View deklarujemy nasz ViewModel w następujący sposób
<UserControl.Resources>
        <vm:MyViewModel x:Key="myViewModel" />
        <vm:MyConverter x:Key="myConverter" />
    </UserControl.Resources>

Dodatkowo zadeklarowaliśmy konwerter MyConverter, który ma postać

public class MyConverter : IValueConverter
    {
        public MyConverter()
        {
            
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var msg = value as string;
            if (!string.IsNullOrWhiteSpace(msg) && msg.Contains("Hello!"))
                msg = msg.Replace("Hello!", "Hello World!");
            return msg ?? String.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

Na koniec bindujemy kontrolki z View z naszym ViewModel
<TextBox Text="{Binding 
                            Source={StaticResource myViewModel}, 
                            Path=Nickname, 
                            Mode=OneWay}"
                 Grid.Column="1"
                 Grid.Row="0"
                 VerticalAlignment="Center"" />

<TextBox Text="{Binding 
                            Source={StaticResource myViewModel}, 
                            Path=Description, 
                            Mode=TwoWay, 
                            Converter={StaticResource myConverter}}"
                 Grid.Column="1"
                 Grid.Row="1"
                 VerticalAlignment="Center" />


Działanie:
Pierwszy TextBox jest zbindowany z właściwością Nickname w MyViewModel. Jest to bindowanie typu OneWay co oznacza, że widok pobiera wartości z ViewModel, a ViewModel nic nie wie o ewentualnych zmianach wartości tego TextBox’a.

Drugi TextBox jest zbindowany z właściwością Description w MyViewModel. Jest to bindowanie typu TwoWay co oznacza, że zarówno widok, jak i ViewModel są informowane o zmianie wartości zbindowanej właściwości. Dodatkowo wartość wysyłana do TextBox’a podlega konwersji w klasie MyConverter.

Efekt:
• wpisujemy w pierwszego TextBox’a tekst „Ala ma kota” – nic się nie dzieje,
• wpisujemy w drugiego TextBox’a tekst „Hello!”
• ustawia się właściwość Description instancji klasy myModel na „Hello!”
• ustawia się właściwość NickName instancji klasy myModel na „ Hello!” (zauważ, że pomimo wpisania w pierwszego TextBox’a wartości „Ala ma kota”, nie ma jej po stronie ViewModel)
• odpalane jest powiadomienie o zmianie właściwości „Description”
• pobierana jest wartość właściwości „Description” z uwzględnieniem konwertera,
• pobierana jest wartość właściwości „Nickname”

Kod źródłowy

Ktoś pewno zada pytanie „Poco to wszystko ?”. Otóż korzystając ze wzorca MVVM poza separacją widoku (projektant) od całej reszty (programista) mamy szereg dodatkowych plusów tj. możliwość stosowania testów jednostkowych, uproszczone zarządzanie kodem, schemat działania pozwalający na systematyczny rozwój aplikacji oraz łatwe wdrożenie nowych programistów do projektu.

Powyższy opis ma na celu ogólne przedstawienie koncepcji tworzenia oprogramowania przy wykorzystaniu wzorca MVVM. Ma to posłużyć większemu zrozumieniu kolejnych kroków związanych z rozwojem aplikacji „Dziennik Zdrowej Diety”.
Po więcej szczegółów odsyłam do artykułu zamieszczonego w MSDN Magazine (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx).

Wartym wspomnienia jest fakt, że wzorzec MVVM zyskał na popularności dzięki czemu wielu programistów zaczęło tworzyć własne rozszerzenia tego wzorca.
I tak np. Calcium wspiera funkcjonalność Undo/Redo, ale jest opracowany tylko dla WPF podczas gdy Cinch nadaje się zarówno do WPF jak i Silverlight i dodatkowo współpracuje z MEF (Managed Extensibility Framework).
By przyjrzeć się zestawieniu frameworków MVVM odysłam pod poniższy adres:
(http://www.japf.fr/silverlight/mvvm/index.html)

Brak komentarzy:

Prześlij komentarz