EnumerableCollection
Prenons le scénario suivant. On utilise MVVM.
On a les deux classes suivantes dans le model :
public class Child
{
}
public class Parent
{
private ObservableCollection<Child> _children;
public ObservableCollection<Child> Children
{
get { return _children ?? (_children = new ObservableCollection<Child>()); }
}
}
et les deux suivantes dans le ViewModel :
public class ChildViewModel
{
public ChildViewModel(Child child)
{
Child = child;
}
private Child Child { get; set; }
}
public class ParentViewModel
{
public ParentViewModel(Parent parent = null)
{
Model = parent ?? new Parent();
}
private Parent Model { get; set; }
private ObservableCollection<ChildViewModel> _children;
public ObservableCollection<ChildViewModel> Children
{
get { return _children ?? (_children = new ObservableCollection<ChildViewModel>(Model.Children.Select(c => new ChildViewModel(c)))); }
}
}
Comment gérer le problème de synchronisation entre les deux collections ?
Je pense que, dans ce cas, le ViewModel ne devrait pas exposer une collection mais seulement un IEnumerable.
Mon code deviendrait alors ceci :
public class ParentViewModel : INotifyPropertyChanged
{
public ParentViewModel(Parent parent = null)
{
Model = parent ?? new Parent();
Model.Children.CollectionChanged += delegate { RaisePropertyChanged(() => Children); };
}
private Parent Model { get; set; }
public IEnumerable<ChildViewModel> Children
{
get { return Model.Children.Select(c => new ChildViewModel(c)); }
}
#region INotifyPropertyChanged Members
public void RaisePropertyChanged<T>(Expression<Func<T>> exp)
{
var memberExpression = exp.Body as MemberExpression;
if (memberExpression == null)
return;
string propertyName = memberExpression.Member.Name;
if (propertyName != null && PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Ca marche. Cependant, si j’ajoute ou supprime un child de la collection Parent.Children, tous mes ChildViewModel sont recréés se qui peut poser un gros problème de binding.
Pour résumer, on a besoin d’un IEnumerable<T> qui implémente aussi INotifyCollectionChanged.
Je propose donc la classe EnumerableCollection suivante :
public interface IEnumerableCollection<out T> : IEnumerable<T>, INotifyCollectionChanged
{
void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e);
}
public class EnumerableCollection<T> : EnumerableCollection<T, T>
{
public EnumerableCollection(ObservableCollection<T> observableCollection)
: base(observableCollection, null)
{
}
public EnumerableCollection(IEnumerable<ObservableCollection<T>> observableCollections)
: base(observableCollections, null)
{
}
public EnumerableCollection(params ObservableCollection<T>[] observableCollections)
: this((IEnumerable<ObservableCollection<T>>)observableCollections)
{
}
public EnumerableCollection(IEnumerable<IEnumerableCollection<T>> enumerableCollections)
: base(enumerableCollections, null)
{
}
public EnumerableCollection(params IEnumerableCollection<T>[] observableCollections)
: this((IEnumerable<IEnumerableCollection<T>>)observableCollections)
{
}
public EnumerableCollection(IEnumerable<T> enumerable)
: base(enumerable, null)
{
}
}
public class EnumerableCollection<TSource, TResult> : IEnumerableCollection<TResult>, IEnumerable<TResult>, INotifyCollectionChanged
{
private IEnumerable<TResult> _enumerable;
#region ctors
public EnumerableCollection(ObservableCollection<TSource> observableCollection, Func<TSource, TResult> selector)
{
observableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
_enumerable = Convert(observableCollection, selector);
}
public EnumerableCollection(IEnumerable<ObservableCollection<TSource>> observableCollections, Func<TSource, TResult> selector)
{
foreach (ObservableCollection<TSource> observableCollection in observableCollections)
observableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
_enumerable = Convert(observableCollections.SelectMany(item => item), selector);
}
public EnumerableCollection(IEnumerable<IEnumerableCollection<TSource>> enumerableCollections, Func<TSource, TResult> selector)
{
foreach (EnumerableCollection<TSource> enumerableCollection in enumerableCollections)
enumerableCollection.CollectionChanged += (sender, e) => RaiseCollectionChanged(e);
_enumerable = Convert(enumerableCollections.SelectMany(item => item), selector);
}
public EnumerableCollection(IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
{
_enumerable = Convert(enumerable, selector);
}
#endregion ctors
private IEnumerable<TResult> Convert(IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
if (selector != null)
return source.Select(selector);
if (typeof(TSource) == typeof(TResult))
return (IEnumerable<TResult>)source;
throw new InvalidOperationException();
}
public void OrderBy<K>(Func<TResult, K> keySelector)
{
if (_enumerable == null)
throw new InvalidOperationException();
_enumerable = _enumerable.OrderBy(keySelector);
}
public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, e);
}
#region IEnumerable<T> Members
public IEnumerator<TResult> GetEnumerator()
{
return _enumerable.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
}
Avec ceci, le code de mon ParentViewModel devient ceci :
public class ParentViewModel
{
public ParentViewModel(Parent parent = null)
{
Model = parent ?? new Parent();
}
private Parent Model { get; set; }
private EnumerableCollection<Child, ChildViewModel> _children;
public EnumerableCollection<Child, ChildViewModel> Children
{
get { return _children ?? (_children = new EnumerableCollection<Child, ChildViewModel>(Model.Children, c => new ChildViewModel(c))); }
}
}
Hope that helps
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :