OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

WPF DataContextを設定した直後にBinding先で値を参照する

突然ですが、以下の出力結果はどうなるでしょう?

//適当なボタン  
var button = new Button() { Width = 120, Height = 30 };  

//IsEnabledに"Used"というパスをバインド  
button.SetBinding(Button.IsEnabledProperty, new Binding("Used"));  

//Usedプロパティを持つ匿名クラスをコンテクストに  
button.DataContext = new { Used = false };    

Console.WriteLine(button.IsEnabled);    // <- true? false?  

ButtonのIsEnabledプロパティに対して、Usedという名前のプロパティをバインドして
DataContextにUsedプロパティ(=false)を持つオブジェクトを代入する。

処理的にFalseが出力されることを期待しているのはわかると思うが、
実際には、Trueが出力される。
参照時点でバインドソース(Context)の値がターゲット(Button)に反映されていないためだ。

ためしに、Dispatcher.BeginInvokeとかで非同期的に遅延させてから参照した値を
出力するとちゃんとFalseが出力されるし、
実際このボタンを画面に配置してやれば、ちゃんと無効状態になるのがわかる。

では、バインドした直後に同期的に値を取りたい場合はどうするか。
感覚的には、参照する前に
button.GetBindingExpression(Button.IsEnabledProperty).UpdateTarget();
ってやってやれば反映されそうなものだけど、これもうまくいかないんだよな。

とりあえず単純な解決策としては、以下のように
SetBindingとDataContext設定の順番を逆にしてやれば
即座に反映されるようになった。

var button = new Button() { Width = 120, Height = 30 };  

button.DataContext = new { Used = false };  

button.SetBinding(Button.IsEnabledProperty, new Binding("Used"));  

Console.WriteLine(button.IsEnabled);    // <- false!  

てことはつまり、DataContextを入れたタイミングではBindingが同期的に反映されないけど、
SetBindingしたあとは同期的に即時反映されるってことなのかな。

WPFはいろいろアクロバティックなことをやっているから
こういうちょっとした内部仕様がわからずつまづくことがままあるんよなー。