В процессе изучения MVVM натолкнулся на проблему обновления данных в Model, полученных из ViewModel , имеется некое приложение, в котором:
Model:
public class Journal:IJournal
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 ObservableCollection
public JournalViewModel(Journal journal)
{
CommonInfo = new CommonViewModel(journal.CommonInfo);
Excavations = new ObservableCollection
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 ObservableCollection
public JournalViewModel(Journal journal)
{
CommonInfo = new CommonViewModel(journal.CommonInfo);
Excavations = new ObservableCollection
public Journal GetModel()
{
ObservableCollection
};
}
}
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 ObservableCollection
private Journal _JournalM;
public JournalViewModel(Journal journal)
{
_JournalM = journal;
CommonInfo = new CommonViewModel(_JournalM.CommonInfo);
Excavations = new ObservableCollection
}
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. что вызывает некоторые тормоза в графике =(
Если я все ещё не правильно интерпретирую данную мне информацию к размышлению и исправлению структуры и логики приложения это очень печально.
Комментариев нет:
Отправить комментарий