TSM - MVVM în Windows Phone 8

Cosmin Stirbu - iOS Software Developer

Şablonul de proiectare Model-View-ViewModel (MVVM) defineşte trei componente principale: Model, View şi ViewModel:

Model: Business domain (logica ce ţine de domeniul aplicaţiei, accesul la date, entităţi),

View: Interfaţa cu utilizatorul (în Windows Phone - PhoneApplicationPage),

ViewModel: "Modelul View-ului" - abstractizare a View-ului ce intermediază comunicarea dintre View şi Model.

Una dintre diferenţele majore între Model-View-Controller şi MVVM este că un obiect de tipul ViewModel nu păstrează o referinţă la obiectul de tip View. Obiectul View se leagă (binding) la proprietăţile obiectului ViewModel care în schimb expune datele conţinute în model şi alte stări specifice View-ului. Mecanismul de binding asigură că, atunci când proprietăţile din ViewModel se modifică, cele din View se actualizează automat şi vice-versa. Când utilizatorul apasă un buton, o comandă se execută în ViewModel, View-ul nu modifică niciodată în mod direct entităţile din model. Clasele View nu ştiu de existenţa claselor din model în timp ce clasele ViewModel şi modelul nu ştiu de existenţa claselor View. În acest fel View-ul este pur și simplu un client (consumer) al obiectelor ViewModel, ele putând fi foarte ușor înlocuite cu clase de test pentru ViewModel.

Cum aplicăm MVVM în Windows Phone 8?

MVVM Light este un instrument foarte popular dezvoltat şi întreţinut de Laurent Bugnion, fiind folosit pentru a dezvolta cu rapiditate aplicaţii MVVM în WPF, Silverlight şi Windows Phone. Acest toolkit ne ajută să sepărăm View-ul de Model pentru a crea aplicaţii mai curate şi mai uşor de întreţinut şi de extins, stratul dedicat interfeţei cu utilizatorul este cât se poate de subţire iar astfel aplicaţiile dezvoltate fiind uşor de testat automat.

Sursa: blogs.msdn.com/b/tims/archive/2010/11/02/kung-fu-silverlight-architectural-patterns-and-practices-with-mvvm-and-ria-services.aspx

Una dintre componentele oferite este clasa ViewModelBase pe care fiecare clasă ViewModel a aplicaţiei noastre o poate extinde fără a fi nevoită să implementeze interfaţa INotifyPropertyChanged (pentru a notifica View-ul la schimbarea valorii unei proprietăţi). În general, într-o aplicație Windows Phone 8, fiecărei Pagini îi va corespunde un obiect ViewModel ce va extinde clasa ViewModelBase. Un alt mare avantaj al clasei ViewModelBase este expunerea proprietăţii IsInDesignMode, proprietate ce ne permite să verificăm dacă suntem în Expression Blend sau în Visual Studio Designer şi să oferim "design data" cu care designerul poate lucra. Suntem nevoiţi să facem acest lucru, deoarece atât Blend cât şi Visual Studio Designer nu permit operaţii în reţea sau conexiuni la baze de date.

În general, o abordare posibilă şi des întâlnită este de a expune toate obiectele noastre ViewModel ca proprietăţi folosind clasa oferită de MVVM Light numită ViewModelLocator. În această clasă putem folosi un IoC Container preferat (sau SimpleIoc inclus în MVVM Light) pentru a crea obiectele ViewModel şi pentru a injecta dependenţe în acestea (de exemplu o interfaţă care expune metode pentru a manipula diferite entităţi din model).

Obiectul de tip ViewModelLocator poate fi adăugat în resursele aplicaţiei în App.xaml şi folosit apoi în fiecare pagină pentru a seta DataContext-ul acesteia la ViewModel-ul corespunzător expus în ViewModelLocator.

ViewModelLocator adăugat în mod automat la crearea unui proiect ca resursă în App.xaml:


