niedziela, 27 listopada 2011

MVVM cz. 2 - Komunikacja ViewModel-View

Bindings - czyli wiązanie ze sobą danych

W MVVM kluczowym pojęciem w komunikacji View-ViewModel są bindingi. Dzięki nim, można w łatwy sposób połączyć jakąś właściwość komponentu graficznego z właściwością ViewModelu. Binding jest normalną klasą, która definiuje sposób wiązania danych. Ograniczeniem bindingów jest to, że za ich pomocą możemy podpinać się jedynie do szczególnego rodzaju właściwości - DependencyProperty (opisane w oddzielnym punkcie niniejszego opracowania). Po drugiej stronie wiązania podpinamy zwykłą właściwość, która daje znać Bindingowi, że się zmieniła poprzez mechanizm notyfikacji (INotifyPropertyChanged). Jeżeli nie zadbamy o to, żeby podnieść event PropertyChanged po stronie ViewModelu to właściwość kontrolki nie zmieni się.

Schemat połączenia:

Target View.DependencyProperty <==> Source ViewModel.Property



Oczywiście zamiast ViewModelProperty istnieje też możliwość podłączenia drugiej DependencyProperty. Wówczas nie musimy się martwić o implementację interfejsu INotifyPropertyChanged. DependencyProperties zawierają alternatywny mechanizmem notyfikacji.
W xamlu jedyną właściwością obiektu klasy Binding, którą musimy zdefiniować, jest Path. Jest to nazwa właściwości źródła (w MVVM źródło to ViewModel czyli obiekt znajdujący sięw DataContexcie:)).
Przykłady Bindingów:
<Component DependencyProperty="{Binding Path=ViewModelProperty}"/>

<Component DependencyProperty="{Binding ViewModelProperty}"/>

<Component>
<Component.DependencyProperty>
<Binding Path="ViewModelProperty"/>
</Component.DependencyProperty>
</Component>

Właściwość Path jest domyślna (i opcjonalna - powyżej dwa pierwsze przykłady), dlatego nie musimy jej podawać. Wszystkie powyższe zapisy są równoważne. Ostatni zapis jest najdłuższy, dlatego w praktyce stosuje się go np z wielowartościowymi konwerterami (opisanymi w oddzielnym punkcie), przy których nie można zastosować skróconego zapisu.
Warto w tym miejscu wspomnieć o bindigu przez ".". Tzn. Jeżeli zapiszemy:
<Component DependencyProperty="{Binding .}"/>

To znaczy, że naszą źródłową property będzie cały obiekt znajdujący się w DataContexcie, czyli w przypadku MVVM będzie to cały ViewModel. Domyślnie powyższy zapis jest równoważny z:
<Component DependencyProperty="{Binding}"/>

Jednak gdy stosujemy zapis:
<Binding Path="."/>

Musimy podać “.” gdyż w przeciwnym razie otrzymamy exception ;P Dobrym zwyczajem jest zawsze wpisywać kropkę i wtedy wiadomo, że wszystko będzie działało:)

W wersji 4 framework'a nie ma możliwości debugowania bindingów [taka możliwość jest już w Silverlighcie 5 więc najprawdopodobniej pojawi się w kolejnej wersji WPF]. Jeżeli coś źle powiązaliśmy informacje o tym wyświetlane są tylko w okienku Output, do którego warto zaglądać. Kolejnym sposobem sprawdzenia jaka wartość przychodzi do DependencyProperty widoku jest stworzenie specjalnego konwertera w którym można postawić break pointa (patrz oddzielny punkt na temat konwerterów).

W praktyce wielokrotnie podpinając bindingi do widoku, okazuje się, że coś nie działa a debug i okienko output nie skutkują. Często w takim wypadku okazuje się, że zapomnieliśmy o ustawieniu właściwego trybu Bindingu (property Mode). Istnieje kilka trybów połączeń:

  • "OneWay" - ViewModel.Property może zmieniać DependencyProperty, ale nie na odwrót

  • "TwoWay" - ViewModel.Property może zmieniać DependencyProperty i odwrotnie

  • "OneWayToSource" - DependencyProperty może zmieniać ViewModel.Property, ale nie na odwrót

  • "OneTime" - DependencyProperty jest jedynie inicjowana za pomocą ViewModel.Property, a następnie właściwości nie synchronizują się

  • "Default" - wybierany jest tryb domyślny


Każda DependencyProperty może mieć domyślnie ustawiony różny tryb bindingu, jednak zazwyczaj jest to OneWay. Domyślny tryb bindingu definiuje się przy definiowaniu DependencyProperty (patrz punkt nt. DependencyProperty).

Przykład:
<Component DependencyProperty="{Binding ViewModelProprety, Mode=TwoWay}"/>

Kolejną właściwością bindingu (często nadużywaną) jest UpdateSourceTrigger. Może ona przyjmować 4 wartości:

  • Default - domyślny sposób odświeżania ViewModel.Property

  • Explicit - rzadko kiedy wykorzystuje się ten sposób odświeżania. Dzięki niemu nasza ViewModelProperty odświeży się tylko gdy z poziomu code behind odpalimy metodę UpdateSource na obiekcie klasy BindingExpression (obiekt BindingExpression możemy wydobyć za pomocą metody GetBindingExpression klasy BindingOperations)

  • PropertyChanged - W momencie kiedy nasza DependencyProperty się zmieni, wówczas odświeżana jest też property po drugiej stronie bindingu. Jest to najczęstszy przypadek.

  • LostFocus - ViewModelProperty odświeża się gdy tracimy focus na kontrolce do której się podpięliśmy.


Domyślnie większość DependencyProperty odświeża binding na PropertyChanged, jednak w przypadku np. TextBoxa, wiązanie do TextProperty odświeży się dopiero jak TextBox straci focus. W tym przypadku czasami warto przestawić tą właściwość z LostFocus na PropertyChanged.
Czasami możemy wykorzystać system asynchronicznego bindingu. Jeżeli we właściwości ViewModelu z nieznanych przyczyn zaszyliśmy jakąś logikę, która wykonuje się dość długo wówczas zawieszamy cały UI i użytkownik może być zdezorientowany. Nasz program zamula. Możemy wtedy ustawić w Bindingu property IsAsync na true. Wówczas logika ta wykona się asynchronicznie w stosunku do wątku UI, a na ekranie pokaże się wartość którą ustawiamy za pomocą FallbackValue property. Np:
<Component Property="{Binding ViewModelProperty, IsAsync=True, FallbackValue={x:Null}}"/>

Nie jest to najlepsza praktyka, raczej powinno się unikać stosowania tego rozwiązania. Property w ViewModelu powinny być "lekkie" i wykonywać się odrazu.

0 komentarze:

Prześlij komentarz

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Online Project management