BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











WP7 dla programistów. Część 3 - Data binding i MVVM

18-02-2011 22:45 | Maciej Grabek
Zapraszam do lektury trzeciego artykułu poświęconego Windows Phone 7. Dziś przyjrzymy się zagadnieniu bindowania danych do kontrolek oraz wzorcowi Model - View - View Model.

Wstęp

Zapraszam do lektury trzeciego artykułu poświęconego Windows Phone 7. W poprzedniej części zapoznaliśmy się z kontrolkami i stylami jakie są dostępne w Windows Phone 7. Dziś przyjrzymy się zagadnieniu bindowania danych oraz wzorcowi Model - View - View Model. Artykuł ten ściśle nawiązuje do równoległego cyklu webcastów dostępnych na Channel 9 poświęconych programowaniu na platformie Windows Phone 7.

Odcinek 3

Data Binding w Windows Phone 7

Z racji na fakt, że do tworzenia aplikacji na Windows Phone 7 korzystamy z Silverlight, mamy do dyspozycji mechanizm pozwalający nam bindować dane do kontrolek. Tłumaczenie tego wyrażenia na język polski sprowadza się do określenia "wiązania danych", jednakże z przyzwyczajenia będę czasem używał spolszczonej wersji angielskiego pierwowzoru. Dla osób, które wcześniej miały do czynienia z tym mechanizmem przy okazji pisania aplikacji w Silverlight lub WPF będzie to przypomnienie, natomiast dla tych którzy się z nim nie spotkali będzie to wprowadzenie to tego zagadnienia. Niemniej jednak każdy może znaleźć tu coś ciekawego.

Rodzaje wiązań

Mamy do dyspozycji trzy rodzaje bindowania:

  • OneWay - wartość jednego elementu wpływa na drugi, ale zmiana w drugim nie ma wpływu na wartości w pierwszym. Jest to wartość domyślna.
  • TwoWay - zmiana wartości w którymkolwiek z obiektów wpływa na ten drugi
  • OneTime - bindowanie dokonuje się tylko raz

Aby zdefiniować wiązanie posługujemy się konstrukcją widoczną na listingu 1.

 

Listing 1. Tworzenie wiązania

SomeProperty="{Binding Path=SomeOtherProperty ElementName=someControl, Source=someClass, Mode=OneOfModes}

Widzimy tu przede wszystkim słowo kluczowe Binding, a dalej odpowiednie właściwości pozwalające nam uzyskać dostęp do interesujących nas wartości. Trzeba tu zauważyć, że ElementName i Source są stosowane zamiennie. Pierwszy odnosi się do pobierania wartości z innej kontrolki, natomiast drugi korzysta z obiektów znajdujących się w projekcie (na przykład właściwości strony, providerów itp).

Przyjrzyjmy się jak w praktyce możemy wykorzystać ten mechanizm. W pierwszym z przykładów wykorzystam dwa slidery do manipulacji wielkością prostokąta wyświetlanego na ekranie. Kod odpowiedzialny za wyświetlanie sliderów jest dość oczywisty, dlatego skupię się na prostokącie, którego definicja widoczna jest na listingu 2.

Listing 2. Prostokąt z właściwościami podpiętymi pod slidery

<Rectangle Height="{Binding ElementName=sliderY, Path=Value}" 
Width="{Binding ElementName=sliderX, Path=Value}"
HorizontalAlignment="Left" Margin="106,243,0,0" Name="rectangle1"
Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Blue" />

Interesująca dla nas jest definicja wysokości i szerokości tego prostokąta. Widzimy, że będą one czerpać swoją wartość ze sliderów, a konkretniej z właściwości Value. Po uruchomieniu projektu na emulatorze widać, ze zmiana wartości sliderów powoduje odpowiednie zmiany wielkości prostokąta. Efekt prezentuje rysunek 1.

Rysunek 1. Binding jednostronny w działaniu

Ten typ bindingu zapewnia nam zmiany wartości tylko w jedną stronę. Aby pozwolić na uaktualnianie właściwości w obie strony należy posłużyć się drugim z rodzajów bindingu, czyli TwoWay. Podobnie jak w poprzednim przykładzie posłużymy się dwoma sliderami. Tym razem naszym celem jest wzajemne uaktualnianie wartości na podstawie pola ustawionego w drugim sliderze. Niezbędny kod widać na listingu 3.