< Application 
 ...
 xmlns:vm="clr-namespace:{Default Namespace}.ViewModel"
 ...>
  < Application.Resources>
    < ResourceDictionary>
    < !-- Global View Model Locator -->
    < vm:ViewModelLocator x:Key="Locator"
      d:IsDataSource="True" />
      ...
   < /ResourceDictionary>
  < /Application.Resources>
< /Application>

Putem seta ca DataContext pentru Pagina (View) un obiect ViewModel expus ca proprietate în ViewModelLocator pentru a realiza apoi "binding" între proprietățile obiectului ViewModel și Pagină (View). Acest lucru îl putem face fie direct în .xaml sau folosind Blend.

Dacă vrem de exemplu să afișăm conținut într-un obiect TextBlock, tot ce trebuie să facem este să legăm proprietatea Text de o proprietate de tip string în obiectul ViewModel nefiind nevoiți să scriem cod de UI (cunoscut drept code-behind) pentru a actualiza textul din TextBlock-ul respectiv. Legarea o putem face atât în codul XAML cât și în Blend.

string _textProperty
public string TextProperty
{
  get
  {
     return _textProperty;
  }
  set
  {
  if (_textProperty != value)
  {
    _textProperty = value;
    RaisePropertyChanged("TextProperty");
   }
  }
}

Apelul metodei moștenite din ViewModelBase RaisePropertyChanged asigură actualizarea UI-ului de fiecare dată când valoarea proprietății se schimbă.

Codul de legare arată astfel:

< TextBlock Text="{Binding TextProperty}"
 Style="{StaticResource PhoneTextNormalStyle}"
 HorizontalAlignment="Center"
 VerticalAlignment="Center"
 TextAlignment="Center"
 TextWrapping="Wrap"
 FontSize="40" / > 

Practic orice proprietate a unui element de UI poate fi legată de o proprietate din obiectul ViewModel fără a fi nevoiți să scriem cod de UI. De exemplu putem lega proprietatea ItemSource a unui obiect ListBox de o propritetate de tip List din ViewModel și apoi putem configura un ItemTemplate pentru ListBox care va avea ca DataContext obiectul de tip T care conceptual vorbind este și el la rândul său un ViewModel (sau ItemViewModel).

Cum comunică View-ul cu ViewModel-ul?

MVVM Light oferă clasa EventToCommand ce extinde clasa Behavior și ce permite legarea unei comenzi de un eveniment de pe interfața cu utilizatorul. Astfel la apariția unui eveniment putem executa direct codul din ViewModel fără a scrie deloc cod de UI. În ViewModel expunem o proprietate de tipul RelayCommand pe care cu ajutorul clasei EventToCommand o putem lega de orice eveniment.

Evenimentul Click al unui buton este legat de comanda ButtonPressed:

// Button Pressed
public RelayCommand ButtonPressed
{
  get
  {
    return new RelayCommand(() =>
    {
    Console.WriteLine("Button Pressed");
    });
  }
} 

Merită menționat că putem transmite și parametri atunci cînd folosim EventToCommand: de exemplu, putem transmite textul dintr-un TextBox, iar pentru aceasta am expune o proprietate de tipul RelayCommand. EventToCommand poate fi atașat oricărui element de UI, chiar și Paginii, apelând o comandă atunci când pagina s-a încărcat (evenimentul Loaded) pentru a face un request HTTP la un serviciu Web.

Cum comunică ViewModel-ul cu View-ul?

În afara mecanismului de binding ViewModel-ul poate comunica cu View-ul prin mecanisme precum Behavior, Messenger sau prin folosirea unor interfețe pe care View-ul le va implementa, după care va fi injectat în ViewModel.

Extinzând clasa Behavior putem declara proprietăți DependencyProperty împreună cu proprietăți normale pe care le putem lega de proprietăți din ViewModel și care atunci când își schimbă valoarea pot executa cod de UI. Avantajele obiectelor Behavior sunt: reutilizarea cu ușurință, folosirea în Blend și posibilitatea de a muta codul din View.

