Artykuł

freeimages.com freeimages.com
paź 15 2015
0

MVVM Light - wykorzystanie messengera w komunikacji

Czasem tworząc aplikację wykorzystującą XAML, chcielibyśmy przekazywać dane pomiędzy poszczególnymi stronami. Niestety technologie Microsoftu nie są do tego dobrze przygotowane. Co prawda możemy zawsze wykorzystać jakiś globalny obiekt kontekstu, ale w praktyce jest to rozwiązanie nieefektywne. Możemy również wykorzystać foldery lokalne/roaming, który opisywałem tutaj, ale w pewnym sensie strzelamy tutaj do muchy z armaty. Czy można to zrobić jakoś prosto i elegancko? Okazuje się, że tak - choć po części zależy to od używanych przez Was bibliotek.

Jakiś czas temu opisywałem na łamach tego blogu MVVM Light, która upraszcza wdrożenie modelu MVVM w wielu technologiach Microsoftu, a oprócz tego skrywa w sobie kilka fajnych funkcji. Dziś chciałbym opisać jedną z nich - Messengera, który może być swego rodzaju mostkiem informacyjnymi pomiędzy ViewModelami umieszczonymi w naszej aplikacji.

Słowem wstępu

Messenger to moduł MVVM Light, który pozwala na komunikację pomiędzy poszczególnymi instancjami ViewModeli (a tym samym stron) umieszczonych w aplikacji. Aby korzystać z tego mechanizmu musimy zrobić trzy rzeczy:

  • Zaimplementować klasę komunikatu, które będzie zawierać właściwości pozwalające na przekazanie wszystkich danych pomiędzy dwoma ViewModelami
  • Zaimplementować odbieranie wiadomości w ViewModelu, który ma zbierać dane za pomocą metody Register
  • Zaimplementować wysyłanie wiadomości w ViewModelu, który te dane przygotowuje za pomocą metody Send

W praktyce jest to więc bardzo trywialne zadanie i sprowadza się do obsłużenia określonych metod we wskazanych ViewModelach.

Przykład - intro

W oparciu częściowo o źródła wcześniejszego projektu, przygotowałem nowe rozwiązanie na potrzeby całego wpisu. Pominę standardową implementację NavigationService oraz kilka innych rzeczy, które nie są do końca związane z tematem tego tekstu i skupię się na konkretach. Gotowy projekt aplikacji na Windows Phone (UA) znajdziecie tutaj.

Z założenia projekt składać będzie się z dwóch stron, a tym samym z dwóch ViewModeli. Na pierwszej ze stron, znajdziecie prosty przycisk oraz TextBlock. TextBlock wyświetlać będzie wybrany przez Was numerek na drugiej stronie, do której dotrzecie po tapnięciu na przycisk;-)

Na drugiej stronie znajdziecie pięć przycisków, a każdy z nich reprezentować będzie inny numerek. Wszystkie przyciski korzystać będą z tej samej komendy. Różnica polegać będzie na przekazaniu odpowiedniej wartości w atrybucie CommandParameter. Oczywiście całość można by zrealizować w oparciu o ListView, ale w tym przypadku byłoby to zbędne zaciemnianie kodu.

Po tapnięciu w określony przycisk, akcja przypisana do komendy wyśle określony numerek za pomocą komunikatu do pierwszego ViewModelu, a następnie za pomocą NavigationService wykonamy krok wstecz:)

Klasa komunikatu

Messenger może obsługiwać dowolny typ obiektu, może nawet przyjmować typy proste. Pewnie spytacie dlaczego nie zarejestrować po prostu inta? Można jak najbardziej, ale jeśli planujemy wykorzystanie Messengera na szerszą skalę, to szybko przekonamy się, że takie rozwiązanie jest drętwe, bo jeśli później będziemy chcieli gdzieś indziej odwoływać się również do inta, to możemy przypadkiem odebrać wiadomość nie tam gdzie tego oczekiwaliśmy. Dlatego też dużo lepiej tworzyć osobną klasę wiadomości dla każdego rodzaju treści. Poniżej prosta implementacja:

public class MvvmMessage
{
    public int SelectedNumber { get; set; }
}

ViewModele

W całym procesie niezbędne oczywiście będą również ViewModele. Zasadniczo ich funkcjonalność opisałem już w poprzednich akapitach, przy okazji informacji o tym co będą robić poszczególne strony, poniżej prezentuje zatem należy kod źródłowy:

public class MainViewModel : ViewModelBase
{
    private INavigationService navigationService = null;

