【Xamarin.Forms】コンテキストメニューでListViewから行を削除する

#はじめに

Xamarin.FormsのListViewで行のコンテキストメニューからその行を削除します。
コードビハインドは書かず、MVVMで実現します。

最初に言い訳です。
この記事を書いてみて、Xamarin.Fromsの基礎がわかったいないことを改めて自覚。。。
https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/app-fundamentals/
曖昧な表現がありますので、予めご了承ください。

#したいこと（できたこと）

![ListViewContextMenuDelete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342629/e06306ed-2d6a-9ca2-c52c-e8dc389a9f19.gif)

#環境
Visual Studio Community 2019 for Mac
Xamarin.Forms 4.2.0
Prism.Unity.Forms 7.2.0（オプション）

#ソース
GitHubにソースを上げています。
https://github.com/ats-y/TryXamarinListViewContextMenu

以下、上記ソースを抜粋して説明します。

#説明

ViewとViewModelにわけて説明します。

## View

```XML:MainPage.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"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="TryContextMenu.MainPage"
             x:Name="MainPageContentPage">
    <StackLayout>

        <!-- 社員一覧 -->
        <ListView x:Name="EmployeeListView"
                  ItemsSource="{Binding EmployeeList}"
                  HasUnevenRows="True">

            <!-- 社員行 -->
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>

                        <!-- コンテキストメニューの定義-->
                        <ViewCell.ContextActions>

                            <!--
                            削除コンテキストメニュー
                            Command属性：タップするとMainPageContentPageのDeleteCommandを呼び出す。
                            CommandParameter属性：Command属性で指定したコマンドに渡すデータを指定。-->
                            <MenuItem Text="削除"
                                      IsDestructive="True"
                                      Command="{Binding Source={x:Reference MainPageContentPage}, Path=BindingContext.DeleteCommand}"
                                      CommandParameter="{Binding .}" />

                        </ViewCell.ContextActions>

                        <!-- レイアウト定義 -->
                        <StackLayout Padding="10,100,0,100"
                                     BackgroundColor="{Binding BackColor}"
                                     HorizontalOptions="FillAndExpand"
                                     Orientation="Horizontal">

                            <StackLayout Orientation="Vertical">
                                <Label Text="名前：" />
                                <Label Text="{Binding FullName}" />
                            </StackLayout>

                        </StackLayout>

                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>

        </ListView>

    </StackLayout>
</ContentPage>
```

###Viewのポイント

#####コードビハインド（MainPage.xaml.cs）
今回やりたいことを実現するにあたって、コードビハインドには特に何も書いていません。


#####ルートContentPageの名前付け
ルートのContentPage要素には「MainPageContentPage」という名前をつけています。

```XML
   <ContentPage （略）
    x:Name="MainPageContentPage">
```

#####削除コンテキストの定義
MenuItem要素で削除コンテキストメニューを定義します。

```XML
<!--
削除コンテキストメニュー
Command属性：タップするとMainPageContentPageのDeleteCommandを呼び出す。
CommandParameter属性：Command属性で指定したコマンドに渡すデータを指定。-->
<MenuItem Text="削除"
            IsDestructive="True"
            Command="{Binding Source={x:Reference MainPageContentPage}, Path=BindingContext.DeleteCommand}"
            CommandParameter="{Binding .}" />
```
Command属性、CommandParameter属性でコンテキストメニューをタップした時のコマンドとコマンドに渡すパラメータを定義しています。

Command属性で指定した値によって、
**「*MainPageContentPage*に紐づいているViewModelの*DeleteCommand*を呼び出してね」**
と定義しています。
「MainPageContentPage」はルートのContentPage要素につけた名前です。

CommandParameter属性はCommand属性で指定したコマンドを呼び出すときに渡す値を定義します。
```{Binding .}```とすることでタップした行データ、つまりListView要素のItemSource属性に紐づいているListのタップした行の要素が渡されました。

## ViewModel

```C#:MainPageViewModel.cs
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using TryContextMenu.Models;
using Xamarin.Forms;

namespace TryContextMenu.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        /// <summary>
        /// 社員リスト
        /// </summary>
        private ObservableCollection<Employee> _employeeList;

        /// <summary>
        /// 社員リストプロパティ
        /// </summary>
        public ObservableCollection<Employee> EmployeeList
        {
            get { return _employeeList; }
            set { SetProperty(ref _employeeList, value); }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainPageViewModel()
        {
            // 社員リストを生成する。
            _employeeList = new ObservableCollection<Employee>
            {
                new Employee { FullName = "社員 太郎" },
                new Employee { FullName = "社員 二郎" },
                new Employee { FullName = "社員 三郎" },
                new Employee { FullName = "社員 四郎" },
                new Employee { FullName = "社員 五郎" },
            };
        }

        /// <summary>
        /// 社員リストから引数で指定した社員を削除する。
        /// </summary>
        public ICommand DeleteCommand =>
            new Command<Employee>((arg) =>
        {
            Debug.WriteLine("MainPage.DeleteCommand()");

            EmployeeList.Remove(arg);
        });
    }
}
```

###ViewModelのポイント

#####社員リストおよびプロパティ

```C#:MainPageViewクラスの社員リストおよびプロパティの定義部分
/// <summary>
/// 社員リスト
/// </summary>
private ObservableCollection<Employee> _employeeList;

/// <summary>
/// 社員リストプロパティ
/// </summary>
public ObservableCollection<Employee> EmployeeList
{
    get { return _employeeList; }
    set { SetProperty(ref _employeeList, value); }
}
```

EmployeeListプロパティは社員リストです。
MainPageViewModelクラスに対するView「MainPage.xaml」のListViewのItemSource属性で指定したバインディング```ItemsSource="{Binding EmployeeList}"```と同じ名前にすることで、社員ListViewのリストデータとMainPageViewModel.EmployeeListが紐づきます。
これにより、MainPageViewModel.EmployeeListに入れたデータがListViewに表示されます。

今回はMainPageViewクラスのコンストラクタでEmployeeListプロパティに社員リストに社員を追加していて、実行すると追加した社員がListViewに表示されます。

```C#:MainPageViewクラスのコンストラクタ
/// <summary>
/// コンストラクタ
/// </summary>
public MainPageViewModel()
{
    // 社員リストを生成する。
    _employeeList = new ObservableCollection<Employee>
    {
        new Employee { FullName = "社員 太郎" },
        new Employee { FullName = "社員 二郎" },
        new Employee { FullName = "社員 三郎" },
        new Employee { FullName = "社員 四郎" },
        new Employee { FullName = "社員 五郎" },
    };
}
```

また、ObservableCollection&lt;Employee&gt;型にすることで、社員リストプロパティの内容が変わるとViewに反映されるようになります。

#####DeleteCommandコマンド

```C#:MainPageViewクラスのDeleteCommandコマンド定義部分
/// <summary>
/// 社員リストから引数で指定した社員を削除する。
/// </summary>
public ICommand DeleteCommand =>
    new Command<Employee>((arg) =>
{
    Debug.WriteLine("MainPage.DeleteCommand()");

    EmployeeList.Remove(arg);
});
```

削除コンテキストのMenuItem要素のCommand属性で指定したPathと同じ名前でICommand型で定義します。そして、CommandParameter属性で渡すことにした型を型引数にしています。

引数argには、削除コンテキストをタップした行のEmployeeオブジェクトが渡されます。
```EmployeeList.Remove(arg);```で社員リストプロパティから、argで渡されたEmployeeオブジェクトを削除します。
社員リストはObservableCollection&lt;&gt;型なので、これで社員ListViewから当該社員行が削除されます。