In order to use the Collection<T> proxy Collection<T> you still have to make adjustments to the model. Your book collection should implement the IList<Book> interface, for example:
public virtual List<Book> Books { get; set; } = new List<Book>();
We will not make any more changes to the model.
In order for your UI to be notified when the contents of the book collection are changed - delete, insert, etc., we need to implement the INotifyCollectionChanged interface. Its implementation is nontrivial, but, fortunately, there is (at least) one example implementation in the standard library: ObservableCollection<T> : https://referencesource.microsoft.com/#system/compmod/system/collections/objectmodel/ observablecollection.cs
As seen in the source code, the class is inherited from the Collection<T> proxy collection, but for unknown reasons, the developers have hidden the proxy functionality from us.
Well, I just copied the ObservableCollection<T> implementation and instead of the existing constructors wrote my own:
public class ProxyObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged { public ProxyObservableCollection() : base() { } public ProxyObservableCollection(IEnumerable<T> collection) { if (collection == null) throw new ArgumentNullException("collection"); CopyFrom(collection); } public ProxyObservableCollection(IList<T> list) : base(list) { }
Of particular interest here is the last constructor — it’s just (unlike the copy constructor) that creates a proxy to the transmitted IList<T> .
Now you can use it. Add to VM:
ProxyObservableCollection<Book> _booksOfCurrentAuthor; public ProxyObservableCollection<Book> BooksOfCurrentAuthor { get { return _booksOfCurrentAuthor; } private set { _booksOfCurrentAuthor = value; RaisePropertyChanged(nameof(BooksOfCurrentAuthor)); } }
Add to CurrentAuthor property:
public Author CurrentAuthor { get { ... } set { _author = value; BooksOfCurrentAuthor = new ProxyObservableCollection(CurrentAuthor.Books); RaisePropertyChanged("CurrentAuthor"); }
Now a copy of the collection will not be created, but at the same time, all changes in the book collection will notify the UI:
<ListView ... ItemsSource="{Binding Path=BooksOfCurrentAuthor}"
Just in case, I quote the full code of ProxyObservableCollection<T> :
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace MyNamespace { public class ProxyObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged { public ProxyObservableCollection() : base() { } public ProxyObservableCollection(IEnumerable<T> collection) { if (collection == null) throw new ArgumentNullException("collection"); CopyFrom(collection); } public ProxyObservableCollection(IList<T> list) : base(list) { } private void CopyFrom(IEnumerable<T> collection) { IList<T> items = Items; if (collection != null && items != null) { using (IEnumerator<T> enumerator = collection.GetEnumerator()) { while (enumerator.MoveNext()) { items.Add(enumerator.Current); } } } } public void Move(int oldIndex, int newIndex) { MoveItem(oldIndex, newIndex); } event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { PropertyChanged += value; } remove { PropertyChanged -= value; } } public virtual event NotifyCollectionChangedEventHandler CollectionChanged; protected override void ClearItems() { CheckReentrancy(); base.ClearItems(); OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); OnCollectionReset(); } protected override void RemoveItem(int index) { CheckReentrancy(); T removedItem = this[index]; base.RemoveItem(index); OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index); } protected override void InsertItem(int index, T item) { CheckReentrancy(); base.InsertItem(index, item); OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); } protected override void SetItem(int index, T item) { CheckReentrancy(); T originalItem = this[index]; base.SetItem(index, item); OnPropertyChanged(IndexerName); OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index); } protected virtual void MoveItem(int oldIndex, int newIndex) { CheckReentrancy(); T removedItem = this[oldIndex]; base.RemoveItem(oldIndex); base.InsertItem(newIndex, removedItem); OnPropertyChanged(IndexerName); OnCollectionChanged(NotifyCollectionChangedAction.Move, removedItem, newIndex, oldIndex); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); protected virtual event PropertyChangedEventHandler PropertyChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) { using (BlockReentrancy()) { CollectionChanged(this, e); } } } protected IDisposable BlockReentrancy() { _monitor.Enter(); return _monitor; } protected void CheckReentrancy() { if (_monitor.Busy) { if ((CollectionChanged != null) && (CollectionChanged.GetInvocationList().Length > 1)) throw new InvalidOperationException(); } } private void OnPropertyChanged(string propertyName) => OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex)); private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); private void OnCollectionReset() => OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); private class SimpleMonitor : IDisposable { public void Enter() { ++_busyCount; } public void Dispose() { --_busyCount; } public bool Busy { get { return _busyCount > 0; } } int _busyCount; } private const string CountString = "Count"; private const string IndexerName = "Item[]"; private SimpleMonitor _monitor = new SimpleMonitor(); } }