Страницы

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

четверг, 11 июля 2019 г.

Реализация обновления Model из ViewModel (MVVM pattern, update Model from ViewModel)

В процессе изучения MVVM натолкнулся на проблему обновления данных в Model, полученных из ViewModel , имеется некое приложение, в котором:
Model:
public class Journal:IJournal { public Common CommonInfo { get; set; } public ObservableCollection Excavations { get; set; } }
public class Common:ICommon { public String Object { get; set; } public UInt32 NumJournal { get; set; } }
public class Excavation:IExcavation { public String NumExcavation { get; set; } public DateTime DateCreateExcavation { get; set; } }
ViewModel:
class JournalViewModel : PropertyChangedNotification,IJournal { public CommonViewModel CommonInfo { get { return GetValue(() => CommonInfo); } set { SetValue(() => CommonInfo, value); } }
public ObservableCollection Excavations { get { return GetValue(() => Excavations); } set { SetValue(() => Excavations, value); } }
public JournalViewModel(Journal journal) { CommonInfo = new CommonViewModel(journal.CommonInfo); Excavations = new ObservableCollection(); foreach (var excavation in journal.Excavations) { Excavations.Add(new ExcavationViewModel(excavation)); } } }
class CommonViewModel : PropertyChangedNotification,ICommon { public String Object { get { return GetValue(() => Object ); } set { SetValue(() => Object , value); } }
public UInt32 NumJournal { get { return GetValue(() => NumJournal ); } set { SetValue(() => NumJournal , value); } }
public CommonViewModel(Common common) { Object = common.Object; NumJournal = common.NumJournal ; } }
class ExcavationViewModel : PropertyChangedNotification, IExcavation { public String NumExcavation { get { return GetValue(() => NumExcavation ); } set { SetValue(() => NumExcavation , value); } }
public DateTime DateCreateExcavation { get { return GetValue(() => DateCreateExcavation ); } set { SetValue(() => DateCreateExcavation , value); } }
public ExcavationViewModel(Excavation excavation) { NumExcavation = common.NumExcavation ; DateCreateExcavation = common.DateCreateExcavation ; } }
Думаю будет совершенно не критично, если обойтись без View. В приведенном примере кода четко видно, что объект класса Journal передается в конструктор JournalViewModel и после чего, данные из этого объекта заносятся в свойства VM. Однако заносятся только данные, т.е. VM с M по сути не связаны. Возникает вопрос, а что если модель предназначена для сериализации и десериализации в определенный формат файла и работает именно с ним, ведь нужно обновлять данные модели...
Начитавшись статей, нашел 3 различных способа решить проблему обновления модели из VM:
1) Создать метод в VM который будет обновлять всю модель сразу (действия обратные действиям в конструкторе в полученный объект Journal записывать данные из свойств VM)
2) Создать некие Event отслеживающие изменения VM и записывающие данные в M
3) Создать свойство хранящее начальный объект Journal и в конструкторе записывать в него принимаемого значения после чего в getter и setter свойств использовать return Journals.Common (например) Однако, есть некоторая проблема с типами не могу понять, где ссылка работает, а где перестает действовать. Потому что я передаю объект (ссылку) Journal в конструктор VM, он так же содержит внутри себя Common который тоже является объектом (ссылкой), но связывания не происходит передаются только данные.
В данном примере
class BookViewModel : ViewModelBase { public Book Book;
public BookViewModel(Book book) { this.Book = book; }
public string Title { get { return Book.Title; } set { Book.Title = value; OnPropertyChanged("Title"); } } }
Связь VM с M реализована как раз через ссылку, не пойму правильно ли так их связывать в условиях паттерна, ведь по сути мы просто протягиваем модель и в нее через привязку к представлению записываем данные... В данной статье видел картинку в ней указано что VM общаясь с M использует 3 вида связи:
События изменения модели Обновление модели Получение данных
Сейчас работаю по примеру. не смотря на то что проблема выгрузки возникла почти сразу не обращал внимание, а теперь решил все таки заняться этим =( Если есть возможность, подскажите, пожалуйста, куда лучше копать чтобы не набить еще больше шишек..


Ответ

Как оказалось, действительно существует несколько способов взаимодействия с моделью. А использовать прямую связь с моделью, можно, как в этом примере:
class BookViewModel : ViewModelBase { public Book Book;
public BookViewModel(Book book) { this.Book = book; }
public string Title { get { return Book.Title; } set { Book.Title = value; OnPropertyChanged("Title"); } } }
однако в этом случае VM не будет той гибкой прослойкой, функцию которой она должна исполнять в серьезных приложениях. В любом случае, такой способ имеет место быть, но скорее в случаях когда модель и логика объединены или имеют одну и ту же структуру.<>
Если же Модель требуется обновляться во время изменения информации в VM сразу же (что может требоваться в любой системе) то нужно использовать Event-ы отслеживающие изменения VM и записывающие данные в M. К сожалению я не нашел примера =(
Однако моя задача сводилась к тому, что изменение в VM можно было бы вносить в M в любое время, и не важно, делать это сразу (Event) или во время выполнения команды (например сохранения). Для этого код классов VM был дополнен методами GetModel
class JournalViewModel : PropertyChangedNotification,IJournal { public CommonViewModel CommonInfo { get { return GetValue(() => CommonInfo); } set { SetValue(() => CommonInfo, value); } }
public ObservableCollection Excavations { get { return GetValue(() => Excavations); } set { SetValue(() => Excavations, value); } }
public JournalViewModel(Journal journal) { CommonInfo = new CommonViewModel(journal.CommonInfo); Excavations = new ObservableCollection(); foreach (var excavation in journal.Excavations) { Excavations.Add(new ExcavationViewModel(excavation)); } }
public Journal GetModel() { ObservableCollection excavations = null; if(Excavations!=null) { excavations = new ObservableCollection(); foreach(var excavation in Excavations) excavations.Add(excavation.GetModel()) } return new Common { Excavations= excavations , CommonInfo = CommonInfo.GetModel()
}; }
}
class CommonViewModel : PropertyChangedNotification,ICommon { public String Object { get { return GetValue(() => Object ); } set { SetValue(() => Object , value); } }
public UInt32 NumJournal { get { return GetValue(() => NumJournal ); } set { SetValue(() => NumJournal , value); } }
public CommonViewModel(Common common) { Object = common.Object; NumJournal = common.NumJournal ; }
public Common GetModel() { return new Common { Object = Object , NumJournal = NumJournal
}; } }
class ExcavationViewModel : PropertyChangedNotification, IExcavation { public String NumExcavation { get { return GetValue(() => NumExcavation ); } set { SetValue(() => NumExcavation , value); } }
public DateTime DateCreateExcavation { get { return GetValue(() => DateCreateExcavation ); } set { SetValue(() => DateCreateExcavation , value); } }
public ExcavationViewModel(Excavation excavation) { NumExcavation = common.NumExcavation ; DateCreateExcavation = common.DateCreateExcavation ; }
public Excavation GetModel() { return new Excavation { NumExcavation = NumExcavation , DateCreateExcavation = DateCreateExcavation };
}
}
Если у людей читающих этот ответ будет возможность ответить на вопрос: "Как организовать обновление Model во время изменения VM", буду очень благодарен. Иметь представление, что делается это через Event - хорошо, но видеть пример - ещё лучше. Так же буду благодарен за советы и комментарии касательно использованного способа (GetModel).
EDIT 25.06.2015 После точных замечаний @VladD, я решил внести некоторые изменения, возможно они будут правильнее нынешней реализации. И ведь действительно везде есть свои плюсы и минусы. В моей реализации, пользователь может вести работу сразу с несколькими журналами, предположим с 10. Сейчас я храню коллекцию не из 10 Journal (модели), а из 10 JournalViewModel (далее буду называть JourVM или просто VM), естественно JourVM содержит в себе множество свойств, полей, методов и команд, в таком случае, я не храню данные в виде объекта Journal в памяти (именно в объект модели Journal сериализуются данные из JSON, после чего грузятся в JourVM через конструкторы), а храню все JourVM в памяти и при необходимости вызываю метод GetModel() выгружаю все данные в объект модели только чтобы сериализовать модель в JSON.
В этом случае я вроде бы выигрываю немного памяти с тем, что не храню Journal (убирается после того как отдал данные в VM), однако перегружаю всю систему храня кучу VM =(
Видимо правильней было бы хранить коллекцию из Journal (List) и при переключении вкладок, используя interaction обращаться к модели, выгружая данные из модели в VM, при этом провести объекты модели через все VM для мгновенных изменений в модели, если разобрать на примере кода описанном выше, получится:
class JournalViewModel : PropertyChangedNotification, IJournal { public CommonViewModel CommonInfo { get { return GetValue(() => CommonInfo); } set { SetValue(() => CommonInfo, value);} }
public ObservableCollection Excavations { get { return GetValue(() => Excavations); } set { SetValue(() => Excavations, value); } }
private Journal _JournalM;
public JournalViewModel(Journal journal) { _JournalM = journal;
CommonInfo = new CommonViewModel(_JournalM.CommonInfo); Excavations = new ObservableCollection(); foreach (var excavation in _JournalM.Excavations) { Excavations.Add(new ExcavationViewModel(excavation)); } }
}
class CommonViewModel : PropertyChangedNotification, ICommon { public String Object { get { return GetValue(() => Object); } set { SetValue(() => Object, value); _CommonM.Object = Object; } }
public UInt32 NumJournal { get { return GetValue(() => NumJournal); } set { SetValue(() => NumJournal, value); _CommonM.NumJournal = NumJournal; } }
private CommonInfo _CommonM;
public CommonViewModel(Common common) { _CommonM = common; Object = _CommonM.Object; NumJournal = _CommonM.NumJournal; }
}
class ExcavationViewModel : PropertyChangedNotification, IExcavation { public String NumExcavation { get { return GetValue(() => NumExcavation); } set { SetValue(() => NumExcavation, value); _ExcavationM.NumExcavation = NumExcavation; } }
public DateTime DateCreateExcavation { get { return GetValue(() => DateCreateExcavation); } set { SetValue(() => DateCreateExcavation, value); _ExcavationM.DateCreateExcavation = DateCreateExcavation; } }
private Excavation _ExcavationM;
public ExcavationViewModel(Excavation excavation) { _ExcavationM = excavation; NumExcavation = _ExcavationM.NumExcavation; DateCreateExcavation = _ExcavationM.DateCreateExcavation; } }
По идее в конструктор VM поступает ссылка на M, мы ссылаемся на нее в VM и у нас не тратится драгоценная память, при изменении информации она сразу же меняется в нужной области в M и не требуется переключаться между VM. Сейчас мое приложение реализовано через TabControl-ы. 1) TabControl имеет вкладки Журналы, Настройки Справка и в качестве DataContex имеет MainVM который в свою очередь хранит VM с коллекцией журналов, отдельную VM с настройками и VM со справкой. 2) TabControl в качестве вкладок использует журналы из коллекции (DataContex на коллекцию из VM) хранящий ещё один TabControl с ссылками вкладок на определенные части VM. что вызывает некоторые тормоза в графике =( Если я все ещё не правильно интерпретирую данную мне информацию к размышлению и исправлению структуры и логики приложения это очень печально.

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

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