Ключевым элементом для работы с наборами данных является ListView. И нередко бывает важно продумать, какие инструменты использовать для управления данными в ListView. Одним из таких инструментов является контекстное меню. Элемент ListView позволяет прикрепить к каждому элементу контекстное меню через свойство ContextActions.
Итак, для работы с контекстным меню создадим новый проект. В качестве объектов с которыми будем работать, возьмем класс Phone
public class Phone { public string Title { get; set; } public string Company { get; set; } public int Price { get; set; } }
Далее добавим в проект модель PhoneViewModel, через которую мы будем взаимодействовать с объектами Phone:
using System.ComponentModel; namespace HelloApp { public class PhoneViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public Phone Phone { get; set; } public PhonesListViewModel ListViewModel { get; set; } public PhoneViewModel() { Phone = new Phone(); } public string Title { get { return Phone.Title; } set { if (Phone.Title != value) { Phone.Title = value; OnPropertyChanged("Title"); } } } public string Company { get { return Phone.Company; } set { if (Phone.Company != value) { Phone.Company = value; OnPropertyChanged("Company"); } } } public int Price { get { return Phone.Price; } set { if (Phone.Price != value) { Phone.Price = value; OnPropertyChanged("Price"); } } } protected void OnPropertyChanged(string propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } }
Кроме свойств-надстроек над свойствами объекта Phone здесь есть свойство, которое представляет модель PhonesListViewModel - объект списка, в котором будет находиться текущий объект PhoneViewModel. И далее добавим в проект новый класс PhonesListViewModel:
using System.Collections.ObjectModel; using System.Windows.Input; using Xamarin.Forms; namespace HelloApp { public class PhonesListViewModel { public ObservableCollection<PhoneViewModel> Phones { get; set; } public ICommand MoveToTopCommand { protected set; get; } public ICommand MoveToBottomCommand { protected set; get; } public ICommand RemoveCommand { protected set; get; } public PhonesListViewModel() { Phones = new ObservableCollection<PhoneViewModel> { new PhoneViewModel { Title="Pixel 5", Price=55000, Company="Google", ListViewModel=this}, new PhoneViewModel {Title="Xiaomi Mi 10", Price= 28000, Company="Xiaomi", ListViewModel=this}, new PhoneViewModel {Title="iPhone 12 Pro", Price=30000, Company="Apple", ListViewModel=this }, new PhoneViewModel {Title="Galaxy S 10", Price=60000, Company="Samsung", ListViewModel=this }, new PhoneViewModel {Title="Huawei P40 Pro", Price=36000, Company="Huawei", ListViewModel=this } }; MoveToTopCommand = new Command(MoveToTop); MoveToBottomCommand = new Command(MoveToBottom); RemoveCommand = new Command(Remove); } private void MoveToTop(object phoneObj) { PhoneViewModel phone = phoneObj as PhoneViewModel; if (phone == null) return; int oldIndex = Phones.IndexOf(phone); if (oldIndex > 0) Phones.Move(oldIndex, oldIndex - 1); } private void MoveToBottom(object phoneObj) { PhoneViewModel phone = phoneObj as PhoneViewModel; if (phone == null) return; int oldIndex = Phones.IndexOf(phone); if (oldIndex < Phones.Count-1) Phones.Move(oldIndex, oldIndex + 1); } private void Remove(object phoneObj) { PhoneViewModel phone = phoneObj as PhoneViewModel; if (phone == null) return; Phones.Remove(phone); } } }
Для хранения данных определена коллекция Phones, которая инициализируется в конструкторе.
Кроме этой коллекции в классе определены три команды. Команда MoveToTopCommand поднимает элемент на одну позицию вверх в списку (в реальности это выглядит как перемещение ближе к началу коллекции Phones). Команда MoveToBottomCommand, наоборот, опускает элемент на одну позицию вниз. А команда RemoveCommand удаляет элемент из списка.
В коде C# у главной страницы MainPage определим контекст данных:
using System; using Xamarin.Forms; namespace HelloApp { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); this.BindingContext = new PhonesListViewModel(); } } }
В коде 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.MainPage"> <StackLayout> <ListView ItemsSource="{Binding Phones}" HasUnevenRows="True"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.ContextActions> <MenuItem Text="Вверх" Command="{Binding Path=ListViewModel.MoveToTopCommand}" CommandParameter="{Binding}" /> <MenuItem Text="Вниз" Command="{Binding Path=ListViewModel.MoveToBottomCommand}" CommandParameter="{Binding}" /> <MenuItem Text="Удалить" Command="{Binding Path=ListViewModel.RemoveCommand}" CommandParameter="{Binding}" /> </ViewCell.ContextActions> <ViewCell.View> <StackLayout> <Label Text="{Binding Title}" FontSize="Medium" /> <Label Text="{Binding Company}" FontSize="Small" /> <Label Text="{Binding Price}" FontSize="Small" /> </StackLayout> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
Для определения меню применяется свойство ContextActions объекта ViewCell, который представляет собой отдельный элемент. Каждый пункт в этом меню представляет элемент MenuItem. У MenuItem можно определить команду и параметр, который будет пределаться команде при ее выполнении. В данном примере будут вызываться команды из PhonesListViewModel, которым будет передаваться текущий объект, для которого вызвано контекстное меню.
И если мы запустим приложение и одним долгим касанием нажмем на какой-нибудь элемент списка, то отобразится контекстное меню:
При этом надо отметить, что на разных ОС (даже на разных вериях одной ОС Android) визуально контекстное меню может выглядеть иначе.
Хотя здесь элементы меню используют команды и их параметры, однако нам в принцпе не обязательно использовать контекстное меню именно в контексте MVVM. Так,
у каждого элемента меню есть стандартное событие Clicked
, для которого мы можем определить в коде обработчик. Например:
<MenuItem Text="Show" Clicked="Show" />
А в коде прописать какой-нибудь метод обработки:
private async void Show(object sender, EventArgs e) { await DisplayAlert("Контекстное меню", "Пункт Show", "OK"); }
В тоже время в MVVM использование контекстного меню имеет свои плюсы, в частности, возможность передачи параметра.