Как представить коллекцию моделей (View) в ViewModel

У меня есть вопрос относительно дизайна MVVM для С#/WPF. Я посмотрел несколько демо-приложений, но они не справлялись с моей проблемой. Мое приложение состоит из объектов, которые содержат другие объекты. Как и в отношениях между родителями и детьми.

Теперь мой вопрос:

  • атрибут children должен быть ViewModel
  • и если да, то каким образом я могу создать новые родительские объекты, содержащие существующие дочерние объекты через ViewModels?

У меня есть следующий сценарий:

class Child {
    string Name;
}

class ChildVM {
    Child _child;
    string Name{return _child.Name;}
}

class Parent {
    string Name;
    List<Child> children;
}

class ParentVM{
    Parent _parent;

    string Name{return _parent.Name;}
    List<ChildVM> children {get;set;}

    ParentVM(Parent p){_parent = p;}
}

void CreateANewParent(){
    List<ChildVM> children = new List<ChildVM>(){new ChildVM(new Child()),...};
    ParentVM parent = new ParentVM(new Parent());
    foreach(ChildVM child in children)
        parent.children.Add(child);
}

Проблема заключается в том, что ParentVM содержит ChildVM, но фактический родитель (который находится внутри ParentVM) не имеет дочерних объектов, содержащихся в объектах ChildVM. Я также не думаю, что это хорошая идея для дублирования объектов Child, поскольку это приводит к избыточности, и в моем контексте приложения также нет необходимости/возможности создавать новые дочерние объекты.

Я также подумал о следующем классе:

class ParentVM {
    Parent _parent;

    string Name{return _parent.Name;}
    List<Child> children {get{return _parent.Children;}}
}

Однако это означало бы, что я буду непосредственно работать с Модели, если я хочу манипулировать дочерним объектом ParentVM.

С другой стороны, я мог просто оставить родительский (Model) родительский и использовать ParentVM для создания нового родителя в базе данных. Но это хороший способ справиться с этой проблемой?

Ответ 1

На самом деле, правильный способ сделать это - это когда вы сначала создаете ParentVM, вы выполняете итерацию через дочерние элементы переданного родителя, создавая для них ChildVM, а затем добавляйте эти объекты ChildVM в свойство ParentVM ChildVMs. (Некоторые просто назовут это свойство "Дети", но мне лично нравится очищать его от ChildVM, а не от коллекции объектов Child. Просто добавление суффикса "VM" делает это очень понятным.

Затем вам также необходимо прослушать уведомление об изменении в фактической коллекции Parent Children и соответствующим образом обновить вашу коллекцию ChildVM.

Таким образом, у вас есть модель с Parent- > Children- > Child и ViewModel из ParentVM- > ChildVMs- > ChildVM, которая, я считаю, именно то, что вы хотите.

Теперь я также полагаю, что вы должны быть в состоянии разоблачить родителя непосредственно из ParentVM, а также ребенка непосредственно из ChildVM, поскольку ваш пользовательский интерфейс может быть привязан к различным свойствам этих элементов, например, ваше имя свойство выше. Однако пуристы M-V-VM сказали бы, что никогда не делают этого, заявляя, что пользовательский интерфейс никогда не должен знать о модели, потому что, если модель изменяется, вы должны изменить интерфейс. Мой аргумент заключается в том, что если модель меняется, вы все равно должны изменить ViewModel по той же причине. Единственная экономия будет заключаться в том, что если есть несколько просмотров, которые используют один и тот же ViewModel, так как вам просто нужно будет изменить его в одном месте, но в реальности что-то вроде "Имя" не изменит его "имя" от модели до ViewModel, поэтому в этих случаях это не аргумент.

Кроме того, есть накладные расходы на производительность, делая это "пурист" в том, что вы не можете просто делегировать элемент модели, как вы делаете с именем выше, потому что представление никогда не узнает ни о какой модели, сгенерированные изменения свойства Name, если вы также не добавили все дополнительные уведомления об изменении внутри виртуальной машины, то есть теперь у вас есть одно уведомление об изменении в модели, единственной целью которого является запуск второго уведомления об изменении в VM, которое затем уведомляет пользовательский интерфейс. Чистый? Да. Производительность инвазивная? Вы делаете ставку, особенно когда происходят большие изменения, и вы используете интерфейс INotifyPropertyChanged, потому что это означает, что вам нужно выполнять сравнения строк в обработчике изменений, чтобы обнаруживать и делегировать все эти изменения! Однако если вы привязываетесь непосредственно к свойству ParentVM.Parent.Name, у вас уже есть уведомление об изменении из модели, чтобы уведомить пользовательский интерфейс, и вы также сохраняете свою виртуальную машину чистой для вещей, которые являются только VM- или View-specific.

Однако я никогда не делаю ничего в модели, которая доступна только для просмотра. Для меня это то, что для ViewModel. Например, если у детей есть определенный цвет, основанный на перечислении или что-то еще, то для меня это что-то, что происходит в ChildVM, а не в самой модели, и если в модели есть какие-либо свойства, которые диктуют этот цвет, например свойство этого перечисления, в этом случае, да, я бы связал изменения-уведомления из модели внутри ChildVM. (По правде говоря, я могу даже просто сделать это через цветной конвертер в пользовательском интерфейсе напрямую, все еще привязанный к перечислению модели. Это действительно дело в каждом случае.)

НТН,

Марк

Ответ 2

Хорошо, я получил некоторую помощь и совет от форума .NET: Я разрешу проблему, предоставив Get-Method для модели внутри ViewModel. Поэтому, если я создаю нового родителя с существующими ChildVM, я просто верну ссылку Child в ChildVMs и назначу их новому родительскому.