MVVM – #3 – MVVM Toolkit #2

W poprzednim artykule rozpoczęliśmy pisanie aplikacji do wyliczania kosztu zamówienia w restauracji. Dzisiaj kontynuujemy prace. Udało nam się już przebrnąć przez kilka kluczowych elementów tworzenia aplikacji przy użyciu WPF oraz wzorca MVVM przy wykorzystaniu MVVM Toolkit. W tej części stworzymy okno z podsumowaniem zamówienia. Wykorzystamy do tego mechanizm do komunikacji pomiędzy modelami widoków oferowany przez MVVM Toolkit.

Do dzieła!

 

Widok

Ponownie jak wcześniej zaczniemy od zdefiniowania widoku. Zacznijmy od utworzenia nowego okna:



 

Nazwijmy go zwyczajnie, MessageBox. Po utworzeniu pliku, dodaj następującą definicję widoku:

<Window x:Class="Restauracja.MessageBox"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Podsumowanie zamówienia" Height="200" Width="300">
    <Grid VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="25" />
        </Grid.RowDefinitions>
        
        <Label HorizontalAlignment="Center" FontWeight="Bold">TWOJE ZAMÓWIENIE</Label>
        
        <Separator Grid.Row="1" />
        
        <ItemsControl Grid.Row="2" >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <TextBlock />
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <Separator Grid.Row="3" />
        
        <StackPanel Grid.Row="4" Orientation="Horizontal">
            <Label Width="40">Koszt</Label>
            <Label />
        </StackPanel>
    </Grid>
</Window>


 

Wiązania danych dodamy po utworzeniu modelu widoku. Zatem zbytnio nie zwlekając – dodajmy go!

 

 

Model widoku

Utwórzmy nową klasę o nazwie MessageBoxViewModel, a następnie zaktualizujmy nasz kontener IoC o potrzebny kod:

public class ViewModelLocator
{
    public ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

        SimpleIoc.Default.Register<MainViewModel>();
        SimpleIoc.Default.Register<MessageBoxViewModel>();
    }

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

    public MessageBoxViewModel MessageBox
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MessageBoxViewModel>();
        }
    }

    public static void Cleanup()
    {

    }
}

 

Przejdźmy do nowo utworzonej klasy. Będziemy potrzebowali listy do przechowywania pozycji zamówienia oraz pola z jego kosztem. Dodajmy je zatem!

public class MessageBoxViewModel : ViewModelBase
{
    public ObservableCollection<DishModel> Positions { get; } = new ObservableCollection<DishModel>();

    private double _cost;
    private const string CostPropertyName = "Cost";
    public double Cost
    {
        get { return _cost; }
        set
        {
            Set(CostPropertyName, ref _cost, value);
        }
    }

    public MessageBoxViewModel()
    {

    }
}

 

Dobrym pomysłem będzie dodanie teraz wiązań w widoku, dzięki czemu widok będzie gotowy już na wyświetlenie danych.

<Window x:Class="Restauracja.MessageBox"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Podsumowanie zamówienia" Height="200" Width="300"
        DataContext="{Binding Source={StaticResource Locator}, Path=MessageBox}">
    <Grid VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="25" />
        </Grid.RowDefinitions>
        
        <Label HorizontalAlignment="Center" FontWeight="Bold">TWOJE ZAMÓWIENIE</Label>
        
        <Separator Grid.Row="1" />
        
        <ItemsControl Grid.Row="2" ItemsSource="{Binding Positions}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding}" />
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <Separator Grid.Row="3" />
        
        <StackPanel Grid.Row="4" Orientation="Horizontal">
            <Label Width="40">Koszt</Label>
            <Label Content="{Binding Cost}" />
        </StackPanel>
    </Grid>
</Window>

 

 

Tworzenie nowego okna

Przedstawię teraz mechanizm udostępniany przez MVVM Light do komunikacji pomiędzy modelami widoków. Jest nim klasa Messenger. Interesują nas dwie metody jedna do wysyłania komunikatu i druga do jego odbierania. Istnieje kilka przeciążonych wersji tych metod. Nas będzie interesowała tylko jedna z nich:

Wysyłanie:

Messenger.Default.Send<T>(T object, object token)

Rejestracja odbioru komunikatu:

Messenger.Default.Register<T>(object recipient, object token, Action<t> action);

Mechanizm jest bardzo elastyczny. Wystarczy, że przygotujemy obiekt do wysłania, zarejestrujemy go w metodzie Send oraz dodamy miejsce gdzie zostanie on przechwycony. Tokeny mogą być przydatne gdy chcemy, aby pewne komunikaty trafiły do konkretnego adresata, który takiego tokenu oczekuje.

