MVVMのバインド処理をインターセプトする

MVVMのフレームワークはいくつかある。代表的なのは、PrismだがCodePlexを探すだけでもいろいろある。
面白いものにはこんなのもあるIntroducing MicroModels 。このフレームワークではICustomTypeDescriptor インターフェイスを利用して動的にバインドする仕組みを構築しているようである。このICustomTypeDescriptorのテクニックを今回WPFで用いられているが、WindowsFormsのデータバインドでも利用できるテクニックで、以前からバインド処理のインターセプトとして利用していた。

サンプルコード

バインド処理される場合に以下のSetValueとGetValueが呼び出される。ここに様々な処理を付与することができる。この方式の良いところはこの仕組みが動作するのはTypeDescriptor経由で取得した場合のみで通常のタイプセーフな処理では動作しないため、性能的な影響はない。また、WPFでのバインドがTypeDescriptor経由でありWPFのバインド処理への性能影響も少ない。使い方は継承するだけ。

public class ViewModelItem : INotifyPropertyChanged, ICustomTypeDescriptor
{
    protected virtual void SetValue(PropertyInfo prop, object value)
    {
        prop.SetValue(this, value, null);
        OnPropertyChanged(prop.Name);
    }
    protected virtual object GetValue(PropertyInfo prop)
    {
        object value = prop.GetValue(this, null);
        return value;
    }

    public ViewModelItem()
    {
    }
    #region INotifyPropertyChanged メンバー
    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
    #endregion

    #region ICustomTypeDescriptor メンバ

    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return new AttributeCollection(null);
    }
    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }
    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }
    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return new EventDescriptorCollection(null);
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return new EventDescriptorCollection(null);
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        PropertyDescriptorCollection props = new PropertyDescriptorCollection(null);
        foreach (PropertyInfo prop in this.GetType().GetProperties())
        {
            props.Add(new ViewModelItemPropertyDescriptor(prop));
        }
        return props;
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }
    [DebuggerNonUserCode]
    internal class ViewModelItemPropertyDescriptor : PropertyDescriptor
    {
        private PropertyInfo _originalPropertyInfo;

        public ViewModelItemPropertyDescriptor(PropertyInfo prop)
            : base(prop.Name,
        (Attribute[])prop.GetCustomAttributes(typeof(Attribute), true))
        {
            _originalPropertyInfo = prop;
        }
        public override bool Equals(object obj)
        {
            ViewModelItemPropertyDescriptor other = obj as ViewModelItemPropertyDescriptor;
            return other != null && other._originalPropertyInfo.Equals(_originalPropertyInfo);
        }
        public override int GetHashCode() { return _originalPropertyInfo.GetHashCode(); }
        public override bool IsReadOnly { get { return !_originalPropertyInfo.CanWrite; } }
        public override void ResetValue(object component) { }
        public override bool CanResetValue(object component) { return false; }
        public override bool ShouldSerializeValue(object component) { return false; }
        public override Type ComponentType { get { return _originalPropertyInfo.DeclaringType; } }
        public override Type PropertyType { get { return _originalPropertyInfo.PropertyType; } }
        public override object GetValue(object component)
        {
            return ((ViewModelItem)component).GetValue(this._originalPropertyInfo);
        }
        public override void SetValue(object component, object value)
        {
            ((ViewModelItem)component).SetValue(this._originalPropertyInfo, value);
            OnValueChanged(component, EventArgs.Empty);
        }
    }
    #endregion
}