takataka430’s blog

.NET系を中心に勉強したことのまとめを書きます

【Xamarin.Forms】CheckBoxをListView内で利用して複数選択する(実装編)

前回の記事では複数選択する方法を書きました。

takataka430.hatenablog.com

今回の記事ではチェックを入れた要素を削除する機能を実装してみたいと思います。

*この記事内での「編集モード」はチェックボックスと削除ボタンが表示されている状態を意味します。

コード

画面(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:local="clr-namespace:CheckBox" 
             x:Class="CheckBox.MainPage">
    <StackLayout>
        <StackLayout Orientation="Horizontal">
            <!-- チェックした要素を削除するボタン -->
            <Button x:Name="DeleteButton"
                    Text="削除"
                    Clicked="Delete_clicked"
                    TextColor="Red"
                    IsVisible="false"/>
            <!-- 削除ボタンとCheckBoxを非表示にする -->
            <Button x:Name="BackButton"
                    Text="戻る"
                    Clicked="Back_clicked"
                    IsVisible="false"/>
            <!-- 削除ボタンとCheckBoxを表示する -->
            <Button x:Name="EditButton"
                    Text="編集"
                    Clicked="Handle_Clicked"
                    HorizontalOptions="EndAndExpand"/>
        </StackLayout>
        
        <ListView x:Name="list">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid>
                            <!-- 2行1列のグリッドを作成 -->
                            <Grid.ColumnDefinitions>
                                <!-- チェックボックスを表示する領域 -->
                                <ColumnDefinition Width="30"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <CheckBox IsChecked="{Binding IsChecked}"
                                      IsVisible="{Binding EditMode}"
                                      Grid.Column="0"/>
                            <Label Text="{Binding Name}"
                                   Grid.Column="1"
                                   VerticalOptions="Center"/>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

  

コードビハインド(MainPage.Xaml.cs)

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Xamarin.Forms;

namespace CheckBox
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(true)]
    public partial class MainPage : ContentPage
    {
        //編集モードかどうかの状態を保持
        private bool IsEditMode = false;

        //ListView表示用のコレクション
        private ObservableCollection<Person> person = new ObservableCollection<Person>
        {
            new Person{Name = "A"},
            new Person{Name = "B"},
            new Person{Name = "C"},
            new Person{Name = "D"},
            new Person{Name = "E"},
        };

        public MainPage()
        {
            InitializeComponent();

            //ListViewの要素を指定
            list.ItemsSource = person;
        }

        void Handle_Clicked(object sender, EventArgs e)
        {
            ChangeMode();
        }

        //チェックしたListViewの要素を削除
        private async void Delete_clicked(object sender, EventArgs e)
        {
            bool isDelete = await DisplayAlert("削除の確認", "選択した項目を削除してよいですか?", "削除する", "キャンセル");
            if (isDelete)
            {
                var itemNumber = person.Count;

                //personコレクションと中身が同じコレクションを新たに作成
                var itemCollection = new ObservableCollection<Person>(person);

                //チェックが入っている要素を削除 
                for (int i = 0; i < itemNumber; i++)
                {
                    var item = itemCollection[i];
                    if (item.IsChecked)
                    {
                        person.Remove(item);
                    }
                }
            }
        }
        
        private void Back_clicked(object sender, EventArgs e)
        {
            ChangeMode();
        }

        //編集モードの切り替えのためのメソッド
        private void ChangeMode()
        {
            IsEditMode = !IsEditMode;
            DeleteButton.IsVisible = IsEditMode;
            BackButton.IsVisible = IsEditMode;
            EditButton.IsVisible = !IsEditMode;

            foreach (var a in person)
            {
                a.EditMode = IsEditMode;

                if (!IsEditMode)
                {
                    a.IsChecked = false;
                }
            }
        }
    }

    //変更通知がいくようにしないとObservableCollectionの要素内のプロパティが変更されても画面に反映されない
    public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string name;
        public string Name
        {
            get { return this.name; }
            set
            {
                if (name != value)
                {
                    this.name = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
                }
            }
        }

        private bool isChecked;
        public bool IsChecked
        {
            get { return this.isChecked; }
            set
            {
                if (isChecked != value)
                {
                    this.isChecked = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
                }
            }
        }

        private bool editMode;
        public bool EditMode
        {
            get { return this.editMode; }
            set
            {
                if (editMode != value)
                {
                    this.editMode = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(EditMode)));
                }
            }
        }
    }
}

では動きを見てましょう。

f:id:takataka430:20190704232629g:plain:w200

うまく動きました!

実装の失敗例

しかし、削除機能を実装するのに結構苦労しました・・・。何かの参考になるかと思い、失敗した実例をご紹介したいと思います。(Delete_clickedイベントの「チェックが入っている要素を削除 」というコメントがある所の処理です)

失敗1

「繰り返しだからforeachだろう」と思って以下のようなコードにしてみました。

foreach (var item in person)
{
      if (item.IsChecked)
      {
          person.Remove(item);
      }
}

これだとエラーになります。調べてみると、どうやらforeach内でコレクションの追加、削除などはできないようです。こういった処理をしたい場合はfor文を使うといいみたいです。

失敗2

for文を利用して以下のように実装してみました。

for (int i = 0; i < person.Count; i++)
{
       var item = person[i];
       if (item.IsChecked)
       {
           person.Remove(item);
       }
}

これはうまく動くこともありますが、動きが変になります。例えば全ての要素にチェックを入れて削除しようとしても中途半端に残ってしまいます。

これはfor文の中でコレクションの要素の数が変わってしまうためです。person.Countは削除するたびに減少しますし、person[i]は意図した要素が選択されません。これを解決するため、上のコードではpersonと全く同じ別のコレクションを作成しました。
ところでこの方法であってるんでしょうか・・・?あまり自信がないので、修正点あったら教えてください。

  
以上、一例として削除機能を実装してみました。是非皆さんも使ってみてくださいね。それでは!