Контекстное меню

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

Ключевым элементом для работы с наборами данных является 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, которым будет передаваться текущий объект, для которого вызвано контекстное меню.

И если мы запустим приложение и одним долгим касанием нажмем на какой-нибудь элемент списка, то отобразится контекстное меню:

Контекстное меню в MVVM в Xamarin Forms

При этом надо отметить, что на разных ОС (даже на разных вериях одной ОС Android) визуально контекстное меню может выглядеть иначе.

Хотя здесь элементы меню используют команды и их параметры, однако нам в принцпе не обязательно использовать контекстное меню именно в контексте MVVM. Так, у каждого элемента меню есть стандартное событие Clicked, для которого мы можем определить в коде обработчик. Например:

<MenuItem Text="Show" Clicked="Show" />

А в коде прописать какой-нибудь метод обработки:

private async void Show(object sender, EventArgs e)
{
    await DisplayAlert("Контекстное меню", "Пункт Show", "OK");
}

В тоже время в MVVM использование контекстного меню имеет свои плюсы, в частности, возможность передачи параметра.

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