Подтвердить что ты не робот

MVVM и коллекции виртуальных машин

Общий сенарио: модель с набором моделей предметов.
Например, Дом с коллекцией людей.

Как правильно структурировать это для MVVM - особенно в отношении обновления коллекций Model и ViewModel с дополнениями и удалениями?

Модель House содержит набор моделей People (обычно a List<People>).
View model HouseVM содержит объект House, который он обертывает, и ObservableCollection модели представления PeopleVM (ObservableCollection<PeopleVM>). Обратите внимание, что мы заканчиваем здесь, когда HouseVM проводит две коллекции (которые требуют синхронизации):
1. HouseVM.House.List<People>
2. HouseVM.ObservableCollection<PeopleVM>

Когда House обновляется с новыми людьми (добавление) или Люди уходят (удаляют), это событие теперь должно обрабатываться в обеих коллекциях коллекции Model House People И VM HouseVM PeopleVM ObservableCollection.

Является ли эта структура правильной MVVM?
В любом случае, не нужно делать двойное обновление для добавлений и удалений?

4b9b3361

Ответ 1

Ваш общий подход отлично выглядит MVVM, поскольку ViewModel, представляющий коллекцию других ViewModels, является очень распространенным сценарием, который я использую повсюду. Я бы не рекомендовал подвергать элементы непосредственно в ViewModel, как сказал nicodemus13, так как вы в конечном итоге привязали свое представление к моделям без ViewModels между ними для ваших элементов коллекции. Итак, ответ на ваш первый вопрос: Да, это действительно MVVM.

Проблема, с которой вы обращаетесь в своем втором вопросе, - это синхронизация между списком моделей людей в модели вашего дома и списком людей ViewModels в вашем доме ViewModel. Вам следует сделать это вручную. Таким образом, нет никакого способа избежать этого.

enter image description here

Что вы можете сделать: Внедрите пользовательский ObservableCollection<T>, ViewModelCollection<T>, который подталкивает его к базовой коллекции. Чтобы получить двухстороннюю синхронизацию, сделайте также коллекцию моделей ObservableCollection < > и зарегистрируйтесь в событии CollectionChanged в вашем ViewModelCollection.

Это моя реализация. Он использует службу ViewModelFactory и т.д., Но просто взгляните на главного принципала. Надеюсь, это поможет...

/// <summary>
/// Observable collection of ViewModels that pushes changes to a related collection of models
/// </summary>
/// <typeparam name="TViewModel">Type of ViewModels in collection</typeparam>
/// <typeparam name="TModel">Type of models in underlying collection</typeparam>
public class VmCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    where TViewModel : class, IViewModel
    where TModel : class

