Artykuł

freeimages.com freeimages.com
gru 07 2014
0

Biblioteki warte poznania w C# - MVVM Light

Dużo w ostatnim czasie piszę o aplikacjach uniwersalnych, a wcześniej również sporo było tekstów o WPF. Co łączy oba tematy? Oprócz XAMLa który pojawia się w obu tych technologiach, pewnym dość istotnym łącznikiem jest wzorzec architektoniczny MVVM. MVVM pozwala na rozbicie logiki naszej aplikacji na pewne określone elementy. Mamy widoki, modele oraz tajemniczy element ViewModel. ViewModel to klasa, która zawiera uporządkowane i wypełnione danymi klasy modelu, pasujące do pewnego określonego widoku. Innymi słowy, jest to pewien łącznik pomiędzy klasycznym modelem i widokiem. W praktyce takie rozwiązanie jest dosyć elastyczne i coraz częściej zaczyna przenikać również do... ASP.NET MVC, jednak nie o tym jest dzisiejszy tekst.

Dziś chciałbym Wam zaprezentować bibliotekę MVVM Light, która działa w różnych środowiskach i daje solidne wsparcie dla tego wzorca. Z tytułowego rozwiązania możemy skorzystać zarówno w WPF, jak też w projektach opartych o Silverlight, aplikacjach uniwersalnych, czy projektach Xamarin. Zaintrygowani? Zainteresowani? Zapraszam do krótkiego opisu;-)

Charakterystyka i zastosowanie

MVVM Light to biblioteka, której głównym zadaniem jest zapewnienie wsparcia dla wzorca MVVM wszędzie tam, gdzie mógłby on pasować. Tak jak wspomniałem we wstępie, z biblioteki możemy skorzystać m.in. w następujących projektach:

  • Aplikacje uniwersalne
  • Windows Phone
  • Windows
  • Xamarin
  • WPF
  • Silverlight

Co daje nam zastosowanie tej biblioteki? Przede wszystkim uzyskujemy dostęp do kilku istotnych elementów:

  • RelayCommand - lokalna implementacja interfejsu ICommand. Możemy w ten sposób tworzyć różne akcje, które później zostaną podpięte do widoku
  • SimpleIoC - prosty kontener IoC, który pozwala nam na automatyczne tworzenie instancji wybranych obiektów (więcej o IoC pisałem przy okazji recenzji biblioteki Ninject)
  • ViewModelBase - bazowa klasa dla naszych ViewModeli

Oczywiście interesujących elementów w tej bibliotece jest znacznie więcej, ale tak naprawdę to właśnie te trzy powyższe stanowią o sile tego rozwiązania.

Kiedy warto zastosować bibliotekę MVVM Light?

Warto ją zastosować zawsze tam gdzie ona pasuje;-) MVVM Light jest na tyle lekkim rozwiązaniem, że zadziała równie dobrze na telefonie jak i na komputerze. Oczywiście jeśli w Waszym rozwiązaniu będziecie mieli jeden-dwa ViewModele, to warto sobie zadać pytanie, czy użycie zewnętrznej biblioteki ma sens. Nie mniej jednak w większości przypadków spełni ona swoje zadanie.

Przykład praktyczny

Teoria teorią, ale czym byłby opis biblioteki bez jakiegoś sensownego przykładu praktycznego? W dzisiejszym wpisie przygotowałem dla Was przykład aplikacji uniwersalnej, która wykorzystuje prosty ViewModel do wczytywania listy stringów na stronie. Cały kod projektu znajdziecie w dziale download.

Dla uproszczenia tematu, będziemy rozpatrywać kod z perspektywy aplikacji Windows Phone (wersja dla Windows ma minimalne różnice w widoku).

W przypadku aplikacji uniwersalnej, powinniśmy dodać wersję portable biblioteki MVVM Light. Oczywiście w projekcie, który przygotowałem dla Was, biblioteki są już na miejscu;-)

Z perspektywy dzisiejszej biblioteki, najważniejsze dla nas są cztery poniższe pliki:

  • ViewModels/MainViewModel.cs - czyli nasz przykładowy ViewModel
  • ViewModels/ViewModelLocator.cs - klasa pomocnicza wykorzystywana przez MVVM do odnajdywania naszych ViewModeli
  • App.xaml - punkt wejściowy aplikacji
  • MainPage.xaml - widok

W kolejnych akapitach opiszę szerzej, co się dzieje w każdym z tym plików;-)

MainViewModel.cs

MainViewModel to niezwykle prosta klasa typu ViewModel. W tym przypadku dziedziczymy z klasy ViewModelBase, która jest integralnym elementem MVVM Light. Kluczowym elementem jest tutaj kolekcja stringów oraz dwie komendy odpowiedzialne za wypełnianie oraz czyszczenie tej kolekcji:

public class MainViewModel : ViewModelBase
{
    private ObservableCollection<string> items = null;