    public MainViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
        this.NavigateToNumbersCommand = new RelayCommand(this.NavigateToNumbersAction);
        Messenger.Default.Register<MvvmMessage>(this, this.HandleMessage);
    }

    public int Number
    {
        get; set;
    }

    public ICommand NavigateToNumbersCommand
    { 
        get; 
        private set; 
    }

    private void NavigateToNumbersAction()
    {
        this.navigationService.NavigateTo("SecondPage");
    }

    private void HandleMessage(MvvmMessage message)
    {
        this.Number = message.SelectedNumber;
    }
}

Najważniejsze z perspektywy przykładu, dzieje się w konstruktorze. To właśnie w tym miejscu rejestrujemy nasz komunikat oraz metodę odpowiedzialną za jego obsługę. Sama metoda jest niezwykle prosta i najzwyczajniej w świecie przypisuje otrzymaną wartość do lokalnej właściwości.

Wspomniana wartość przychodzi z drugiego ViewModelu, którego kod znajduje się poniżej:

public class SecondViewModel : ViewModelBase
{
    private INavigationService navigationService = null;

    public SecondViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
        this.SelectNumberCommand = new RelayCommand<object>(this.SelectNumberAction);
    }

    public ICommand SelectNumberCommand
    {
        get;
        private set;
    }

    private void SelectNumberAction(object selectedNumber)
    {
        Messenger.Default.Send<MvvmMessage>(new MvvmMessage
        {
            SelectedNumber = int.Parse(selectedNumber.ToString())
        });
        this.navigationService.GoBack();
    }
}

W tym przypadku esencją jest realizacja zadania przypisanego do komendy i to właśnie w tym miejscu nadajemy komunikat do pierwszego ViewModelu, a następnie za pomocą serwisu nawigacji, wracamy do poprzedniej strony. Metody Send oraz Register z Messengera, używają generycznego parametru do określenia rodzaju komunikatu.

Strony

Zwieńczeniem przykładu będzie kod XAML obu stron. Tak naprawdę nie dzieje się tu zbyt wiele ciekawego - ot zwykłe podpięcie ViewModeli oraz właściwości i komend w nich zawartych. Myślę, że kod nie wymaga większego omówienia. Poniżej kod strony pierwszej:

<Page mc:Ignorable="d"
    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"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Margin="19,0,0,0">
            <TextBlock Text="MVVM LIGHT DEMO" Style="{ThemeResource TitleTextBlockStyle}" Margin="0,12,0,0"/>
            <TextBlock Text="pierwsza" Margin="0,-6.5,0,26.5" Style="{ThemeResource HeaderTextBlockStyle}" 
                       CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
        </StackPanel>

        <StackPanel Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
            <Button Content="wybierz liczbę" Command="{Binding NavigateToNumbersCommand}" 
                    HorizontalAlignment="Stretch" Margin="0,0,0,19" />
            <TextBlock FontSize="{StaticResource TextStyleMediumFontSize}">
                <Run Text="Twoja liczba to: " />
                <Run Text="{Binding Number}" />
            </TextBlock>
        </StackPanel>
    </Grid>
</Page>

A tutaj kod strony drugiej:

<Page mc:Ignorable="d"
    x:Class="MVVMLightDemo.SecondPage"
    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"
    DataContext="{Binding Source={StaticResource Locator}, Path=Second}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Margin="19,0,0,0">
            <TextBlock Text="MVVM LIGHT DEMO" Style="{ThemeResource TitleTextBlockStyle}" Margin="0,12,0,0"/>
            <TextBlock Text="druga" Margin="0,-6.5,0,26.5" Style="{ThemeResource HeaderTextBlockStyle}" 
                       CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
        </StackPanel>

        <StackPanel Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
            <StackPanel.Resources>
                <Style TargetType="Button">
                    <Setter Property="HorizontalAlignment" Value="Stretch" />
                    <Setter Property="Margin" Value="0,0,0,19" />
                </Style>
            </StackPanel.Resources>
            <Button Content="1" Command="{Binding SelectNumberCommand}" CommandParameter="1" />
            <Button Content="2" Command="{Binding SelectNumberCommand}" CommandParameter="2" />
            <Button Content="3" Command="{Binding SelectNumberCommand}" CommandParameter="3" />
            <Button Content="4" Command="{Binding SelectNumberCommand}" CommandParameter="4" />
            <Button Content="5" Command="{Binding SelectNumberCommand}" CommandParameter="5" />
        </StackPanel>
    </Grid>
</Page>

Oczywiście w samej solucji jest więcej kodu, ale nie są to rzeczy, które wykraczają poza niepisany standard, dlatego też za przyzwoleniem - przemilczę ich omówienie (cały projekt Windows Phone, możecie pobrać stąd).

Sam Messenger może być bardzo przydatny i daje spore możliwości - warto dać mu szansę jak również samej bibliotece MVVM Light. Szerzej na jej temat pisałem już wcześniej.

Przydatne linki

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

Send to Kindle

Komentarze

blog comments powered by Disqus