既存のDependencyPropertyの変更を検知する
自前でDependencyPropertyを実装する場合は、Register時にPropertyMetadataを渡すことで、値の変更時にコールバックを呼び出すことが可能となります。
しかし既存のDependencyPropertyの場合はすでにRegisterされているため、変更を検知するには別のアプローチが必要となります。
今回は2つのアプローチを実装してみたいと思います。
なお実用上は、既存のコントロールには対応するChangedイベントが用意されているため(ActualWidthならSizeChangedなど)、DependencyPropertyの変更の検知が必要なケースはほとんどないと思います。
DependencyPropertyDescriptor
RectangleとTextBlockがあり、RectangleのActualWidthがTextBlockに表示される例を考えます。
FromPropertyでDependencyPropertyDescriptorを取得し、コールバックをAddValueChangedで登録しています。
var desc = DependencyPropertyDescriptor.FromProperty(Rectangle.ActualWidthProperty, typeof(Rectangle));
desc.AddValueChanged(rectangle, (s, e) => textblock.Text = rectangle.ActualHeight.ToString());
この方法の場合、RemoveValueChangedを呼び出さない限りコントロールの参照が持ち続けられるので、コントロールを動的に削除したりする時には注意が必要です。
DependencyObjectにBindingして検知
ターゲットのDependencyPropertyにBindingして変更を検知する仲介クラス、DependencyPropertyChangedを定義してみました。
仲介用のValueプロパティを持っており、値が変化したときに登録したコールバックを呼ぶようになっています。
またConditionalWeakTableにターゲットのコントロールと共に保持することにより、ターゲットと同じ寿命になるようにしてあります。
public class DependencyPropertyChanged<T> : DependencyObject { public T Value { get { return (T)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(T), typeof(DependencyPropertyChanged<T>), new PropertyMetadata(default(T), (obj, e) => (obj as DependencyPropertyChanged<T>).OnValueChanged())); static ConditionalWeakTable<DependencyObject, List<DependencyObject>> _instances = new ConditionalWeakTable<DependencyObject, List<DependencyObject>>(); Action<T> _changed; public DependencyPropertyChanged(DependencyObject target, string path, Action<T> changed) { _changed = changed; _instances.GetOrCreateValue(target).Add(this); BindingOperations.SetBinding(this, DependencyPropertyChanged<double>.ValueProperty, new Binding(path) { Source = target, Mode = BindingMode.OneWay }); } void OnValueChanged() => _changed(Value); }
new DependencyPropertyChanged<double>(rectangle, nameof(rectangle.ActualWidth), x => textboxWidth.Text = x.ToString());
この方法であれば、コントロールを動的に削除しても参照が残り続けることはなくなります。