Пример MVVM

Последнее обновление: 14.01.2021

Рассмотрим на конкретном примере применение паттерна MVVM. Итак, создадим новое приложение, которое назовем MvvmApp. Задача нашего приложения будет заключаться в создании списка друзей, а также их добавлении, редактировании и удалении. Вобщем управление списком друзей.

Вначале добавим в главный проект три новых папки для каждого из компонентов паттерна:

  • Папка Models

  • Папка ViewModels

  • Папка Views

Страницу MainPage.xaml, если она имется в проекте по умолчанию, можно удалить.

Финальная структура проекта будет выглядеть следующим образом:

Структура проекта в MVVM в Xamarin

Вначале определим в папке Models модель, которая будет представлять данные - класс Friend:

namespace HelloApp.Models
{
    public class Friend
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
    }
}

Чтобы работать с данными этой модели добавим в папку Views две страницы XAML по типу Content Page: FriendsListPage.xaml (для вывода списка друзей) и FriendPage.xaml (для управления одним другом).

Пример Mvvm в Xamarin Forms

Далее добавим в другую папку ViewModels класс FriendViewModel:

using System.ComponentModel;
using HelloApp.Models;

namespace HelloApp.ViewModels
{
    public class FriendViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        FriendsListViewModel lvm;

        public Friend Friend { get; private set; }

        public FriendViewModel()
        {
            Friend = new Friend();
        }

        public FriendsListViewModel ListViewModel
        {
            get { return lvm; }
            set
            {
                if (lvm != value)
                {
                    lvm = value;
                    OnPropertyChanged("ListViewModel");
                }
            }
        }
        public string Name
        {
            get { return Friend.Name; }
            set
            {
                if (Friend.Name != value)
                {
                    Friend.Name = value;
                    OnPropertyChanged("Name");
                }
            }
        }
        public string Email
        {
            get { return Friend.Email; }
            set
            {
                if(Friend.Email != value)
                {
                    Friend.Email = value;
                    OnPropertyChanged("Email");
                }
            }
        }
        public string Phone
        {
            get { return Friend.Phone; }
            set
            {
                if (Friend.Phone != value)
                {
                    Friend.Phone = value;
                    OnPropertyChanged("Phone");
                }
            }
        }

        public bool IsValid
        {
            get
            {
                return ((!string.IsNullOrEmpty(Name.Trim())) ||
                    (!string.IsNullOrEmpty(Phone.Trim())) ||
                    (!string.IsNullOrEmpty(Email.Trim())));
            }
        }
        protected void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

Фактически этот класс является надстройкой над объектом Friend.

Затем добавим в папку ViewModels новый класс, который будет представлять список друзей и который будет использоваться на странице FriendsListPage - класс FriendsListViewModel:

using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using System.ComponentModel;
using HelloApp.Views;

namespace HelloApp.ViewModels
{
    public class FriendsListViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<FriendViewModel> Friends { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand CreateFriendCommand { protected set; get; }
        public ICommand DeleteFriendCommand { protected set; get; }
        public ICommand SaveFriendCommand { protected set; get; }
        public ICommand BackCommand { protected set; get; }
        FriendViewModel selectedFriend;

        public INavigation Navigation { get; set;}

        public FriendsListViewModel()
        {
            Friends = new ObservableCollection<FriendViewModel>();
            CreateFriendCommand = new Command(CreateFriend);
            DeleteFriendCommand = new Command(DeleteFriend);
            SaveFriendCommand = new Command(SaveFriend);
            BackCommand = new Command(Back);
        }

