Использование BindableBase в классическом дизайне MVVM
Меня немного смущает использование класса BindableBase и то, как применить этот" новый " механизм к классическому дизайну MVVM.
Вкратце, вопрос заключается в следующем: Как правильно использовать класс BindableBase, когда у нас есть ссылка на модель в нашем классе view-model?
Подробности:
Классическая модель MVVM: вид Вид-модель - > Модель
Как мы видим, модель представления в этой схеме знает о модели, но модель ничего не знает о представлении и модели представления.
Если мы реализуем этот подход, то получим примерно следующее:
// Model
class Task
{...}
// View-Model
class TaskViewModel : BindableBase
{
private readonly Task _task;
public TaskViewModel(Task task)
{
_task = task;
}
...
}
Представим себе, что класс задач обладает свойством "субъект", и мы должны показать эти данные. Итак, согласно MVVM я должен:
Создать дублирование свойства "субъект" в View-модели:
// View-Model
class TaskViewModel : BindableBase
{
public String Subject
{
get{ return _task.Subject; }
set
{
_task.Subject = value;
// I can't use SetProperty(ref _task.Subject, value)
// it's contradict c# syntax
OnPropertyChanged("Subject");
}
}
}
Как вы видите, я не могу использовать метод SetProperty для такого дизайна, и единственный способ-это вызов метода raw onPropertyChanged.
Похоже, что SetProperty-это самое большое преимущество класса BindableBase и очень странно, что мы не можем использовать его в такой прямой и общей реализации MVVM. Поэтому я подумал, что, возможно, я что-то пропустил или неправильно работаю с указанным классом.
Знаете ли вы, как использовать BindableBase для указанного дизайна и получить некоторое улучшение кода?
Спасибо
4 ответов:
В настоящее время Ваш
ViewModelпредоставляетModelсвойства вашемуView. Это прекрасно однако становится довольно смешно, если вашModelимеет много свойств, которые должны быть выставлены. Можете ли вы представить себе необходимость создавать свойства дляModel, который имеет 20 + свойств?Вместо этого вы должны открыть
ModelдляView, используя свойство внутри вашегоViewModel.public MyClass Model { get; private set; }Примечание: это тоже можно реализовать
INotifyPropertyChanged.И свойства в вашем
Modelследует реализоватьINotifyPropertyChanged, или, в вашем случае,BindableBase.public class MyClass : BindableBaseВаш
Единственным недостатком здесь является то, что вашViewможет быть привязан непосредственно к свойствуModel. Это может показаться, что вы нарушаете шаблон проектирования, однако это не так, вашViewвсе еще ничего не знает о вашемModel, однако он просто делает предположения о том, какие свойства он ожидает, поэтому ваши классы все еще разделены.Modelтеперь зависит отBindableBase, это не конец света, но если вы находитесь в ситуации, когда вы не можете изменить классыModel, тогда ваш текущий подход - это путь.
Как сказал Майк Исон, это прекрасно, чтобы показать свою модель, одна из главных целей ViewModel-подготовить модель для вашего представления. Тем не менее, я сам склонен выставлять модель только для просмотра только для чтения.
Можно наследовать от BindableBase и создать метод, позволяющий изменять свойства модели таким же образом, как и поля.
Функция isValueNew предназначена для определения, отличается ли значение или нет. Затем вы можете использовать его как следующее:public class ViewModelBase : BindableBase { protected bool SetProperty( Func<bool> isValueNew, Action setValue, [CallerMemberName] string propertyName = null) { if (isValueNew()) { return false; } setValue(); OnPropertyChanged(propertyName); return true; } }Это самый простой способ, который я могу придумать, чтобы достичь того,что вы, кажется, хотите.public class MyViewModel : ViewModelBase { private readonly MyModel myModel = new MyModel(); public string Name { get { return myModel.Name; } set { if (SetProperty(() => myModel.Name == value, () => myModel.Name = value)) { // Do something here since the value was changed. } } } }
Я бы сохранил ваш сеттер для свойства субъекта простым.
Сначала загрузите данные из модели, а затем назначьте их свойствам модели представления.
class TaskViewModel : BindableBase { LoadData() { Subject = GetSubject(); // Relies on model } string _subject = null; public String Subject { get{ return _subject; } set { _subject = value; SetProperty(ref _task.Subject, value) } } }
Попробуйте:
class TaskViewModel : BindableBase { private readonly Task _task; private string _subject; public TaskViewModel(Task task) { _task = task; Subject = _task.Subject; } public string Subject { get { return _subject; } set { _task.Subject = value; SetProperty(ref _subject, value) } } }UPD
Я решил добавить некоторые пояснения после первого комментария.BindableBase-это всего лишь базовая версия класса, реализующего интерфейс
INotifiPropertyChanged. Это реализация методаSetProperty(для структурыPrism):public abstract class BindableBase : INotifyPropertyChanged { // Some other members public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (object.Equals((object) storage, (object) value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } }Как видно, метод
SetPropertyесли просто "синтаксический сахар" иOnPropertyChangedвыполняется внутри. Поэтому на самом деле не имеет значения, вызываете ли вы методSetPropertyилиOnPropertyChanged.
Comments