Страницы

Поиск по вопросам

воскресенье, 22 декабря 2019 г.

WPF(MVVM) + Entity Framework: обновление GUI с изменением навигационного свойства

#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 есть окно, в котором этот автор подробно расписан:


    
        
    
    
        
        
        
            
                
                    
                
            
        
        


Ответы

Ответ 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 ObservableCollection Books { get; set; } } Если не хотите менять модель, то вам придется завести такую коллекцию в модели представления, привязаться к ней и следить за тем, чтобы содержимое коллекции в модели представления всегда совпадало с содержимым коллекции модели.

Комментариев нет:

Отправить комментарий