Содержание
В предыдущей части мы познакомились с шаблоном MVVM и даже написали небольшое приложение, использующее все три компонента — View, ViewModel и Model. В этом приложении мы продемонстрировали лишь часть работы — передачу данных от модели к представлению. Однако на ViewModel возлагаются также и задачи по обработке событий, сообщающих об изменении модели и передаче событий об изменении данных в пользовательский интерфейс. Для решения этих задач в .NET MAUI используются команды.
Команды в .NET MAUI
Команда в .NET MAUI — это специальный объект, связанный (обычно) с какой-либо функцией приложения и реализующий интерфейс ICommand, к которому привязывается пользовательский интерфейс.
Интерфейс System.Windows.Input.ICommand выглядит следующим образом:
public interface ICommand
{
event EventHandler? CanExecuteChanged;
bool CanExecute(object? parameter);
void Execute(object? parameter);
}
- Событие
CanExecuteChangedгенерируется каждый раз, когда происходят какие-либо изменения, который могут повлиять на выполнение команды. - Метод
CanExecute()возвращаетtrue, если команда может быть выполнена. - Метод
Execute()выполняет команду.
Для реализации системы команд в приложении мы можем либо создать свой класс, наследующий ICommand, либо воспользоваться классами Command или Command<T> для создания объектов своих команд.
Пример использования команд в приложении .NET MAUI
Добавление команды в модель представления
Чтобы продемонстрировать работу команд в .NET MAUI воспользуемся примером из предыдущей части. На данный момент у нас определена модель представления следующего вида:
public class ProjectViewModel : INotifyPropertyChanged
{
private Project project = new()
{
Id = 1,
Name = "MVVM в .NET MAUI",
Description = "Изучаем применение шаблона MVVM в .NET MAUI",
Author = "Vlad (csharp.webdelphi.ru)",
};
public string Name
{
get => project.Name;
set
{
if (project.Name != value)
{
project.Name = value;
OnPropertyChanged();
}
}
}
public int Id
{
get => project.Id;
set
{
if (project.Id != value)
{
project.Id = value;
OnPropertyChanged();
}
}
}
public string Description
{
get => project.Description;
set
{
if (project.Description != value)
{
project.Description = value;
OnPropertyChanged();
}
}
}
public string Author
{
get => project.Author;
set
{
if (project.Author != value)
{
project.Author = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
Доработаем этот класс, добавив в него команду:
public class ProjectViewModel : INotifyPropertyChanged
{
private Project project = new()
{
Id = 1,
Name = "MVVM в .NET MAUI",
Description = "Изучаем применение шаблона MVVM в .NET MAUI",
Author = "Vlad (csharp.webdelphi.ru)",
};
public ObservableCollection<Project> Projects { get; set; } = [];
public ICommand AddCommand { get; set; }
public ProjectViewModel()
{
AddCommand = new Command(() =>
{
Project project = new()
{
Id = Projects.Count + 1,
Name = this.Name,
Description = this.Description,
Author = this.Author
};
Projects.Add(project);
Id = -1;
Name = string.Empty;
Description = string.Empty;
Author = string.Empty;
});
}
public string Name
{
get => project.Name;
set
{
if (project.Name != value)
{
project.Name = value;
OnPropertyChanged();
}
}
}
public int Id
{
get => project.Id;
set
{
if (project.Id != value)
{
project.Id = value;
OnPropertyChanged();
}
}
}
public string Description
{
get => project.Description;
set
{
if (project.Description != value)
{
project.Description = value;
OnPropertyChanged();
}
}
}
public string Author
{
get => project.Author;
set
{
if (project.Author != value)
{
project.Author = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
Посмотрим на изменения. Во-первых теперь класс ProjectViewModel хранит в своих свойствах не только информацию по проекту, но и список этих проектов:
public ObservableCollection<Project> Projects { get; set; } = [];
при этом, чтобы мы могли получать сообщения об изменении этой коллекции, свойство Projects определено именно как ObservableCollection<Project>(информацию об этом классе см. здесь), а не обычный List<T>.
Во-вторых, мы определили новое свойство типа ICommand:
public ICommand AddCommand { get; set; }
а сам объект команды мы создаем непосредственно в конструкторе класса:
public ProjectViewModel()
{
AddCommand = new Command(() =>
{
Project project = new()
{
Id = Projects.Count + 1,
Name = this.Name,
Description = this.Description,
Author = this.Author
};
Projects.Add(project);
Id = -1;
Name = string.Empty;
Description = string.Empty;
Author = string.Empty;
});
}
Здесь мы воспользовались одно из версий конструктора класса Command. Наша команда создает новый объект типа Project, используя свойства класса ProjectViewModel, затем добавляет полученный объект в коллекцию Projects и сбрасывает свойства класса до значений по умолчанию. Теперь воспользуемся этой командой в нашем приложении.
Использование команды в XAML
Чтобы использовать команду в приложении .NET MAUI мы должны использовать привязку. На данный момент, наша модель представления зарегистрирована в приложении как сервис и передается в представление через конструктор MainPage и нам остается только немного доработать код XAML страницы. Изменим код MainPage.xaml следующим образом:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvvmExample.MainPage"
xmlns:models ="clr-namespace:MvvmExample.Models"
xmlns:local ="clr-namespace:MvvmExample.ViewModels"
x:DataType="local:ProjectViewModel">
<ScrollView>
<VerticalStackLayout>
<Label Text="Название проекта" />
<Entry Text="{Binding Name}" FontAttributes="Bold" />
<Label Text="Описание" />
<Entry Text="{Binding Description}" FontAttributes="Bold" />
<Label Text="Автор" />
<Entry Text="{Binding Author}" FontAttributes="Bold" />
<Button Text="Добавить" Command="{Binding AddCommand}"/>
<CollectionView ItemsSource="{Binding Projects}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type models:Project}">
<TableView>
<TableRoot>
<TableSection Title="{Binding Name}">
<TextCell Text="{Binding Id}"/>
<TextCell Text="{Binding Description}"/>
<TextCell Text="{Binding Author}"/>
</TableSection>
</TableRoot>
</TableView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Первое, что мы сделали — это добавили пространство имен
xmlns:models ="clr-namespace:MvvmExample.Models"
чтобы использовать тип Project при определении шаблона данных. Далее, мы привязали нашу команду AddCommand к свойству Command кнопки:
<Button Text="Добавить" Command="{Binding AddCommand}"/>
И в заключении мы определили элемент CollectionView для вывода всего списка проектов:
<CollectionView ItemsSource="{Binding Projects}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type models:Project}">
<TableView>
<TableRoot>
<TableSection Title="{Binding Name}">
<TextCell Text="{Binding Id}"/>
<TextCell Text="{Binding Description}"/>
<TextCell Text="{Binding Author}"/>
</TableSection>
</TableRoot>
</TableView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Обратите внимание, что использование механизма команд позволяет нам не использовать события той же кнопки Button, связывая пользовательский интерфейс с моделью данных через нашу модель представления ProjectViewModel. Запустим приложение и проверим его работу.
Сразу после запуска приложения мы увидим заполненные элементы Entry и пустой список проектов:
Как только мы нажмем кнопку «Добавить» выполнится метод Execute нашей команды и мы увидим:
Можно добавить и другие проекты в список, заполняя Entry:
Итого
Для того, чтобы обрабатывать какие-либо действия в пользовательском интерфейсе, в MVVM используется механизм команд. Команда — это специальный объект, реализующий интерфейс ICommand и выполняющий какую-либо функцию в приложении. Для использования команд в приложении, элементы управления привязываются к командам, используя свойство Command.