前回、ListView
をXAMLで定義する場合の最小限のコードを書きました。
【Xamarin.Forms】ListView の CustomCell を XAML で定義する
上記の記事でListView
とその中身のCustomViewCell
をXAMLで定義できるようになりましたが、これではまだデータソースに変更があった場合にUIが更新されません。
そこで、今回はデータソースの変更に合わせて自動でUIが更新されるよう修正を加えたいと思います。
参考
今回は以下の記事を全面的に参考にしています。
ListViewで学ぶコレクションのBind通知 - @furugen
上記の記事との差分としては、getterにラムダを使っていることと、nullチェックを今風の書き方にしているくらいかと思います。
本文
事前準備
まずはデータソースを任意のタイミングで変更できるよう、以下2種類のボタンを追加しておきます。
- 1件目のデータの
Title
を変更するボタン - データソースの末尾にデータを追加するボタン
XAMLにボタンを2つ追加し、StackLayout
でレイアウトを整えます。1
<StackLayout
Orientation="Vertical"
Margin="16"
Spacing="16">
<StackLayout
Orientation="Horizontal"
Spacing="16">
<Button
HorizontalOptions="FillAndExpand"
HeightRequest="60"
BackgroundColor="Navy"
TextColor="White"
Text="Change Title"
Clicked="ChangeFirstTitle"/>
<Button
HorizontalOptions="FillAndExpand"
HeightRequest="60"
BackgroundColor="Teal"
TextColor="White"
Text="Append Data"
Clicked="AppendNewData"/>
</StackLayout>
<ListView
x:Name="scheduleList">
... 省略 ...
</ListView>
</StackLayout>
</StackLayout>
コードビハインド側にはボタンタップ時に実行されるメソッドを追加します。
public void ChangeFirstTitle(object sender, EventArgs e)
{
ScheduleListData[0].Title = "The Concert is Canceled!!!";
}
public void AppendNewData(object sender, EventArgs e)
{
ScheduleListData.Add(new Schedule("2019/01/29", "Enjoy Onsen Ryokan in Chichibu", "Chichibu, Saitama"));
}
これで、ボタンの準備は完了です。
現状ではこのボタンを押してScheduleListData
の中身を追加・変更しても特にUIに変化はありません。
では、ここから動的なListView
のUI変更に対応していきます。
データの追加に対応する
まずデータがScheduleList
に追加された場合に、追加された項目がリストへ自動で表示されるようにします。
対応内容自体は簡単で、データソースの型にList<T>
ではなくObservableCollection<T>
を使うだけです。
private ObservableCollection<Schedule> ScheduleListData = new ObservableCollection<Schedule>();
実行すると、以下のように"Append Data"ボタンを押すたびにリストの項目が1件ずつ追加されているのがわかるかと思います。
ObservableCollection<T>
は、Microsoftのドキュメントによると
項目が追加または削除されたとき、あるいはリスト全体が更新されたときに通知を行う動的なデータ コレクションを表します。
とのことです。詳しい仕組みはまだ調査できていませんが、要するにObservableCollection<T>
をListView
のデータソースとして利用することで、ObservableCollection<T>
が発する通知をListView
が受信し、UIを更新してくれるような作りになっているのだと思います。
データの変更に対応する
ObservableCollection<T>
はリスト自体の状態の変化は検知できても、リストに格納される一つひとつのインスタンスの状態の変化は検知しません。そのため、個別のデータのプロパティが変化してもUIには反映されません。
個別のデータのプロパティの変化を検知してListView
がUIを動的に変更できるようにするために、INotifyPropertyChanged
インターフェースを利用します。
先ほどのObservableCollection<T>
がやっていた変更通知を自分のデータクラスにも実装してあげるイメージですね。この方法はListView
に限らずC#においてModelがプロパティの値の変更を通知する手段としてよく利用されるもののようです(調査中)。
というわけで、Schedule
クラスを以下のように修正します。
-
INotifyPropertyChanged
を実装する -
public event PropertyChangedEventHandler PropertyChanged
を定義する - セッターで
PropertyChanged.Invoke()
を呼ぶ
上記を対応したコードが以下になります。
public class Schedule : INotifyPropertyChanged
{
private string datestr;
public string Datestr
{
get => this.datestr;
set
{
this.datestr = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Datestr)));
}
}
private string title;
public string Title
{
get => this.title;
set
{
this.title = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
}
}
private string place;
public string Place
{
get => this.place;
set
{
this.place = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Place)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
動作確認をすると、"Change Title"ボタンを押すと1件目の予定のタイトルが"The Concert is Canceled!!!"に変化しているのが分かります。
これで、個別のデータのプロパティが変化した場合も、動的にUIが変化するようになりました。
まとめ
AndroidやiOSのネイティブアプリを経験していると、notifyDatasetChanged()
やreloadData()
のようなメソッドがListView
にあるのではないかという気がしてきますが、見た感じXamarin.FormsのListView
にはそのようなメソッドはありませんでした。
Xamarin.Formsでは、リストのUIは自分で更新するものではなく、データバインディングの仕組みを利用してデータ側からListView
へ通知を送り、UIが自動で更新されるものと考えるのがよさそうです。
この記事のコード
コードはGitHubで公開しています。
今回の記事の分のコミットは以下のあたりです。
-
ContentPage
は子ビューを1つだけとるレイアウトですので、ListView
とボタンを並べるStackLayout
をさらにStackLayout
でひとまとめにする必要がありますので注意です。 ↩