That's what I got in the end.
public class VmList<TModel, TViewModel> : IList<TViewModel>, IEnumerator<TViewModel>, INotifyCollectionChanged, INotifyPropertyChanged where TModel : ModelBase where TViewModel : ViewModelBase, new() { IList<TModel> _list; IEnumerator<TModel> _enumerator; bool _modelListIsCollectionNotifier; public event NotifyCollectionChangedEventHandler CollectionChanged; public event PropertyChangedEventHandler PropertyChanged; public VmList(IList<TModel> list) { _list = list; { var notifier = _list as INotifyCollectionChanged; if (notifier != null) { _modelListIsCollectionNotifier = true; notifier.CollectionChanged += Notifier_CollectionChanged; } } { var notifier = _list as INotifyPropertyChanged; if (notifier != null) notifier.PropertyChanged += Notifier_PropertyChanged; } _enumerator = _list.GetEnumerator(); } private void Notifier_PropertyChanged(object sender, PropertyChangedEventArgs e) { PropertyChanged?.Invoke(this, e); } void Notifier_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } public TViewModel this[int index] { get { return new TViewModel { Model = _list[index] }; } set { _list[index] = (TModel)value.Model; if(!_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, index)); } } public int Count { get { return _list.Count; } } public TViewModel Current { get { return new TViewModel { Model = _enumerator.Current }; } } public bool IsReadOnly { get { return _list.IsReadOnly; } } object IEnumerator.Current { get { return Current; } } public void Add(TViewModel item) { _list.Add((TModel)item.Model); if (!_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } public void Clear() { _list.Clear(); if (!_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public bool Contains(TViewModel item) { return _list.Contains((TModel)item.Model); } public void CopyTo(TViewModel[] array, int arrayIndex) { throw new NotImplementedException(); } public void Dispose() { _enumerator.Dispose(); } public IEnumerator<TViewModel> GetEnumerator() { return this; } public int IndexOf(TViewModel item) { return _list.IndexOf((TModel)item.Model); } public void Insert(int index, TViewModel item) { _list.Insert(index, (TModel)item.Model); if (!_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } public bool MoveNext() { return _enumerator.MoveNext(); } public bool Remove(TViewModel item) { var res = _list.Remove((TModel)item.Model); if (res && !_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); return res; } public void RemoveAt(int index) { _list.RemoveAt(index); if (!_modelListIsCollectionNotifier) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index)); } public void Reset() { _enumerator.Reset(); } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } }
Sorry for the lack of comments in the code. Something like this happens. As you can see, this is a universal class dependent on two basic types: ModelBase
and ViewModelBase
. The entire collection is not duplicated from the model. When I request an element from VmList'a
, a class takes the corresponding element from the collection of the ModelBase
type and makes a new element of the ViewModelBase
type from it.
Here is the ModelBase
listing. Do not pay attention that it is empty, its task is not in logic, but in presence. That is, it is necessary to clearly distinguish between the model and the twist model. In the future, I will supply it with serialization and anything else, but this is not required to solve the current problem.
[DataContract] public class ModelBase: MvvmBase { }
And here is a listing of ViewModelBase
.
public class ViewModelBase: MvvmBase { bool _initialized; ModelBase _model; Lookup<string, string> _relatives; public ViewModelBase() { } public ViewModelBase(ModelBase model) { Model = model; } void Initialize() { DependentAttribute attr; var tmpRel = new List<KeyValuePair<string, string>>(); foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(this)) { attr = (DependentAttribute)pd.Attributes[typeof(DependentAttribute)]; if (attr != null) tmpRel.Add(new KeyValuePair<string, string>(attr.PropertyName, pd.Name)); else { var modelProp = TypeDescriptor.GetProperties(Model)[pd.Name]; if (modelProp != null) tmpRel.Add(new KeyValuePair<string, string>(modelProp.Name, pd.Name)); } } _relatives = (Lookup<string, string>)tmpRel.ToLookup(kv => kv.Key, kv => kv.Value); Model.PropertyChanged += Model_PropertyChanged; Initialized = Model != null; } protected virtual void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) { foreach(var propName in _relatives[e.PropertyName]) OnPropertyChanged(propName); } internal protected ModelBase Model { get { return _model; } set { _model = value; Initialize(); } } public bool Initialized { get { return _initialized; } set { _initialized = value; } } } public class ViewModelBase<TModel> : ViewModelBase where TModel: ModelBase { public ViewModelBase() { } public ViewModelBase(TModel model) : base(model) { } protected internal new TModel Model { get { return (TModel)base.Model; } set { base.Model = value; } } }
As you can see there are 2 classes. General and typed. There is also a type of DependentAttribute
.
[AttributeUsage(AttributeTargets.Property,AllowMultiple = true, Inherited = true)] public sealed class DependentAttribute: Attribute { public string PropertyName { get; set; } public DependentAttribute(string propertyName) { PropertyName = propertyName; } }
Something like this happens. When a PropertyChanged
event is triggered by a model, I twist the model to see if it has the same property, and if so, it triggers the PropertyChanged
event itself. So, if in the model you have a property called CircRad
, and when you twist the model you want to name it Radius
, then you just hang the attribute [Dependent("CircRad")]
on the Radius
property and when you change the CircRad
property in the model, the VM will report change the Radius
property. that is, ideally, the property in the VM will look like this
public string SomeProperty { get; set; }
Instead
public string SomeProperty { get { return _model.SomeProperty; } set { _model.SomeProperty = value; OnPropertyChanged(nameof(SomeProperty)); } }
Of course, if you need to provide interaction logic, then the property will have to be disclosed. Although I simply subscribe to my own PropertyChanged
inside the VM and depending on the name of the property that has changed, I perform certain actions. I do not know how it is right, but it's easier for me.
_model.Title = value;
not? - iRumba