This article is currently in the process of being translated into Japanese (~92% done).
Responding to changes
これまでのチュートリアルでは、ほとんどUI要素と既存クラスのバインディングを扱いましたが、実際のアプリケーションでは明らかにデータオブジェクトとのバインディングを行います。これは簡単ですが、これを始めたら、あなたはきっとがっかりするでしょう。変更が、以前のサンプルのように自動的に反映されないのです。この記事では、これを起こすために少しの余分の作業が必要なことを学びます。しかし幸運なことに、WPFではこれが極めて簡単にできます。
データソースの変更に対する反応
データソースの変更に対応する時に、処理する場合としない場合の2つの異なるシナリオが存在します。リストアイテムの変更と、それぞれのデータに結びついたプロパティの変更です。それらをどの様に処理するかは、あなたがどうしたいか、あなたが何を目指しているかに依ります。しかしWPFは2つの大変簡単な解決法を備えています。ObservableCollection と INotifyPropertyChanged インターフェースです。
次のサンプルは、何故これら2つが必要か示しています。
<Window x:Class="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="150" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataBinding
{
public partial class ChangeNotificationSample : Window
{
private List<User> users = new List<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User
{
public string Name { get; set; }
}
}
サンプルを走らせて、リストに何かを追加するかまたはユーザーの名前を変更しても、UIが更新されないことを確認してください。このサンプルはユーザーの名前を保持するユーザークラスと、それらを表示するListBoxとリストとコンテンツを操作するいくつかのボタンからなる、かなり簡単なクラスです。リストのアイテムソースには、ウインドウのコンストラクタで生成したいくつかのユーザーのクイックリストが割り当てられています。問題点は、ボタンが機能していないように見える点です。簡単な2つのステップでこれを修正しましょう。
リストデータソース変更の反映
最初のステップはUIがリストのソース(ItemsSource)のユーザーの追加や削除のような変更に反応するようにすることです。必要なのは、コンテンツの変更をあらゆる通知先に通知するリストです。そして幸運にもWPFにはまさにこれを行う種類のリストを提供しています。ObservableCollection といい、少しの違いはありますが、通常の List<T> と同じ様に使えます。
下に示した最後の例では、単純に List<User> を ObservableCollection<User> に置き換えました。それだけです!これで追加と削除ボタンは動作します。しかし、"Change name" ボタンは動作しません。何故なら、結びついたデータオブジェクト自身が変更されていて、ソースリストは変更されないからです。2番目のステップでこのシナリオに対応します
データオブジェクトの変更の反映
第二のステップは、我々のカスタムユーザークラスに INotifyPropertyChanged インターフェースクラスを実装することです。これによって、ユーザーオブジェクトがUI層の変更をそのプロパティに通知出来るようになります。これは先に行った、Listの種類を変更するよりは多少面倒ですが、自動でアップデートを行うための最も簡単な方法の一つです。
最後の完全なサンプル
上で説明した2つの変更で、データソースの変更を反映するサンプルが出来ました。それは次の様なものです。
<Window x:Class="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="135" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfTutorialSamples.DataBinding
{
public partial class ChangeNotificationSample : Window
{
private ObservableCollection<User> users = new ObservableCollection<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User : INotifyPropertyChanged
{
private string name;
public string Name {
get { return this.name; }
set
{
if(this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
まとめ
ご覧のように、INotifyPropertyChanged の実装はとても簡単です。しかし、これはクラスへの少しの余分のコードの追加とプロパティへの少しのロジックの追加が生じます。これは、クラスにバインドを行い、変更を直接UIに反映するために支払う対価です。明らかにあなたは、バインドを行うプロパティのセッターで NotifyPropertyChanged を呼ぶ必要があるだけです。後は何もする必要はありません。
一方、ObservableCollection は大変簡単に対処します。ソースリストの変更をバインドした送り先に反映したいときは、単純にこの指定の型のListを使うことを要求しているだけです。