データバインドをシンプルに


DataBindings

概要

設定クラス等をコントロールにデータバインドする場合について、
値が変更されたときに、現在値と比較して違っている場合だけ OK ボタンをクリック可能にしたい場合がある。
これを実現するには、通常、下記の手順で実装となる。

  1. 設定クラスを作成
  2. 設定クラスに INotifyPropertyChanged インターフェイスを実装
  3. 編集用コントロールを配置
  4. コントロールに設定クラスのインスタンスをデータバインド

が、INotifyPropertyChanged インターフェイスをいちいち実装するのが面倒。。

INotifyPropertyChanged インターフェイスは必要か

例えば、TextBox にデータバインドする場合、DataSourceUpdateMode を OnPropertyChanged にしても、プロパティの値が変更されるタイミングは、TextChanged イベントの後になるため、このイベントで値を比較しても正しい結果は得られない。

解決案

TextChanged イベントが発生したらデータソースを更新する処理を手動で入れてやる!

static void BindData(this Control control, string propertyName, object dataSource, string dataMember, Action onChanged = null)
{
    //control.DataBindings.Clear();
    var binding = control.DataBindings.Add(propertyName, dataSource, dataMember, true, DataSourceUpdateMode.Never);
    if (binding != null)
    {
        // デフォルトイベント発生時に手動でデータソースを更新
        // (DataSourceUpdateMode.OnPropertyChanged だと、TextBox.TextChanged イベント後にデータソースが更新されるため)
        var defaultEvent = control.GetType().GetDefaultEvent();
        if (defaultEvent != null)
        {
            defaultEvent.AddEventHandler(control, new EventHandler((sender, e) =>
            {
                binding.WriteValue();
                onChanged?.Invoke();
            }));
        }
        else binding.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
    }
}

値変更時のイベントは、コントロールによって TextChanged だったり ValueChanged だったりするため、DefaultEventAttribute で指定されているイベントを取得する。

static EventInfo GetDefaultEvent(this Type type) => type.GetEvent(type.GetAttribute<DefaultEventAttribute>()?.Name ?? string.Empty);

static TAttribute GetAttribute<TAttribute>(this ICustomAttributeProvider element, bool inherit = true) where TAttribute : Attribute
{
    if (element == null) throw new ArgumentNullException(nameof(element));
    return element.GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
}

各コントロールと Default 属性値

Control DefaultProperty DefaultEvent DefaultBindingProperty
TextBox Text TextChanged Text
CheckBox Checked CheckedChanged CheckState
RadioButton Checked CheckedChanged Checked
NumericUpDown Value ValueChanged Value
DateTimePicker Value ValueChanged Value
DataGridView Text CellContentClick -