Suppose in the MVVM application there are two classes in the model:

public class Author { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Book> Books { get; set; } } public class Book { public int Id { get; set; } public string Title { get; set; } public int AuthorId { get; set; } public virtual Author Author { get; set; } } 

In ViewModel, there is a class in which a parameter with some book with several books is loaded by the EntityFramework from the database:

 public class AuthorsViewModel: ViewModelBase { public AuthorsViewModel() : this(null) { } public AuthorsViewModel(Author author) { _author = author; } Author _author; public Author CurrentAuthor { get { if (_author == null) { _author = new Author(); } return _author; } set { _author = value; RaisePropertyChanged("CurrentAuthor"); } } Book _selectedBook; public Book SelectedBook { get { if (_selectedBook == null) { _selectedBook = new Book(); } return _selectedBook; } set { _selectedBook = value; RaisePropertyChanged("SelectedBook"); } } public RelayCommand RemoveBookCommand { get { return new RelayCommand(RemoveBook); } } void RemoveBook() { CurrentAuthor.Books.Remove(SelectedBook); } } 

And finally, in View there is a window in which this author is described in detail:

 <Window x:Class="{КЛАСС ОКНА}" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="{НАШ NAMESPACE}" xmlns:vm="clr-namespace:{ТАМ ЛЕЖИТ НАШ КЛАСС ViewModel}" mc:Ignorable="d" Title="" Height="246" Width="300"> <Window.DataContext> <vm:AuthorsViewModel/> </Window.DataContext> <Grid> <TextBlock Text="Author:"/> <TextBox Text="{Binding Path=CurrentAuthor.Name}" Width="150" Height="23" Margin="40,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black"/> <ListView BorderBrush="Black" Width="250" Height="150" ItemsSource="{Binding Path=CurrentAuthor.Books}" SelectedItem="{Binding Path=SelectedBook}"> <ListView.View> <GridView> <GridViewColumn Header="Books" DisplayMemberBinding="{Binding Path=Title}"/> </GridView> </ListView.View> </ListView> <Button Content="Remove book" Height="25" Width="50" VerticalAlignment="Bottom" Margin="5" Command="{Binding Path=RemoveBookCommand}"/> </Grid> </Window> 

All this seems to work, but only when you click on the delete button, the current book is indeed removed from the entity, but it still continues to be displayed in the window. How do I update the user interface when changing the CurrentAuthor.Books navigation property?

PS: The example is completely abstract, but the code as a whole repeats the logic of the operation of my application, so if there are comments, I will be glad to hear.

    2 answers 2

    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(); } } 
    • @VladD, log in. - user207618
    • @Other, thanks for the compliment, but unfortunately I am not VladD) - Andrey NOP
    • I know the truth ™, create the appearance of competition, @VladD? :) - user207618
    • one
      @ Andrew what is this "unfortunately"? Never regret that you are not someone else. - Ev_Hyper

    In order for your changes to the collection to be visible in the view, you need to use the ObservableCollection {T} type . Those. Your model will look like this:

     public class Author { ... public virtual ObservableCollection<Book> Books { get; set; } } 

    If you do not want to change the model, you will have to create such a collection in the view model, attach to it and ensure that the contents of the collection in the view model always coincide with the contents of the model collection.