#c_sharp #wpf #entity_framework #mvvm
Допустим в приложении MVVM в модели есть два класса:
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection 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; }
}
Во ViewModel есть класс, в который параметром передается какой-нибудь автор с несколькими
книгами, загруженный из БД средствами EntityFramework:
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);
}
}
И, наконец, во View есть окно, в котором этот автор подробно расписан:
Все это вроде бы работает, но только при нажатии на кнопку удаления текущая книга
действительно удаляется из сущности, но вот в окне она по прежнему продолжает отображаться.
Как при изменении навигационного свойства CurrentAuthor.Books обновлять пользовательский
интерфейс?
P.S.: Пример полностью абстрактный, но код в целом повторяет логику работы моего
приложения, так что если есть замечания - буду рад услышать.
Ответы
Ответ 1
Для того, чтобы воспользоваться прокси-колекцией Collectionвам все же придется внести корректировки в модель. Ваша коллекция книг должна реализовывать интерфейс IList , например: public virtual List Books { get; set; } = new List (); Больше никаких изменений в модель мы вносить не будем. Для того, чтобы ваш UI получал уведомления при изменении содержимого коллекции книг - удалении, вставке и т. п., нам необходимо реализовать интерфейс INotifyCollectionChanged. Реализация его нетривиальна, но, к счастью, в стандартной библиотеке есть (по крайней мере) один пример реализации - это класс ObservableCollection : https://referencesource.microsoft.com/#system/compmod/system/collections/objectmodel/observablecollection.cs Как видно в исходном коде класс наследуется от прокси-коллекции Collection , но, по неизвестным причинам, разработчики скрыли от нас прокси функционал. Чтож, я просто скоприровал реализацию ObservableCollection и вместо существующих конструкторов написал свои: public class ProxyObservableCollection : Collection , INotifyCollectionChanged, INotifyPropertyChanged { public ProxyObservableCollection() : base() { } public ProxyObservableCollection(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); CopyFrom(collection); } public ProxyObservableCollection(IList list) : base(list) { } Особый интерес предоставляет здесь последний конструктор - он-то как раз (в отличие от копирующего конструктора) и создает прокси на передаваемый IList . Теперь этим можно воспользоваться. Добавим в VM: ProxyObservableCollection _booksOfCurrentAuthor; public ProxyObservableCollection BooksOfCurrentAuthor { get { return _booksOfCurrentAuthor; } private set { _booksOfCurrentAuthor = value; RaisePropertyChanged(nameof(BooksOfCurrentAuthor)); } } Добавим в свойство CurrentAuthor: public Author CurrentAuthor { get { ... } set { _author = value; BooksOfCurrentAuthor = new ProxyObservableCollection(CurrentAuthor.Books); RaisePropertyChanged("CurrentAuthor"); } Теперь копия коллекции создаваться не будет, но, в то же время, все изменения в коллекции книг будут оповещать UI: : using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace MyNamespace { public class ProxyObservableCollection : Collection , INotifyCollectionChanged, INotifyPropertyChanged { public ProxyObservableCollection() : base() { } public ProxyObservableCollection(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); CopyFrom(collection); } public ProxyObservableCollection(IList list) : base(list) { } private void CopyFrom(IEnumerable collection) { IList items = Items; if (collection != null && items != null) { using (IEnumerator 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(); } } Ответ 2
Для того, чтобы ваши изменения коллекции были видны в представлении нужно использовать тип ObservableCollection{T}. Т.е. ваша модель будет выглядеть следующим образом: public class Author { ... public virtual ObservableCollectionBooks { get; set; } } Если не хотите менять модель, то вам придется завести такую коллекцию в модели представления, привязаться к ней и следить за тем, чтобы содержимое коллекции в модели представления всегда совпадало с содержимым коллекции модели.
Комментариев нет:
Отправить комментарий