Istnieje wiele szkół tworzenia nowych okien w ramach korzystania z MVVM. Jednak ja przedstawię swój sposób na utworzenie takiego okna i wypełnienia go danymi. Wykorzystam do tego mechanizm wiadomości. Nie jestem w stanie jeszcze określić na ile ta metoda jest optymalna i zgodna ze wzorcem aczkolwiek chciałbym ją przedstawić.

Jeżeli będzie okazja jej wykorzystania w ramach projektu z DSP, postaram się sprawdzić na ile jest przydatna. Jeśli uznam, że jest odpowiednia, przedstawię jej lepszą implementację.

W takim razie jak możemy użyć tego mechanizmu do utworzenia nowego okna? Proponuję dodać polecenie w modelu widoku głównego okna, skąd wyślemy wiadomość z obiektem zawierającym listę pozycji oraz koszt zamówienia. Dodamy token mówiący o tym, że chcemy utworzyć najpierw okno. Zanim dodamy polecenie,  potrzebujemy obiektu opakowującego nasze dane do wysłania. Dodajmy zatem nową klasę o nazwie OrderDetailsMessage:

public class OrderDetailsMessage
{
    public List<DishModel> Positions = new List<DishModel>();
    public double Cost = 0;
}

 

Dla wygodniejszej obsługi tokenów utwórzmy wyliczenie:

public enum MessageType
{
    OrderDetailsCreateWindow = 0,
    OrderDetailsContent = 1
}

 

Następnie dodajmy nowe polecenie w  modelu widoku okna głównego:

private RelayCommand _showDetailsCommand;
public RelayCommand ShowDetailsCommand
{
    get
    {
        return _showDetailsCommand
               ?? (_showDetailsCommand = new RelayCommand(
                   () =>
                   {
                       Messenger.Default.Send<OrderDetailsMessage>(new OrderDetailsMessage
                       {
                           Positions = SelectedDishes.ToList(),
                           Cost = Cost
                       }, MessageType.OrderDetailsCreateWindow);

                   }));
    }
}

 

Wysyłamy wiadomość z obiektem zawierającym potrzebne nam dane. Zależy nam najpierw na utworzeniu okna(dlatego używamy tokenów). Jako miejsce odebrania komunikatu, wybrałem code – behind widoku głównego. Nie chcę zanieczyszczać modelu widoku – myślę, że code-behind będzie odpowiednim miejscem na umieszczenie logiki tworzenia okna szczegółów. Umieść poniższy kod w code-behind widoku głównego:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Messenger.Default.Register<OrderDetailsMessage>(this, MessageType.OrderDetailsCreateWindow, ShowDetailsWindow);
    }

    private void ShowDetailsWindow(OrderDetailsMessage content)
    {
        var view = new MessageBox();
        Messenger.Default.Send(content, MessageType.OrderDetailsContent);
        view.ShowDialog();
    }
}

 

Po odebraniu komunikatu, tworzymy nowe okno. Po jego utworzeniu wysyłamy kolejny komunikat, tym razem z innym tokenem informującym o tym, że chcemy wypełnić danymi model widoku(w nim odbieramy komunikat) utworzonego okna.

Poniższy kod umieść w modelu widoku okna szczegółów zamówienia:

public MessageBoxViewModel()
{
    Messenger.Default.Register<OrderDetailsMessage>(this, MessageType.OrderDetailsContent, ProcessMessage);
}

private void ProcessMessage(OrderDetailsMessage message)
{
    Positions.Clear();
    message.Positions.ForEach((item) =>
    {
        Positions.Add(item);
    });

    Cost = message.Cost;
}

 

Jest to bardzo prosta metoda, jedyne co robimy to aktualizujemy dane. Teraz po kliknięciu przycisku podsumowania, zostanie wyświetlone nowe okno podsumowania zamówienia!

 

Działa!

Udało nam się ukończyć aplikację. Przykład był bardzo prosty, przedstawił podstawowe mechanizmy rządzące MVVM Toolkit. Zachęcam do eksperymentowania z  tym narzędziem, a także do dyskusji na temat samego projektu. Link do projektu na GitHub znajduje się poniżej.

 

Mam coś dla Ciebie

Zapisz się do mojego newslettera, a ja prześlę Ci zbiór kilkunastu praktycznych wskazówek dla programisty aplikacji mobilnych.

Menu