        public FriendViewModel SelectedFriend
        {
            get { return selectedFriend; }
            set
            {
                if (selectedFriend != value)
                {
                    FriendViewModel tempFriend = value;
                    selectedFriend = null;
                    OnPropertyChanged("SelectedFriend");
                    Navigation.PushAsync(new FriendPage(tempFriend));
                }
            }
        }
        protected void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        private void CreateFriend()
        {
            Navigation.PushAsync(new FriendPage(new FriendViewModel() { ListViewModel = this }));
        }
        private void Back()
        {
            Navigation.PopAsync();
        }
        private void SaveFriend(object friendObject)
        {
            FriendViewModel friend = friendObject as FriendViewModel;
            if (friend != null && friend.IsValid && !Friends.Contains(friend))
            {
                Friends.Add(friend);
            }
            Back();
        }
        private void DeleteFriend(object friendObject)
        {
            FriendViewModel friend = friendObject as FriendViewModel;
            if (friend != null)
            {
                Friends.Remove(friend);
            }
            Back();
        }
    }
}

Для хранения списка друзей, который будет выводится на страницу, здесь определена коллекция Friends.

Навигация также является частью логики приложения, которая не относится к визуальной части, поэтому для хранения сервиса навигации здесь определено свойство Navigation. В дальнейшем через это свойство будет производиться переход к FriendPage.

Для управлением списком друзей в классе определено четыре команды. Команда добавления нового друга приводит в действие метод CreateFriend(), в котором производится переход к FriendPage. В конструктор FriendPage передается текущий объект FriendViewModel, который мы далее создадим.

По команде возвращения назад выполняется метод Back(), который производит переход назад.

Команда сохранения объекта выполняет метод SaveFriend(). В этом методе новый объект добавляется в коллекцию Friends.

По команде удаления вызывается метод DeleteFriend, который удаляет объект из списка.

Теперь изменим код страниц из папки Views. В коде C# страницы FriendsListPage пропишем следующее содержимое:

using Xamarin.Forms;
using HelloApp.ViewModels;

namespace HelloApp.Views
{
    public partial class FriendsListPage : ContentPage
    {
        public FriendsListPage()
        {
            InitializeComponent();
            BindingContext = new FriendsListViewModel() { Navigation = this.Navigation };
        }
    }
}

Здесь создается объект FriendsListViewModel, который устанавливается в качестве контекста страницы.

В коде XAML у этой страницы пропишем выражения привязки к этому объекту:

<?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="HelloApp.Views.FriendsListPage" Title="Список друзей">
  <StackLayout>
    <Button Text="Добавить" Command="{Binding CreateFriendCommand}" />
    <ListView x:Name="booksList" ItemsSource="{Binding Friends}" 
              SelectedItem="{Binding SelectedFriend, Mode=TwoWay}" HasUnevenRows="True">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <ViewCell.View>
              <StackLayout>
                <Label Text="{Binding Name}" FontSize="Medium" />
                <Label Text="{Binding Email}" FontSize="Small" />
                <Label Text="{Binding Phone}" FontSize="Small" />
              </StackLayout>
            </ViewCell.View>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>

Несмотря на то, что тут определена кнопка для добавления нового объекта, код страницы не содержит никаких обработчиков, потому что всю обработку будет выполнять команда CreateFriendCommand, определенная в FriendsListViewModel.

Затем изменим код c# у страницы FriendPage:

using HelloApp.ViewModels;
using Xamarin.Forms;

namespace HelloApp.Views
{
    public partial class FriendPage : ContentPage
    {
        public FriendViewModel ViewModel { get; private set; }
        public FriendPage(FriendViewModel vm)
        {
            InitializeComponent();
            ViewModel = vm;
            this.BindingContext = ViewModel;
        }
    }
}

Теперь страница в качестве параметра в конструкторе принимает объект FriendViewModel, устанавливает его в качестве контекста и также не содержит никакой другой логики.

И в коде xaml у страницы определим выражения привязки:

<?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="HelloApp.Views.FriendPage" Title="Информация о друге">
  <StackLayout>
    <StackLayout x:Name="friendStack">
      <Label Text="Имя" />
      <Entry Text="{Binding Name}" FontSize="Medium" />
      <Label Text="Электронная почта" />
      <Entry Text="{Binding Email}" FontSize="Medium" />
      <Label Text="Телефон" />
      <Entry Text="{Binding Phone}" FontSize="Medium" />
    </StackLayout>
    <StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
      <Button Text="Добавить" Command="{Binding ListViewModel.SaveFriendCommand}" CommandParameter="{Binding}" />
      <Button Text="Удалить" Command="{Binding ListViewModel.DeleteFriendCommand}" CommandParameter="{Binding}" />
      <Button Text="Назад" Command="{Binding Path=ListViewModel.BackCommand}" />
    </StackLayout>
  </StackLayout>
</ContentPage>

И при нажатии на кнопки будет вызываться соответствующая команда. Кроме того, первые две кнопки передают в команды параметр, который представляет привязанный объект - то есть тот объект FriendViewModel, который передан в конструктор и установлен в качестве контекста страницы. Выражение {Binding} без указания свойства объекта выполняет привязку к объекту, который является контекстом для данного элемента управления. Таким образом, команды в FriendsListViewModel получат добавляемый или удаляемый объект FriendViewModel.

При этому для редактирования в данном случае не надо нажимать никакую кнопку, так как при изменении значений в текстовых полях сразу же изменяться значения у этого объекта в списке. И после изменения значений достаточно нажать на кнопку Назад для возврата на главную страницу. Правда, такое поведение не всегда бывает удобно. В этом случае мы можем добавить к объекту Friend/FriendViewModel какой-нибудь идентификатор. А при редактировании передавать не объект из списка, а его копию. Затем при сохранении данных в зависимости от Id выполнять либо добавление (если Id не установлен), либо редактирование. Удаление в этом случае также бы производилось бы по Id.

И в конце в классе App установим страницу FriendsListPage в качестве главной:

using Xamarin.Forms;
using HelloApp.Views;

namespace HelloApp
{
    public partial class App : Application
    {
        public App()
        {
            MainPage = new NavigationPage(new FriendsListPage());
        }

        protected override void OnStart()
        { }

        protected override void OnSleep()
        { }

        protected override void OnResume()
        { }
    }
}

Запустим приложение и добавим какой-нибудь объект.

Добавление в MVVM в Xamarin

И после добавления мы увидим этот объект в списке на главной странице:

Работа с MVVM в Xamarin
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850