Статьи

Xamarin Forms для разработчиков Windows Phone — Использование шаблона MVVM

Мы уже много раз говорили о шаблоне MVVM в этом блоге и о том, как реализовать его в приложениях для Windows Phone 8 с использованием Caliburn Micro или в универсальных приложениях Windows с Caliburn Micro и Prism. Шаблон Model-View-ViewModel очень полезен в проектах на основе XAML, потому что разделение между логикой и пользовательским интерфейсом дает много преимуществ в тестируемости и ремонтопригодности. Однако, когда мы имеем дело с проектами, ориентированными на несколько платформ с общей базой кода (например, с универсальными приложениями Windows), использование шаблона MVVM является более или менее основным требованием: благодаря разделению между логикой и пользовательским интерфейсом, становится легче делиться большим количеством кода с различными проектами.

Xamarin Forms не делает никаких исключений: применение шаблона MVVM — лучший способ создать общую кодовую базу, которую можно использовать в проектах iOS, Android и Windows Phone. В этом посте мы увидим, как создать простой проект, используя один из самых популярных наборов инструментов: MVVM Light.

Почему MVVM Light?

MVVM Light, безусловно, является самым простым и гибким инструментарием MVVM, доступным на данный момент. Основным его преимуществом является простота: поскольку его реализация очень проста, его можно легко перенести с одной платформы на другую. Как вы увидите в этом посте, если вы уже работали с MVVM Light на других платформах, вы окажетесь дома: за исключением некоторых незначительных различий, подход точно такой же, как в Windows Phone, Магазин Windows или WPF.

Тем не менее, простота MVVM Light также является ее слабым местом: по сравнению с такими платформами, как Caliburn Micro или Prism, она пропускает всю инфраструктуру, которая часто требуется, когда вам приходится иметь дело с такими функциями платформы, как навигация, жизненный цикл приложения и т. Д. Следовательно, как мы увидим в следующих статьях, у вас может возникнуть необходимость в расширении MVVM Light для решения специфичных для платформы сценариев. В следующем посте я покажу вам реализацию, которую я сделал для решения этой проблемы: сейчас давайте просто сосредоточимся на реализации базового проекта Xamarin Forms с помощью MVVM Light. Эти знания, на самом деле, важны для понимания следующих постов, которые я собираюсь опубликовать.

Создание первого проекта MVVM

Первый шаг — это то же самое, что мы видели в предыдущем посте: создание нового приложения Xamarin.Forms Blank. После того, как мы проверили, что мы используем последнюю версию Xamarin Forms, нам нужно также установить MVVM Light в общем проекте: вам нужно установить специальную версию для библиотек PCL, которая  http: //www.nuget .org / пакеты / Portable.MvvmLightLibs /

Теперь вы готовы настроить инфраструктуру для создания приложения: начнем с добавления нашего первого View и нашей первой ViewModel. Это не обязательно, но для лучшего проектирования приложения я предпочитаю создать папку для представлений (называемых Views) и папку для ViewModels (называемых ViewModels). Затем добавьте новую страницу форм Xamarin в папку Views (называемую, например,  MainView.xaml ) и новый простой класс в папку ViewModels (называемую, например,  MainViewModel.cs).

ViewModelLocator

Одним из основных требований в приложении MVVM является поиск способа подключения View к его ViewModel: мы могли бы просто создать новый экземпляр ViewModel и назначить его в качестве контекста данных страницы, но таким образом мы не будем возможность использовать такие методы, как внедрение зависимостей, для разрешения ViewModels и необходимых сервисов во время выполнения (мы еще поговорим об этом подходе в следующем посте). Типичный подход при работе с MVVM Light — создать класс с именем  ViewModelLocator , который позаботится о передаче каждому View соответствующего ViewModel. Вот как   класс ViewModelLocator выглядит в проекте Xamarin Forms:

public class ViewModelLocator
{
    static ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        SimpleIoc.Default.Register<MainViewModel>();
    }
 
    /// <summary>
    /// Gets the Main property.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
        "CA1822:MarkMembersAsStatic",
        Justification = "This non-static member is needed for data binding purposes.")]
    public MainViewModel Main
    {
        get
        {
            return ServiceLocator.Current.GetInstance<MainViewModel>();
        }
    }
}

