改めて MainViewModel を次のように変更し、コレクションの子要素を追加したり削除したりできるようにしましょう。
コード 6.9:コレクションの要素を追加/削除するコマンドを用意する MainViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
namespace YKWpfIntroduction.Practices.ViewModels {
using System.Collections.Generic;
using YKWpfIntroduction.Practices.Models;
/// <summary>
/// MainView ウィンドウに対するデータコンテキストを表します。
/// </summary>
internal class MainViewModel : NotificationObject {
private List<Person> _people = new List<Person>();
/// <summary>
/// 人物情報のコレクションを取得します。
/// </summary>
public List<Person> People {
get { return this._people; }
private set { SetProperty(ref this._people, value); } }
private int _count;
private DelegateCommand _addCommand;
/// <summary>
/// 追加コマンドを取得します。
/// </summary>
public DelegateCommand AddCommand {
get {
return this._addCommand ?? (this._addCommand = new DelegateCommand(
_ =>
{
this._count++;
var person = new Person() {
FamilyName = "田中",
FirstName = this._count + "太郎", Age = this._count,
};
this.People.Add(person);
this.DeleteCommand.RaiseCanExecuteChanged();
System.Diagnostics.Debug.WriteLine(person.FullName + " を追加しました。");
}));
} }
private DelegateCommand _deleteCommand;
/// <summary>
/// 削除コマンドを取得します。
/// </summary>
public DelegateCommand DeleteCommand {
get
56 57 58 59 60 61 62 63 64 65 66 67
{
return this._deleteCommand ?? (this._deleteCommand = new DelegateCommand(
_ =>
{
this.People.RemoveAt(this.People.Count - 1);
this.DeleteCommand.RaiseCanExecuteChanged();
},
_ => this.People.Count > 0));
} } } }
コンストラクタ内でコレクションの要素を用意せず、AddCommand プロパティのコマンドによってコレクションの要素を ひとつずつ追加できるようにしています。また、DeleteCommand プロパティのコマンドによってコレクションの最後尾の要 素をひとつずつ削除できるようにしています。どちらのコマンドも、実行すると People コレクションの数が変更されるこ とで DeleteCommand プロパティの実行可能判別条件の結果に影響するため、RaiseCanExecuteChanged() メソッドを呼び 出してその変更を通知するようにしています。44 行目には、コレクションに要素を追加したときに Visual Studio の出力 ウィンドウにメッセージを表示するようにしています。
このサンプルコードの動作を確認するために次のような MainView ウィンドウを用意します。「追加」ボタンや「削除」ボ
タンで ListBox コントロールに表示するコレクションを追加/削除するようにしています。
コード 6.10:コレクションの要素を追加/削除するボタンを持つ UI MainView.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<Window x:Class="YKWpfIntroduction.Practices.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainView" Height="300" Width="300">
<DockPanel>
<Button DockPanel.Dock="Top" Content="追加" Command="{Binding AddCommand}" Margin="5" />
<Button DockPanel.Dock="Top" Content="削除" Command="{Binding DeleteCommand}" Margin="5" />
<ListBox ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FullName}" />
<TextBlock Text="{Binding Age, StringFormat=({0})}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>
このアプリケーションを実行してみましょう。すると、のように「追加」ボタンを押しても ListBox コントロールに追 加された要素が表示されません。しかし、出力ウィンドウを確認するとメッセージが表示されているため、MainViewModel ク
ラスの People コレクションには追加されているようです。なぜ ListBox コントロールに変更が反映されないかというと、
それは変更通知をおこなっていないからです。
(a) 「追加」ボタンを押しても表示されない (b) AddCommand は正常に処理されている 図 6.6:「追加」ボタンを押しても ListBox コントロールに反映されない
AddCommand プロパティによってコマンドが実行されたとき、People.Add() メソッドによって People コレクションに 子要素が 1 つ追加されます。このとき、People プロパティ自体に変化があったわけではないため People プロパティの set アクセサは実行されていません。つまり People プロパティの子要素が変更されたということを UI 側が認識できてい ないことになります。
では People.Add() メソッドを呼び出した後、"RaisePropertyChanged("People");" として強制的に People プロパ ティの変更通知を呼べばいいかというと、そうではありません。People プロパティのインスタンス自体には何の変化もなく、
あくまでも People プロパティの子要素が変化した、ということを通知しなければいけません。このように子要素が変化し たということを通知するには INotifyCollectionChanged インターフェースによる CollectionChanged イベントを発 行する必要があります。そして、これを実装しているコレクションとして ObservableCollection<T> クラスというものが 用 意 さ れ て い ま す 。 し た が っ て 、UI に デ ー タ バ イ ン デ ィ ン グ す る コ レ ク シ ョ ン デ ー タ は 、 特 に 理 由 が な い 限り ObservableCollection<T> クラスとするべきです。
MainViewModel クラスの People プロパティの型を List<Person> クラスから ObservableCollection<Person> ク ラスに変更しましょう。List<T> クラスと ObservableCollection<T> クラスは名前空間が異なるので、冒頭の using デ ィレクティブに変更があることに注意しましょう。
コード 6.11:ObservableCollection<T> クラスを用いたコレクションデータ MainViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
namespace YKWpfIntroduction.Practices.ViewModels {
using System.Collections.ObjectModel;
using YKWpfIntroduction.Practices.Models;
/// <summary>
/// MainView ウィンドウに対するデータコンテキストを表します。
/// </summary>
internal class MainViewModel : NotificationObject {
private ObservableCollection<Person> _people = new ObservableCollection<Person>();
/// <summary>
/// 人物情報のコレクションを取得します。
/// </summary>
public ObservableCollection<Person> People {
get { return this._people; }
private set { SetProperty(ref this._people, value); } }
20 21 22 23 24 25 26 27 48 49 50 51 52 53 66 67
private int _count;
private DelegateCommand _addCommand;
/// <summary>
/// 追加コマンドを取得します。
/// </summary>
public DelegateCommand AddCommand...
private DelegateCommand _deleteCommand;
/// <summary>
/// 削除コマンドを取得します。
/// </summary>
public DelegateCommand DeleteCommand...
} }
これで People コレクションの子要素が追加されたり削除されたりしたときに CollectionChanged イベントが発行さ れるため、UI 側が認識できるようになりました。実行してみると、ListBox コントロールに表示されるようになります。
(a) 「追加」ボタンを押すと追加される (b) 「削除」ボタンを押すと削除される 図 6.7:子要素の数が変更されたことを ListBox コントロールが認識している