Listing 3. Binding dwustronny

<Slider Height="84" HorizontalAlignment="Left" Margin="12,37,0,0" Name="slider1" VerticalAlignment="Top" Width="438" 
Value="50" Minimum="10" Maximum="100" SmallChange="1" />
<Slider Height="84" HorizontalAlignment="Left" Margin="12,127,0,0" Name="slider2" VerticalAlignment="Top" Width="438"
Minimum="10" Maximum="100" SmallChange="1"
Value="{Binding ElementName=slider1, Path=Value, Mode=TwoWay}"/>

Sama definicja wiązania jest umieszczona w jednym sliderze, jednakże w trakcie działania aplikacji jest ona również przenoszona na drugi slider. Wynik działania aplikacji widoczny jest na rysunku 2.

Rysunek 2. Binding dwustronny w działaniu

Ostatni tryb, czyli OneTime działa analogicznie jak OneWay, jednakże powoduje jednokrotne wczytanie wartości ze źródła. Sposób definicji jest również analogiczny, zatem dla chętnych pozostawiam to jako zadanie do samodzielnego wykonania.

Konwertery wartości

Z racji na fakt, że w kodzie XAML nie ma możliwości umieszczania logiki, która mogłaby zająć się translacją wartości jendej kontrolki na odpowiednie właściwości innej kontrolki. W tym celu istnieje możliwość zdefiniowania we wiązaniu konwertera, odpowiedzialnego za takie właśnie tłumaczenie. Konwerter to nic innego jak klasa, która implementuje interfejs IValueConverter. Jako przykład użyjemy konwertera, który będzie odpowiedzialny za zmianę wartości typu bool na wartości typu Visibility. W tym celu do nowej strony w aplikacji dodamy kontrolkę toggle switch oraz textblock. Dla przypomnienia pierwsza z nich pochodzi z toolkita. Definicja samego konwentera widoczna jest natomiast na listingu 4.

Listing 4. Definicja konwertera

public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (Visibility)value == Visibility.Visible;
}
}

Tak przygotowany konwerter jest gotowy do użycia. Sposób jego wykorzystania widoczny jest na listingu 5. Należy zwrócić uwagę na fragment odpowiedzialny za dodanie do strony odpowiedniej przestrzeni nazw w deklaracji PhoneApplicationPage oraz jego definicji w resources, dzięki czemu będzie można z niego wygodniej i optymalniej korzystać.

Listing 5. Użycie konwertera

<phone:PhoneApplicationPage 
...
xmlns:myConverters="clr-namespace:WP7_DataBinding.Converters"
...
/>

<phone:PhoneApplicationPage.Resources>
<myConverters:BooleanToVisibilityConverter x:Key="btvConv"/>
</phone:PhoneApplicationPage.Resources>

<toolkit:ToggleSwitch Name="toggleSwitch1" ... />
<TextBlock Name="textBlock1" ...
Visibility="{Binding IsChecked, ElementName=toggleSwitch1, Converter={StaticResource btvConv}}"/>

Działanie uruchomionej aplikacji widzimy na rysunku 3.

Rysunek 3. Konwerter w akcji

MVVM - Model - View - View Model

Konsekwencją istnienia bindingów w WPF i Silverlight jest powstanie wzorca MVVM czyli Model - View - View Model. Polega on na utworzeniu pewnego bytu pośredniego pomiędzy widokiem i modelem. Zapewne wszystkim znany jest wzorzec MVC czyli Model - View - Controler. W MVVM zamiast kontrolera mamy definicję modelu widoku, która w określony sposób przekształca dane z modelu na formę rozumianą przez widok. Jednocześnie istnieje możliwość manipulacji przy pomocy widoku modelem, jednakże odbywa się to właśnie za pośrednictwem View Model. Dla zilustrowania działania tego przykładu stworzymy klasę view modelu, która będzie udostępniała nam pewne dane. Jest to jeszcze miejsce, w którym mamy możliwość przetworzenia naszych danych do formy zrozumiałej przez widok. Jak wcześniej wspomniałem w kodzie XAML nie ma możliwości stosowania logiki, dlatego wszystkie operacje wykonujemy właśnie w klasie view model.
Nierozerwalną częścią MVVM jest mechanimz nofyfikacji zmian. Korzystamy z niego implementując w naszej klasie interfejs INotifyPropertyChanged. Jeżeli klasa ViewModel implementuje ten właśnie interfejs, wówczas jeżeli skorzystamy z bindingu w momencie zmiany wartości w tej klasie odświeżony zostanie również widok.
Przygotujmy zatem odpowiednie klasy, jedna reprezentująca dane i druga, która będzie reprezentować view model. Odpowiedni kod widoczny jest na listingu 6.