Когда класс создан, мы регистрируем поставщика внедрения зависимостей по умолчанию, который мы хотим использовать: в этом случае мы используем собственный, предложенный MVVM Light, называемый  SimpleIoc . Затем мы регистрируем в контейнере, используя метод  Register <T> ()  , все ViewModels и сервисы, которые мы хотим использовать. В этом примере мы не будем использовать какой-либо сервис: в следующем посте мы увидим, как им управлять; поэтому мы просто регистрируем нашу  MainViewModel  в контейнере. Следующим шагом является создание свойства, которое будет использоваться представлением для запроса правильного экземпляра ViewModel: мы снова используем контейнер внедрения зависимостей, в данном случае, чтобы получить зарегистрированный класс, используя метод  GetInstance <T> ()  ( где  T  — тип объекта, который нам нужен).

Теперь мы можем использовать ViewModelLocator для привязки ViewModel к его View: в нашем случае MainView  должен быть подключен к  MainViewModel . В приложении Windows Phone эта цель обычно достигается путем объявления  ViewModelLocator  в качестве глобального ресурса в  классе App, а затем, в XAML, присваивания правильного свойства (в данном случае  Main )  свойству DataContext страницы. Таким образом, ViewModel будет назначен как DataContext  всей страницы, и каждый вложенный элемент управления сможет получить доступ к командам и свойствам, предоставляемым ViewModel.

Однако этот подход не работает в формах Xamarin, поскольку у нас нет концепции глобальных ресурсов: у нас нет   файла App.xaml , где можно объявлять ресурсы, которые совместно используются на каждой странице приложения. Самый простой способ решить эту проблему — объявить ViewModelLocator  как статическое свойство   класса App в общем проекте Xamarin Forms, как в следующем примере:

public class App: Application
{
    public App()
    {
        this.MainPage = new MainView();
    }
 
    private static ViewModelLocator _locator;
 
    public static ViewModelLocator Locator
    {
        get
        {
            return _locator ?? (_locator = new ViewModelLocator());
        }
    }
}

Таким образом, вы сможете подключить ViewModel к представлению, используя это статическое свойство в коде файла страницы (в нашем случае, файла  MainView.xaml.cs ):

public partial class MainView
{
    public MainView()
    {
        InitializeComponent();
        this.BindingContext = App.Locator.Main;
    }
}

Вы можете заметить одно из самых важных различий между XAML в Windows Phone и XAML в Xamarin Forms:   свойство DataContext называется  BindingContext . Однако его цель точно такая же: определить контекст элемента управления.

Определить ViewModel

Создать ViewModel легко, если вы уже работали с MVVM Light, поскольку основные концепции абсолютно одинаковы. Допустим, мы хотим создать простое приложение, в которое пользователь может вставить свое имя: нажав кнопку, на странице отобразится сообщение, чтобы сказать привет пользователю. Вот как выглядит ViewModel для управления этим сценарием:

public class MainViewModel: ViewModelBase
{
    private string _name;
 
    public string Name
    {
        get { return _name; }
        set
        {
            Set(ref _name, value);
            ShowMessageCommand.RaiseCanExecuteChanged();
        }
    }
 
    private string _message;
 
    public string Message
    {
        get { return _message;}
        set { Set(ref _message, value); }
    }
 
    private RelayCommand _showMessageCommand;
 
    public RelayCommand ShowMessageCommand
    {
        get
        {
            if (_showMessageCommand == null)
            {
                _showMessageCommand = new RelayCommand(() =>
                {
                    Message = string.Format("Hello {0}", Name);
                }, () => !string.IsNullOrEmpty(Name));
            }
 
            return _showMessageCommand;
        }
    }
}

