読者です 読者をやめる 読者になる 読者になる

DataContextの変更を検知する

たまーに、DataContextが変わったかどうかをチェックしたいことがあるのですが、Silverlight には DataContextChanged のイベントが無いのです。そもそも、DependencyProperty の変更イベントがとれないのがちょっと悲しいです。


で、DataContext の変更を検知するクラスを作りました。

public class DataContextChangedNotifier{
  
  public event EventHandler<DataContextChangedEventArgs> DataContextChanged;
  
  public DataContextChangedNotifier(FrameworkElement element){
    element.SetBinding(DataContextProperty, new Binding());
    element.SetValue(InstanceProperty, this);
  }
  
  public static readonly DependencyProperty InstanceProperty =
    DependencyProperty.RegisterAttached("Instance",
      typeof(DataContextChangedNotifier),
      typeof(DataContextChangedNotifier), null); 
  
  public static readonly DependencyProperty DataContextProperty =
  DependencyProperty.Register("DataContext",
                typeof(Object),
                typeof(FrameworkElement),
                new PropertyMetadata(OnDataContextChanged));

  private static void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
    var element = sender as FrameworkElement;
    if(element != null){
      var notifier = element.GetValue(InstanceProperty) as DataContextChangedNotifier;
      if(notifier != null && notifier.DataContextChanged != null){
        notifier.DataContextChanged(notifier,new DataContextChangedEventArgs(element,e.NewValue));
      }
    }
  }
}

public class DataContextChangedEventArgs : EventArgs{
  public DataContextChangedEventArgs(FrameworkElement source,object dataContext){
    this.Source = source;
    this.DataContext = dataContext;
  }

  public FrameworkElement Source { get; private set; }
  public object DataContext { get; private set; }
}

仕組みは簡単で、変更を受け取りたい FrameworkElement に Binding を設定して、バインド先を DataContextChangedNotifier にしています。この時、Binding にパスなどを設定していないので、FrameworkElement の DataContext が変更されたら、その値が DataContextChangedNotifier の DataContextProperty に設定されるって動きになります。

使い方は以下のような感じ。

public partial class MainPage : UserControl {
  public MainPage() {
    InitializeComponent();
    
    var notifier = new DataContextChangedNotifier(this);
    notifier.DataContextChanged += notifier_DataContextChanged;
    
    this.DataContext = new Object();
  }

  void notifier_DataContextChanged(object sender, DataContextChangedEventArgs e) {
    MessageBox.Show("DataContext Changd!");
  }
}

どんな場合でもちゃんといけるかは見てませんが(ItemsControlのItemsSourceとか)、まぁ多くの場合はいけそうな気がしてます。