Как происходит взаимодействие между View и ViewModel?
Во взаимодействии между View (представлением) и ViewModel (моделью представления) в Xamarin.Forms основную роль играет паттерн MVVM (Model-View-ViewModel), который обеспечивает четкое разделение UI от логики. Это повышает масштабируемость, модульность, тестируемость и переиспользуемость кода. Основным механизмом, который обеспечивает связку между View и ViewModel, является привязка данных (Data Binding).
Общая схема взаимодействия
-
View — XAML или код, отображающий пользовательский интерфейс. Не содержит бизнес-логики.
-
ViewModel — класс, содержащий свойства, команды и обработчики, к которым привязывается View.
-
Model — уровень данных, бизнес-логика, работа с БД и API (не участвует напрямую в отображении).
Связь между View и ViewModel осуществляется односторонне или двусторонне через Data Binding, что позволяет View автоматически получать изменения из ViewModel и наоборот (если нужно).
Связывание View с ViewModel
1. Через свойство BindingContext
Присваивая ViewModel в BindingContext, устанавливается источник данных для всех привязок внутри View.
Пример в C#:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
Пример в XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.MainPage">
<ContentPage.BindingContext>
<vm:MainViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Label Text="{Binding Title}" />
<Button Text="Клик" Command="{Binding ClickCommand}" />
</StackLayout>
</ContentPage>
Привязка свойств (Property Binding)
Односторонняя (OneWay)
<Label Text="{Binding Title}" />
UI обновляется, если Title изменяется. Для этого ViewModel должно реализовать INotifyPropertyChanged.
Двусторонняя (TwoWay)
<Entry Text="{Binding UserName, Mode=TwoWay}" />
Используется при вводе данных — изменения в Entry передаются в ViewModel, и наоборот.
Реализация ViewModel: INotifyPropertyChanged
Для того чтобы View знала об изменениях в свойствах ViewModel, она должна реализовать интерфейс INotifyPropertyChanged.
Пример:
public class MainViewModel : INotifyPropertyChanged
{
private string \_title;
public string Title
{
get => \_title;
set
{
if (\_title == value) return;
\_title = value;
OnPropertyChanged(nameof(Title));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Команды (ICommand) — взаимодействие с действиями UI
Позволяют ViewModel обрабатывать события без необходимости писать обработчики событий в View.
Пример:
public ICommand ClickCommand { get; }
public MainViewModel()
{
ClickCommand = new Command(OnClicked);
}
private void OnClicked()
{
Title = "Нажато!";
}
В XAML:
<Button Text="Нажми меня" Command="{Binding ClickCommand}" />
Если нужно передать параметры — используется CommandParameter.
Обработка событий в ViewModel через EventToCommand (поведение)
Xamarin.Forms не поддерживает прямую привязку к событиям, но можно использовать поведение (Behavior):
<Button Text="Кнопка">
<Button.Behaviors>
<behaviors:EventToCommandBehavior EventName="Clicked"
Command="{Binding MyCommand}" />
</Button.Behaviors>
</Button>
View обновляется при изменениях в ViewModel
-
Свойства ViewModel обновляются (например, в результате API-запроса).
-
Вызывается OnPropertyChanged, View автоматически получает обновленное значение.
-
UI мгновенно реагирует без необходимости вручную обновлять элементы.
ViewModel получает данные от View
Ввод текста
<Entry Text="{Binding Email, Mode=TwoWay}" />
Пользователь вводит email — значение передается в свойство Email в ViewModel.
Выбор в Picker
<Picker ItemsSource="{Binding Cities}"
SelectedItem="{Binding SelectedCity}" />
SelectedCity обновляется в ViewModel при выборе пользователем.
Navigation из ViewModel
С помощью внедрения INavigation или через сервис навигации (например, INavigationService):
public INavigation Navigation { get; set; }
public async Task OpenDetailsPage()
{
await Navigation.PushAsync(new DetailsPage());
}
Или при использовании DI:
public class MyViewModel
{
private readonly INavigationService \_nav;
public MyViewModel(INavigationService nav)
{
\_nav = nav;
}
public async Task GoNext() => await \_nav.NavigateToAsync<DetailsViewModel>();
}
Пример полного взаимодействия View и ViewModel
ViewModel:
public class LoginViewModel : INotifyPropertyChanged
{
private string \_username;
public string Username
{
get => \_username;
set { \_username = value; OnPropertyChanged(nameof(Username)); }
}
public ICommand LoginCommand { get; }
public LoginViewModel()
{
LoginCommand = new Command(OnLogin);
}
private void OnLogin()
{
// логика авторизации
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
View (XAML):
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.LoginPage">
<ContentPage.BindingContext>
<vm:LoginViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="20">
<Entry Placeholder="Имя пользователя"
Text="{Binding Username, Mode=TwoWay}" />
<Button Text="Войти"
Command="{Binding LoginCommand}" />
</StackLayout>
</ContentPage>
Практические замечания
-
Все ViewModel желательно наследовать от базового класса, реализующего INotifyPropertyChanged.
-
Для Unit-тестов легко заменять зависимости ViewModel на моки.
-
Не следует использовать code-behind в View — вся логика должна быть в ViewModel.
-
Если ViewModel сложная, стоит разделить на части (например, по вкладкам или экранам).
-
Команды должны быть реализованы через Command или AsyncCommand.
Во взаимодействии View и ViewModel основное правило — UI ничего не должен "знать" о логике, а ViewModel не должен зависеть от UI. Это обеспечивает высокую модульность, переиспользуемость и простоту поддержки приложения. Связь между слоями обеспечивается Data Binding, командами и шаблонами событий, что позволяет разрабатывать масштабируемые и чистые архитектурные приложения в Xamarin.Forms.