    public MainViewModel()
    {
        this.LoadDataCommand = new RelayCommand(LoadData);
        this.ClearDataCommand = new RelayCommand(ClearData);
        this.items = new ObservableCollection<string>();
    }

    public ObservableCollection<string> Items 
    { 
        get
        {
            return items;
        }
        private set
        {
            Set(() => Items, ref items, value);
        }
    }

    public ICommand LoadDataCommand
    { 
        get; 
        private set; 
    }

    public ICommand ClearDataCommand
    {
        get;
        private set;
    }
        
    private void LoadData()
    {
        for(int i = 1; i <= 10; ++i)
        {
            this.items.Add("Element " + i);
        };
    }

    private void ClearData()
    {
        this.items.Clear();
    }
}

Oprócz tego, warto również zwrócić uwagę na:

  • Użycie RelayCommand
  • Zastosowanie metody Set (również zdefiniowana w ViewModelBase) w setterze kolekcji

Generalnie rzec biorąc, mamy tutaj do czynienia z bardzo prostą konstrukcją.

ViewModelLocator.cs

ViewModelLocator to prosta, pomocnicza klasa wykorzystywana do odnajdywania stworzonych wcześniej ViewModeli. Standardowo wygląda ona mniej więcej tak:

public class ViewModelLocator
{
    public MainViewModel Main
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MainViewModel>();
        }
    }

    static ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        SimpleIoc.Default.Register<MainViewModel>();
    }
}

Kluczowy w tym przypadku jest konstruktor, w którym to konfigurujemy statyczną klasę ServiceLocator, a następnie rejestrujemy w kontenerze IoC wszystkie wcześniej stworzone ViewModele. Jeśli chcemy ustawiać DataContext z poziomu XAMLa, to w takiej sytuacji musimy również tworzyć propertisy zwracające konkretne ViewModele.

App.xaml

W App.xaml dodajemy do zasobów referencję do wcześniej utworzonej klasy ViewModelLocator. Dzięki temu będziemy mogli z niej korzystać na poziomie kodu XAML:

<Application
    x:Class="MVVMLightDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MVVMLightDemo"
    xmlns:vm="using:MVVMLightDemo.ViewModels">
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" />
    </Application.Resources>
</Application>

MainPage.xaml

Ostatnim elementem jest strona główna aplikacji. W tym przypadku interesuje nas tylko kod XAML. Spójrzmy na przykładową implementację dla systemu Windows Phone:

<Page
    x:Class="MVVMLightDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MVVMLightDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Title Panel -->
        <StackPanel Grid.Row="0" Margin="19,0,0,0">
            <TextBlock Text="MVVM LIGHT DEMO" Margin="0,12,0,0"
                       Style="{ThemeResource TitleTextBlockStyle}" />
            <TextBlock Text="test" Margin="0,-6.5,0,26.5" 
                       Style="{ThemeResource HeaderTextBlockStyle}" 
                       CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
        </StackPanel>

        <Grid Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Button Content="Wczytaj dane" Command="{Binding LoadDataCommand}" 
                    HorizontalAlignment="Stretch" />
            <Button Content="Wyczyść dane" Command="{Binding ClearDataCommand}" 
                    HorizontalAlignment="Stretch" Grid.Row="1" />
            
            <ListView ItemsSource="{Binding Items}" Grid.Row="2">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Margin="0,0,0,27.5">
                            <TextBlock Text="{Binding}" 
                                       Style="{ThemeResource ListViewItemTextBlockStyle}" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Grid>
</Page>

Kod jest odrobinę długi, ale nas tak naprawdę interesuje kilka elementów.

Przede wszystkim DataContext. Ustawiamy go w linii 10 (zasięgiem obejmuje cała stronę) przypinając do niego instancję obiektu uzyskaną z właściwości Main w klasie ViewModelLocator.

W kolejnym kroku w liniach 34 i 36 przypinamy komendy stworzone w ViewModelu. Następnie w linii 39 podpinamy naszą kolekcję, a w linii 43 podpinamy bindowanie do TextBlocka (kolekcja operuje na stringach, dlatego też nie musimy podawać żadnej właściwości).

W gruncie rzeczy jest to prosty i czytelny kod, który został logicznie podzielony funkcjonalnie.

Podsumowanie

MVVM Light używam od niedawna i teraz już nie wyobrażam sobie tworzenia aplikacji wykorzystujących MVVM bez niej. Biblioteka daje sporo możliwości, a na dodatek można ją w pewnym sensie użyć również w innych celach (np. wykorzystać kontener IoC nie tylko do tworzenia instancji ViewModeli). Jedyne do czego mógłbym się przyczepić, to konieczność ręcznego rejestrowania wszystkich ViewModeli. Chyba będę musiał sięgnąć po refleksję i napisać jakiś sprytny kod, który to będzie robił za mnie;-)

P.S. Jeśli MVVM Light nie do końca Wam przypadł do gustu, polecam sprawdzić konkurencyjne rozwiązanie - Caliburn.Micro.

Metryka

Data ostatniej modyfikacji: 14.10.2015, 20:14.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus