前回は、普通にBindingを何も考えずに使ったときのパターンについて書いた。
今回は、TextBoxにバインドしたときに考えなきゃいけないことについて書いてみる。
とりあえず下準備として、WpfBinding2という名前でプロジェクトを作る。
前回と同様にPersonクラスを作って、Window1.xaml.csのコンストラクタでDataContextに適当な値を突っ込む。
Person.cs
namespace WpfBinding2
{
public class Person
{
public string Name { get; set; }
}
}
Window1.xaml.cs
using System.Windows;
namespace WpfBinding2
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new Person { Name = "田中 太郎" };
}
}
}
画面は、StackPanelにTextBoxとButtonを置く。TextBoxのTextプロパティには、前回やったのと同じ方法でPersonのNameプロパティをバインドしておく。
ボタンには、クリックイベントを割り当てておく。イベントの中身はまだ空のままです。
<Window x:Class="WpfBinding2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBox Name="_textBox" Text="{Binding Path=Name}"/>
<Button Content="Test" Click="Button_Click" />
</StackPanel>
</Window>
この状態で実行すると、以下のような感じになる。
ここまでは、OKです。
ここで動作を確認するために、TestボタンのClickイベントを下のようにします。
private void Button_Click(object sender, RoutedEventArgs e)
{
var p = DataContext as Person;
Debug.WriteLine("Name: " + p.Name);
}
何のことは無い、DataContext内のPersonオブジェクトのNameプロパティを表示してるだけ。
実行した直後に押すと下のように表示される。
TextBoxの中を書き換えてボタンを押してみる。
ばっちり書き換わってる!バインドって素敵。
ここで、クリックイベントの中身を下のように書き換えてみる。
private void Button_Click(object sender, RoutedEventArgs e)
{
var p = DataContext as Person;
p.Name = "田中 一郎";
Debug.WriteLine("Name: " + p.Name);
}
この状態で実行してボタンを押してみる。一応期待する動作は、TextBoxの中身が田中 一郎に書き換わるっていうのだよね。
ということで実行!
実行直後
クリック直後
クリック直後の出力
ということで、うまいこと行ってない。
よく考えれば当然で、BindingはPersonのNameプロパティが更新されたことなんて知ったこっちゃない!
なので、下の図のようにTextBoxからPersonのNameプロパティにしか変更の反映に対応してないことになる。
(因みにうちにはドローイングツールが無いので上の図もXAMLで書いてみた)
まぁ、対応してないというのは言いすぎかな。
要はBindingが変更あったのを感知できればいい。
というわけで、Bindingが変更を知るための仕組みが用意されてる。
ざっと思いつくだけで下の3つがある。
- .NET Framework1.0位からあったプロパティ名Changedという名前のイベントを定義する方法
- INotifyPropertyChangedインターフェースを実装して適切にイベントを発行する方法
- DependencyPropertyにしてしまう方法
とりあえず、2の方法が推奨のような気がする。1の方法は、リフレクションしまくりだし、3の方法はインスタンスを生成したスレッドに縛られるという制約があったりする。
ということで、PersonクラスをINotifyPropertyChangedを実装するように書き換えてみる。
using System.ComponentModel;
namespace WpfBinding2
{
public class Person : INotifyPropertyChanged
{
// さらば!自動プロパティ!
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
#region INotifyPropertyChanged メンバ
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
これが色んな所で言われている、自動プロパティあっても使えね~!!って奴です。
ここらへんまで自動プロパティでどうにかしてほしかったと思う今日この頃。
さて、愚痴は置いといて実行してみよう!
実行直後
ぽちっとな
ちゃんと変わった~~!
めでたしめでたし。