レイアウトばっかりやってきたら飽きてきた。
そろそろ、バインディングを軽く入門してみたいと思う。
バインディングは、わざわざプログラムで見た目と実際の値の同期をとらなくてもへっちゃらだぜ!っていう仕組みだって理解でOK?
例によって、コードでバインディングをやってみようと思う。
その前にバインディングについてちょろっと。
バインディングは、明示的にSourceを指定しないとDataContextに入ってるものとバインドしちゃうよ!以上。
ってことでさくっとC#で書いて見た。
まず、バインディングのソースとなるクラス。これは、値を1つ増やしたり減らしたり出来るカウンターです。
このカウンターには、カウンターの値のほかに、何のカウンターなのかを入力するDescriptionプロパティもあります。
class Counter
{
public int Value { get; private set; }
public string Description { get; set; }
public void Incr()
{
Value++;
}
public void Decr()
{
Value--;
}
}
珍しいのは、自動プロパティを使ってるくらいかな?
このCounterを、画面あたりにバインドしてみようと思う。
C#で画面からバインドまで全部組もうと思ったけど、書いていくうちにめんどくさくなってきた…
なので!!!絵面はXAMLで定義!!
さしあたってこんな感じ?
<Window x:Class="CodeDataBinding1.DataBindingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingWindow" SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="値:" Grid.Row="0" Grid.Column="0" />
<Label Name="valueLabel" Grid.Row="0" Grid.Column="1" Content="ここに値がきますよと" />
<Label Content="概要:" Grid.Row="1" Grid.Column="0" />
<Label Name="descriptionLabel" Grid.Row ="1" Grid.Column="1" Content="ここに概要がきますよと" />
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="incrButton" Content="インクリメント" Margin="2" Click="incrButton_Click" />
<Button Name="decrButton" Content="デクリメント" Margin="2" Click="decrButton_Click" />
<Button Name="dumpButton" Content="Dump" Margin="2" Click="dumpButton_Click" />
</StackPanel>
</Grid>
</Window>
C#のコード側に、さっきのCounterクラスを持たせてボタンのイベントにさくっとコードを書く。
public partial class DataBindingWindow : Window
{
private Counter counter;
public DataBindingWindow()
{
InitializeComponent();
// カウンタを作る
counter = new Counter();
counter.Description = "これはサンプル用のカウンタです";
}
private void incrButton_Click(object sender, RoutedEventArgs e)
{
counter.Incr();
}
private void decrButton_Click(object sender, RoutedEventArgs e)
{
counter.Decr();
}
private void dumpButton_Click(object sender, RoutedEventArgs e)
{
// カウンタの中身を確認
MessageBox.Show(string.Format("{0}: {1}", counter.Value, counter.Description));
}
}
この実行結果は自明なので省略。
ボタン押したらそれなりに動くだけ。
ってことでバインドをしてみたいと思います。
バインドするには、DataContextにバインド元のクラスを突っ込んでバインドまわりの設定をしてやればOK。
具体的には、Bindingオブジェクトを作って、そこにPathを設定。Pathは今回の場合はDataContextにCounterを入れる予定なので、ValueやDescriptionになる。
その後、BindingOperations.SetBindingで、どのコントロールのどのプロパティにバインドするかを決めてあげる。
コードに書くと↓のような感じ。赤字の部分が該当箇所です。
public partial class DataBindingWindow : Window
{
private Counter counter;
public DataBindingWindow()
{
InitializeComponent();
// カウンタを作る
counter = new Counter();
counter.Description = "これはサンプル用のカウンタです";
InitializeBinding();
}
private void InitializeBinding()
{
Binding valueBinding = new Binding("Value");
BindingOperations.SetBinding(
valueLabel, Label.ContentProperty, valueBinding);
Binding descriptionBinding = new Binding("Description");
BindingOperations.SetBinding(
descriptionLabel, Label.ContentProperty, descriptionBinding);
// カウンタをDataContextに!
this.DataContext = counter;
}
private void incrButton_Click(object sender, RoutedEventArgs e)
{
counter.Incr();
}
private void decrButton_Click(object sender, RoutedEventArgs e)
{
counter.Decr();
}
private void dumpButton_Click(object sender, RoutedEventArgs e)
{
// カウンタの中身を確認
MessageBox.Show(string.Format("{0}: {1}", counter.Value, counter.Description));
}
}
これを実行すると、↓みたいに起動直後はそれっぽい。
でも、インクリメントボタンやデクリメントボタンを押しても値が変わらない。
これは、現状だと値が変化したことを察知する方法がないからです。
とりあえず場当たり的な対処だと、明示的にラベルの値をカウンタから再度とってくるように支持する方法があります。
public partial class DataBindingWindow : Window
{
private Counter counter;
public DataBindingWindow()
{
InitializeComponent();
// カウンタを作る
counter = new Counter();
counter.Description = "これはサンプル用のカウンタです";
InitializeBinding();
}
private void InitializeBinding()
{
Binding valueBinding = new Binding("Value");
BindingOperations.SetBinding(
valueLabel, Label.ContentProperty, valueBinding);
Binding descriptionBinding = new Binding("Description");
BindingOperations.SetBinding(
descriptionLabel, Label.ContentProperty, descriptionBinding);
this.DataContext = counter;
}
private void incrButton_Click(object sender, RoutedEventArgs e)
{
counter.Incr();
// 明示的に更新ですよ
valueLabel.GetBindingExpression(Label.ContentProperty).UpdateTarget();
}
private void decrButton_Click(object sender, RoutedEventArgs e)
{
counter.Decr();
// 明示的に更新ですよ
valueLabel.GetBindingExpression(Label.ContentProperty).UpdateTarget();
}
private void dumpButton_Click(object sender, RoutedEventArgs e)
{
// カウンタの中身を確認
MessageBox.Show(string.Format("{0}: {1}", counter.Value, counter.Description));
}
}
これで、晴れてインクリメントボタンをデクリメントボタンを押したときに反映されるようになった。
これは、数が増えたときに大変だ!ということでプロパティが変更したときに自動的にバインド先の値を書き換える仕組みもあります。
といっても、その仕組みに合致するようにCounterをかきかえないといけないんですが…
書き換え内容は、System.ComponentModel.INotifyPropertyChangedインターフェイスを実装して適切にPropertyChangedイベントを発生させてあげること。
ってことでCounterクラスを書き換えると↓みたいになる。
class Counter : INotifyPropertyChanged
{
private int value;
public int Value
{
get { return value; }
set
{
this.value = value;
OnPropertyChanged("Value");
}
}
private string description;
public string Description
{
get { return description; }
set
{
this.description = value;
OnPropertyChanged("Description");
}
}
public void Incr()
{
Value++;
}
public void Decr()
{
Value--;
}
#region INotifyPropertyChanged メンバ
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
まぁなんというか、自動プロパティってこういう時は使えないのね…残念!!
[NotifyPropertyChanged]
public int Value { get; set; }
とかみたいに、何かつければ自動生成してほしかった!!
コードスニペット作ってお茶を濁すかぁ。
こうすると、インクリメントボタンとデクリメントボタンから明示的に更新を行わなくても値が書き換わってくれる。
ビバ バインディング