TOC

This article is currently in the process of being translated into Japanese (~92% done).

Data binding:

Responding to changes

これまでのチュートリアルでは、ほとんどUI要素と既存クラスのバインディングを扱いましたが、実際のアプリケーションでは明らかにデータオブジェクトとのバインディングを行います。これは簡単ですが、これを始めたら、あなたはきっとがっかりするでしょう。変更が、以前のサンプルのように自動的に反映されないのです。この記事では、これを起こすために少しの余分の作業が必要なことを学びます。しかし幸運なことに、WPFではこれが極めて簡単にできます。

データソースの変更に対する反応

データソースの変更に対応する時に、処理する場合としない場合の2つの異なるシナリオが存在します。リストアイテムの変更と、それぞれのデータに結びついたプロパティの変更です。それらをどの様に処理するかは、あなたがどうしたいか、あなたが何を目指しているかに依ります。しかしWPFは2つの大変簡単な解決法を備えています。ObservableCollectionINotifyPropertyChanged インターフェースです。

次のサンプルは、何故これら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を使うことを要求しているだけです。


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!