BindingList和ObservableCollection很好地保持数据更新,并在其中一个对象发生更改时通知。但是,当通知房产即将改变时,我认为这些选择并不是很好。
我现在需要做的就是解决这个问题(我警告这不是优雅的AT ALL),就是在列表的类型对象上实现INotifyPropertyChanging,然后将其绑定到包含列表PropertyChanging事件的对象,或者其他东西如下:
// this object will be the type of the BindingList
public class SomeObject : INotifyPropertyChanging, INotifyPropertyChanged
{
private int _intProperty = 0;
private string _strProperty = String.Empty;
public int IntProperty
{
get { return this._intProperty; }
set
{
if (this._intProperty != value)
{
NotifyPropertyChanging("IntProperty");
this._intProperty = value;
NotifyPropertyChanged("IntProperty");
}
}
}
public string StrProperty
{
get { return this._strProperty; }
set
{
if (this._strProperty != value)
{
NotifyPropertyChanging("StrProperty");
this._strProperty = value;
NotifyPropertyChanged("StrProperty");
}
}
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObjectThatHoldsTheList : INotifyPropertyChanging, INotifyPropertyChanged
{
public BindingList<SomeObject> BindingList { get; set; }
public ObjectThatHoldsTheList()
{
this.BindingList = new BindingList<SomeObject>();
}
// this helps notifie Changing and Changed on Add
private void AddItem(SomeObject someObject)
{
// this will tie the PropertyChanging and PropertyChanged events of SomeObject to this object
// so it gets notifies because the BindingList does not notify PropertyCHANGING
someObject.PropertyChanging += new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged += new PropertyChangedEventHandler(someObject_PropertyChanged);
this.NotifyPropertyChanging("BindingList");
this.BindingList.Add(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this helps notifies Changing and Changed on Delete
private void DeleteItem(SomeObject someObject)
{
if (this.BindingList.IndexOf(someObject) > 0)
{
// this unlinks the handlers so the garbage collector can clear the objects
someObject.PropertyChanging -= new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged -= new PropertyChangedEventHandler(someObject_PropertyChanged);
}
this.NotifyPropertyChanging("BindingList");
this.BindingList.Remove(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this notifies an item in the list is about to change
void someObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
NotifyPropertyChanging("BindingList." + e.PropertyName);
}
// this notifies an item in the list has changed
void someObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("BindingList." + e.PropertyName);
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
很抱歉,我知道这是很多代码,这让我回到了我的主要观点,实现这一点需要很多代码。所以我的问题是,有没有人知道一个更好,更短,更优雅的解决方案?
感谢您的时间和建议。
答案 0 :(得分:1)
您可以创建一个实现ICustomTypeDescriptor的包装类。这个包装器还将实现必要的接口(例如INotifyPropertyChanging),拦截属性读取/写入底层对象,并且您将能够调用由包装器实现的NotifyPropertyChanging()和NotifyPropertyChanged()方法。数据使用者将使用与原始对象相同的包装对象。
但如果您不是一位经验丰富的开发人员,那么实现这样的包装并不容易。
这是一个可能的,尚未完成这种包装器的实现。它已经支持INotifyPropertyChanged,并且很容易理解如何实现INotifyPropertyChanging。
public class Wrapper : ICustomTypeDescriptor, INotifyPropertyChanged, IEditableObject, IChangeTracking
{
private bool _isChanged;
public object DataSource { get; set; }
public Wrapper(object dataSource)
{
if (dataSource == null)
throw new ArgumentNullException("dataSource");
DataSource = dataSource;
}
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes()
{
return new AttributeCollection(
DataSource.GetType()
.GetCustomAttributes(true)
.OfType<Attribute>()
.ToArray());
}
public string GetClassName()
{
return DataSource.GetType().Name;
}
public string GetComponentName()
{
return DataSource.ToString();
}
public TypeConverter GetConverter()
{
return new TypeConverter();
}
public EventDescriptor GetDefaultEvent()
{
return null;
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return Activator.CreateInstance(editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(DataSource, attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(DataSource);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
private IEnumerable<PropertyDescriptor> _Properties;
public IEnumerable<PropertyDescriptor> Properties
{
get
{
if (_Properties == null)
_Properties = TypeDescriptor.GetProperties(DataSource)
.Cast<PropertyDescriptor>()
.Select(pd => new WrapperPropertyDescriptor(pd) as PropertyDescriptor)
.ToList();
return _Properties;
}
}
public PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(Properties.ToArray());
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion ICustomTypeDescriptor
#region ToString, Equals, GetHashCode
public override string ToString()
{
return DataSource.ToString();
}
public override bool Equals(object obj)
{
var wrapper = obj as Wrapper;
if (wrapper == null)
return base.Equals(obj);
else
return DataSource.Equals(wrapper.DataSource);
}
public override int GetHashCode()
{
return DataSource.GetHashCode();
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
_isChanged = true;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public IDictionary<string, object> MakeDump()
{
var result = new Dictionary<String, object>();
foreach (var item in Properties)
result[item.Name] = item.GetValue(this);
return result;
}
#region IEditableObject Members
private IDictionary<string, object> LastDump;
public void BeginEdit()
{
LastDump = MakeDump();
}
public void CancelEdit()
{
if (LastDump != null)
{
foreach (var item in Properties)
item.SetValue(this, LastDump[item.Name]);
_isChanged = false;
}
}
public void EndEdit()
{
AcceptChanges();
}
#endregion IEditableObject
#region IChangeTracking
public void AcceptChanges()
{
LastDump = null;
_isChanged = false;
}
public bool IsChanged
{
get { return _isChanged; }
}
#endregion IChangeTracking
}
public class WrapperPropertyDescriptor : PropertyDescriptor
{
private Wrapper _wrapper;
private readonly PropertyDescriptor SourceDescriptor;
public WrapperPropertyDescriptor(PropertyDescriptor sourceDescriptor) :
base(sourceDescriptor)
{
if (sourceDescriptor == null)
throw new ArgumentNullException("sourceDescriptor");
SourceDescriptor = sourceDescriptor;
}
public override Type ComponentType
{
get
{
return SourceDescriptor.ComponentType;
}
}
public override bool IsReadOnly
{
get
{
return SourceDescriptor.IsReadOnly;
}
}
public override Type PropertyType
{
get
{
return SourceDescriptor.PropertyType;
}
}
public override object GetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var value = SourceDescriptor.GetValue(wrapper.DataSource);
if (value == null)
return value;
var type = value.GetType();
// If value is user class or structure it should
// be wrapped before return.
if (type.Assembly != typeof(String).Assembly)
{
if (typeof(IEnumerable).IsAssignableFrom(type))
throw new NotImplementedException("Here we should construct and return wrapper for collection");
if (_wrapper == null)
_wrapper = new Wrapper(value);
else
_wrapper.DataSource = value;
return _wrapper;
}
return value;
}
public override void SetValue(object component, object value)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var actualValue = value;
var valueWrapper = value as Wrapper;
if (valueWrapper != null)
actualValue = valueWrapper.DataSource;
// Make dump of data source's previous values
var dump = wrapper.MakeDump();
SourceDescriptor.SetValue(wrapper.DataSource, actualValue);
foreach (var item in wrapper.Properties)
{
var itemValue = item.GetValue(wrapper);
if (!itemValue.Equals(dump[item.Name]))
wrapper.OnPropertyChanged(item.Name);
}
}
public override void ResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
SourceDescriptor.ResetValue(wrapper.DataSource);
}
public override bool ShouldSerializeValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.ShouldSerializeValue(wrapper.DataSource);
}
public override bool CanResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.CanResetValue(wrapper.DataSource);
}
}
同样,这不是一个完整的版本,但它已经可以在简单的场景中使用。可能的用法如下所示:
IList<Customer> customers = CustomerRepository.GetAllCustomers();
IList<Wrapper> wrappedCustomers = customers.Select(c => new Wrapper(c)).ToList();
/* If you don't like LINQ in the line above you can use foreach to transform
list of Customer object to a list of Wrapper<Customer> objects */
comboBoxCustomers.DataSource = wrappedCustomers;
// or
dataGridViewCustomers.DataSource = wrappedCustomers;
因此,只需一行简单的代码就可以获得支持INotifyPropertyChanged,IEditableObject,IChangeTracking接口的对象集合!
祝你好运!答案 1 :(得分:0)
这是跨领域关注的典型例子,它呼吁采用AOP方法。 Aspect Oriented Programming是一个扩展经典OOP的范例,可以解决“我希望记录此对象上的所有方法调用”等问题。
在.NET中有几种方法可以做到这一点,这是大多数方法的一个很好的列表:
http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx
列出的方法之一是PostSharp,IL重写器,它让你很容易做AOP。下面是使用此工具实现INotifyPropertyChanged的示例(我认为另一个示例是PostSharp):
http://thetreeknowseverything.net/2009/01/21/auto-implement-inotifypropertychanged-with-aspects/