{
    private readonly object _context;
    private readonly ICollection<TModel> _models;
    private bool _synchDisabled;
    private readonly IViewModelProvider _viewModelProvider;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="models">List of models to synch with</param>
    /// <param name="viewModelProvider"></param>
    /// <param name="context"></param>
    /// <param name="autoFetch">
    /// Determines whether the collection of ViewModels should be
    /// fetched from the model collection on construction
    /// </param>
    public VmCollection(ICollection<TModel> models, IViewModelProvider viewModelProvider, object context = null, bool autoFetch = true)
    {
        _models = models;
        _context = context;

        _viewModelProvider = viewModelProvider;

        // Register change handling for synchronization
        // from ViewModels to Models
        CollectionChanged += ViewModelCollectionChanged;

        // If model collection is observable register change
        // handling for synchronization from Models to ViewModels
        if (models is ObservableCollection<TModel>)
        {
            var observableModels = models as ObservableCollection<TModel>;
            observableModels.CollectionChanged += ModelCollectionChanged;
        }


        // Fecth ViewModels
        if (autoFetch) FetchFromModels();
    }

    /// <summary>
    /// CollectionChanged event of the ViewModelCollection
    /// </summary>
    public override sealed event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { base.CollectionChanged += value; }
        remove { base.CollectionChanged -= value; }
    }

    /// <summary>
    /// Load VM collection from model collection
    /// </summary>
    public void FetchFromModels()
    {
        // Deactivate change pushing
        _synchDisabled = true;

        // Clear collection
        Clear();

        // Create and add new VM for each model
        foreach (var model in _models)
            AddForModel(model);

        // Reactivate change pushing
        _synchDisabled = false;
    }

    private void ViewModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Return if synchronization is internally disabled
        if (_synchDisabled) return;

        // Disable synchronization
        _synchDisabled = true;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Add(m);
                break;

            case NotifyCollectionChangedAction.Remove:
                foreach (var m in e.OldItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Remove(m);
                break;

            case NotifyCollectionChangedAction.Reset:
                _models.Clear();
                foreach (var m in e.NewItems.OfType<IViewModel>().Select(v => v.Model).OfType<TModel>())
                    _models.Add(m);
                break;
        }

        //Enable synchronization
        _synchDisabled = false;
    }

    private void ModelCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (_synchDisabled) return;
        _synchDisabled = true;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var m in e.NewItems.OfType<TModel>()) 
                    this.AddIfNotNull(CreateViewModel(m));
                break;

            case NotifyCollectionChangedAction.Remove:
                    foreach (var m in e.OldItems.OfType<TModel>()) 
                        this.RemoveIfContains(GetViewModelOfModel(m));
                break;

            case NotifyCollectionChangedAction.Reset:
                Clear();
                FetchFromModels();
                break;
        }

        _synchDisabled = false;
    }

    private TViewModel CreateViewModel(TModel model)
    {
        return _viewModelProvider.GetFor<TViewModel>(model, _context);
    }

    private TViewModel GetViewModelOfModel(TModel model)
    {
        return Items.OfType<IViewModel<TModel>>().FirstOrDefault(v => v.IsViewModelOf(model)) as TViewModel;
    }

    /// <summary>
    /// Adds a new ViewModel for the specified Model instance
    /// </summary>
    /// <param name="model">Model to create ViewModel for</param>
    public void AddForModel(TModel model)
    {
        Add(CreateViewModel(model));
    }

    /// <summary>
    /// Adds a new ViewModel with a new model instance of the specified type,
    /// which is the ModelType or derived from the Model type
    /// </summary>
    /// <typeparam name="TSpecificModel">Type of Model to add ViewModel for</typeparam>
    public void AddNew<TSpecificModel>() where TSpecificModel : TModel, new()
    {
        var m = new TSpecificModel();
        Add(CreateViewModel(m));
    }
}

Ответ 2

В этой ситуации я просто создаю модель ObservableCollection, а не List s. Нет особых причин, почему это не должно. ObservableCollection находится в пространстве имен System.Collections.ObjectModel сборки System, поэтому нет никаких необоснованных дополнительных зависимостей, вы почти наверняка имеете System в любом случае. List находится в mscorlib, но этот исторический артефакт как бы ничего.

Это упрощает взаимодействие модели-viewmodel в массовом порядке, я не вижу причины не делать этого, используя List на модели, просто создает много неприятного кода котельной. В конце концов, вас интересуют события.

Кроме того, почему ваша HouseVM упаковка ObservableCollection<PeopleVM>, а не ObservableCollection<People>? VM для привязки к представлениям, поэтому я думаю, что все, что привязано к вашему ObservableCollection<PeopleVM>, действительно интересует People, в противном случае вы привязываете-в-привязке или есть конкретная причина, почему это полезно? У меня обычно не было бы VM выставлять другие виртуальные машины, но, возможно, это только я.

Изменить библиотеки /WCF

Я не понимаю, почему наличие модели в библиотеке или даже отображение WCF-сервера должно влиять на то, поднимают ли они события или нет, это кажется мне совершенно верным (очевидно, что WCF-сервис не будет раскрывать события напрямую). Если вам это не нравится, я думаю, вы застряли в том, что вам нужно связать несколько обновлений, хотя мне интересно, действительно ли вы выполняете ту же самую работу, что и событие в ObservableCollection, если я не неправильно поняли некоторые из них.

Лично, как я уже сказал, я бы сохранил виртуальные машины просто, и пусть они выставляют минимум и не открывают другие виртуальные машины. Это может потребовать некоторую редизайн и немного улучшить некоторые части (например, Converter s, однако в итоге вы получите простой, простой в управлении дизайн с некоторыми простыми в обращении раздражениями по краям.

Мне кажется, что ваш текущий маршрут будет очень сложным довольно быстро и, что самое главное, неудобно следовать... Однако YMMV это просто мой опыт:)

Возможно, может понадобиться переместить часть логики в явные службы?