Listing 6. Klasa modelu i view model

public class DataItem
{
public string Name { get; set; }
public int Weight { get; set; }
}


public class DemoViewModel : INotifyPropertyChanged
{
//simulate data source
private DataItem _data;

public DemoViewModel()
{
this._data = new DataItem() { Name = "some box", Weight = 50 };
}

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
{
PropertyChanged(this, args);
}
}

public string ItemName
{
get { return this._data.Name; }
set
{
if (this._data.Name != value)
{
this._data.Name = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemName"));
}
}
}

public int ItemWeight
{
get { return this._data.Weight; }
set
{
if (this._data.Weight != value)
{
this._data.Weight = value;
OnPropertyChanged(new PropertyChangedEventArgs("ItemWeight"));
}
}
}
}

Tak przygotowane klasy możemy podpiąć do widoku. Podobnie jak w przypadku konwerterów na początku dodajemy odpowiednią przestrzeń nazw, a następnie korzystamy z resources. Dla zasymulowania zmian wartości danych na stronie dodamy dwie sekcje - jedną odpowiedzialną za edycję danych i drugą za ich podgląd. W momencie gdy zmodyfikujemy wartości w pierwszej sekcji nastąpi sekwencja widoczna na rysunku 4.

Rysunek 4. Działanie MVVM

Aby osiągnąć taki efekt musimy skorzystać z kodu widocznego na listingu 7.

Listing 7. Klasa modelu i view model

<phone:PhoneApplicationPage 
...
/>
<phone:PhoneApplicationPage.Resources>
<myViewData:DemoViewModel x:Key="viewModel"/>
</phone:PhoneApplicationPage.Resources>

<TextBox Text="{Binding Source={StaticResource viewModel}, Path=ItemName, Mode=TwoWay}" ... />
<Slider Minimum="0" Maximum="100" Value="{Binding Source={StaticResource viewModel}, Path=ItemWeight, Mode=TwoWay}" ... />

<TextBlock Text="{Binding Source={StaticResource viewModel}, Path=ItemName}" ... />
<TextBlock Text="{Binding Source={StaticResource viewModel}, Path=ItemWeight}" ... />

Jak widać nie ma tu potrzeby odwoływania się do innych kontrolek, gdyż elementem łączącym jest view model. Implikuje to zatem brak konieczności nadawania im nazw. W momencie gdy w ten sposób zainicjujemy nasze elementy, wówczas w edytorze skorzysta on z danych zainicjowanych w konstruktorze DemoViewModel. Widać to na rysunku 5.

Rysunek 5. Edytor wizualny a MVVM

Po uruchomieniu naszej aplikacji zgodnie z oczekiwaniami zmiana w sekcji edycji powoduje zmiany w sekcji podglądu. Widać to na rysunku 6, jednakże dużo lepiej będzie można to zaobserwować uruchamiając kod źródłowy, który udostępnię na moim blogu.

Rysunek 6. Działająca aplikacja oparta o MVVM

Podsumowanie

Tak oto dotarliśmy do do końca dziesiejszego zagadnienia. Przypominam, że równolegle z tym cyklem artykułów na Channel 9 jest również publikowana seria webcastów poświęcona Windows Phone 7. Tym samym zapraszam do obejrzenia trzeciego odcinka. Gorąco zachęcam do śledzenia obu serii oraz w razie pytań umieszczania ich pod artykułami lub bezpośrednio pod nagraniami na CH9.

Maciej Grabek
Maciej Grabek
Pasjonat technologii oferowanych przez Microsoft , współzałożyciel i lider Toruńskiej Grupy Deweloperów .NET. Na co dzień programista .NET w jednej z największych firm e-commerce w Polsce.

Komentarze 1

Maciej Grabek Redaktor
Maciej Grabek
12328 pkt.
Guru
MVP
21-02-2011
oceń pozytywnie 2

Materiały można znaleźć na moim blogu w kategorii Windows Phone 7

Maciek

Jeżeli odpowiedź okazała się pomocna kliknij [Pomógł mi]

BLOG

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.