MVVM Light oferă o clasă Messenger folosită de diferite obiecte pentru a comunica în cadrul aplicației fără ca acestea să știe cu cine comunică, astfel folosind această clasă cuplajul între obiectele care comunică este redus. Mesajele pot conține date simple sau obiecte complexe. Această clasă poate fi des folosită pentru a permite obiectelor ViewModel să comunice între ele sau pentru a permite obiectelor ViewModel să comunice cu obiectele View.

O altă abordare este crearea unor interfețe ce vor fi implementate de View. De exemplu dacă în View trebuie să arătăm un mesaj (un dialog) putem crea o interfață ce expune acele metode după care o implementăm în View și o injectăm în ViewModel (putem obține o referință la ViewModel-ul corespunzător folosind proprietatea DataContext).

Merită menționat că abordarea preferată este extinderea clasei Behavior, deși MVVM nu specifică în mod direct, este de înțeles că atât timp cît codul din Pagini este redus, aplicația este cu atât mai ușor de testat. Un dezavantaj al Behavior -urilor este că acestea nu sunt disponibile pentru Windows 8, făcând astfel portabilitatea aplicațiilor puțin mai dificilă dar sunt librării care oferă clase similare claselor Behavior .

Probleme des întâlnite și soluțiile lor

Una dintre problemele des întâlnite este navigarea: de unde se face navigarea către o altă pagină și cum? Soluția este relativ simplă - creăm o interfață care să expună metodele necesare navigării, o implementăm într-o clasă dedicată apoi o injectăm în obiectele ViewModel din care dorim să realizăm navigarea.

O altă problemă este disponibilitatea datelor la momentul designului - abordarea recomandată este crearea unor interfețe pentru managerii de date (clasele în care avem logică de acces la date, persistență, etc) și implementarea acestora în două clase diferite: una pentru momentul designului, și una ce va fi folosită la momentul rulării aplicației. Folosind proprietatea IsInDesignMode putem înregistra în containerul IoC clasa corespunzătoare ce va fi mai apoi injectată în ViewModel. Această funcționalitate împreună cu Blend oferă posibilitatea formării echipelor de designeri și developeri care să lucreze relativ independent.

Din nou clasele Behavior permit crearea și tranziția între diferite stări ale unei Pagini sau declanșarea unor animații, acest lucru fiind posibil fără a fi nevoiți să scriem cod de UI. Clase Behavior precum DataStateBehavior sau GoToStateAction permit acest lucru.

Din păcate, în Windows Phone 8, nu putem face în mod direct binding între Application Bar și comenzi din ViewModel. Spre deosebire de Windows 8, unde acest lucru este posibil. Astfel în aceste situatii suntem nevoiți să scriem cod de UI, să ascultăm evenimentele butoanelor de pe Application Bar și apoi să exectuăm manual comenzile din ViewModel. Dacă nu dorim să facem aceasta, și de obicei nu dorim, putem folosi librăria AppBarUtils care oferă clase Behavior, Trigger și Action ce permit legarea cu comenzi și diferite proprietăți.

Concluzii

Șablonul MVVM este foarte popular în rândul dezvoltatorilor de aplicații Windows Phone, acesta permite dezvoltarea unor aplicații ușor de menținut, îmbunătățit, testat și de ce nu, cu o interfață grafică ușor de creat (Blend). Componentele definite de șablon au responsbilități bine definite iar cuplajul este redus pe cât de mult posibil. Dacă atunci când șablonul a fost introdus, implementarea acestuia părea destul de greoaie, necesitând multe linii de cod, în perioada recentă librăriile și instrumentele disponibile fac implementarea acestui șablon ușoară și interesantă.

Referințe

MVVM Light www.galasoft.ch/mvvm/

msdn.microsoft.com/en-us/magazine/jj651572.aspx

appbarutils.codeplex.com/

winrtbehaviors.codeplex.com/

vimeo.com/53068482

channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2264

outcoldman.com/en/blog/show/308

blogs.msdn.com/b/tims/archive/2010/11/02/kung-fu-silverlight-architectural-patterns-and-practices-with-mvvm-and-ria-services.aspx