Использование 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 для указанного дизайна и получить некоторое улучшение кода?



Спасибо

664   4  

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 и создать метод, позволяющий изменять свойства модели таким же образом, как и поля.

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;
    }
}
Функция isValueNew предназначена для определения, отличается ли значение или нет. Затем вы можете использовать его как следующее:
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

    Ничего не найдено.