В действии вы можете увидеть все стандартные функции ViewModel, созданные с использованием MVVM Light в качестве инструментария:

  • ViewModel наследуется от   класса ViewModelBase , который дает вам несколько помощников для правильной поддержки  интерфейса INotifyPropertyChanged,  который необходим для уведомления элементов управления в представлении при изменении свойств, связанных через привязку.
  • Каждое свойство не определено стандартным подходом get — set, но когда значение свойства изменяется (в   методе set ), мы вызываем метод  Set (),  предлагаемый MVVM Light, который, кроме простого присвоения значения свойство, обеспечивает отправку уведомления элементам управления в представлении.
  • Когда вы работаете с шаблоном MVVM, вы не можете реагировать на действия пользователя, используя обработчики событий, так как они имеют строгую зависимость с кодом позади: вы не можете объявить обработчик событий внутри другого класса. Решение состоит в том, чтобы использовать  команды , которые являются способом выражения действий со свойством, которое можно связать с представлением с помощью привязки. MVVM Light предлагает класс, облегчающий реализацию этого сценария, который называется  RelayCommand . Когда вы создаете   объект RelayCommand , вам необходимо установить: 1) действие для выполнения (в нашем случае мы определяем сообщение для отображения пользователю) 2) опционально, условие, которое должно быть выполнено для активации команды (в нашем случае пользователь сможет вызывать команду, только если свойство с именем  Name не пусто). Если условие не выполняется, элемент управления автоматически отключается. В нашем примере, если пользователь не вставил свое имя в поле, кнопка будет отключена.
  • Когда значение   свойства Name изменяется, мы также вызываем метод RaiseCanExecuteChanged (),  предложенный  RelayCommand, который  мы только что определили: таким образом, каждый раз, когда  свойство Name  изменяется, мы сообщаем команде оценить его состояние, поскольку оно может быть изменен.

Вид

Следующий код вместо этого показывает страницу XAML форм Xamarin, которая связана с ViewModel, которую мы ранее видели:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       x:Class="MvvmLight_Sample.Views.MainView">
  <StackLayout Padding="12, 0, 12, 0">
    <Label Text="Insert your name:" />
    <Entry Text="{Binding Path=Name, Mode=TwoWay}" />
    <Button Command="{Binding Path=ShowMessageCommand}" Text="Show message" />
    <Label Text="{Binding Path=Message}" FontSize="30" />
  </StackLayout>
</ContentPage>

Это простая форма, созданная текстовой областью, в которую пользователь может вставить свое имя, и кнопкой, которая при нажатии отображает сообщение с помощью метки. Вы можете заметить некоторую разницу со стандартным XAML, доступным в Windows Phone:

  • StackPanel  контроль, который способен отображать вложенные элементы управления один ниже другого, называется 
    StackLayout .

  • Чтобы определить расстояние элемента управления от границы экрана, мы используем 
    свойство
    Padding вместо поля 
    Margin  .
  • Элемент   управления TextBlock , используемый для отображения текста пользователю, называется  Label .
  • Элемент   управления TextBox , используемый для получения ввода от пользователя, называется  Entry.
  • Содержимое кнопки (в данном случае текст) задается с помощью   свойства Text , в то время как в стандартном XAML называется  Content,  и он принимает также более сложную компоновку XAML.

За исключением этих различий в определении XAML, мы используем стандартную привязку для соединения элементов управления со свойствами, определенными в ViewModel:

  • Элемент   управления Entry имеет свойство  Text , которое содержит имя, вставленное пользователем: мы подключаем его к   свойству Name модели представления, используя двустороннюю привязку.
  • Элемент   управления Button имеет свойство  Command , которое связано с ShowMessageCommand, который  мы определили в ViewModel. Таким образом, кнопка будет включена, только если   свойство Name не пустое; если он включен, нажав его, мы отобразим приветственное сообщение пользователю.
  • Приветственное сообщение сохраняется в   свойстве Message ViewModel, которое связано с использованием привязки к последнему  элементу  управления Label на странице.

Завершение

В этой статье мы рассмотрели основные концепции использования MVVM Light в формах Xamarin: за исключением некоторых отличий (например,   использование ViewModelLocator ), подход должен быть очень знаком любому разработчику Windows Phone, который уже работал с шаблоном MVVM и инструментарий MVVM Light. В следующих статьях мы рассмотрим, как работает подход внедрения зависимостей в формах Xamarin и как мы можем использовать его в проекте MVVM Light. Как обычно, вы можете найти пример кода, использованного в этом посте, на GitHub по адресу https://github.com/qmatteoq/XamarinFormsSamples