BindableBase在经典MVVM设计中的使用

本文关键字:经典 MVVM BindableBase | 更新日期: 2023-09-27 18:15:12

我对BindableBase类的用法以及如何将这种"新"机制应用于经典的MVVM设计有点困惑。

简单地说,问题如下:当我们在视图模型类中引用模型时,如何正确使用BindableBase类?

细节:

经典MVVM模式:View <-> View-Model -> Model

正如我们所看到的,这个方案中的View-Model知道Model,但是Model对View和View-Model一无所知。

如果我们实现这种方法,我们将得到这样的东西:

// Model
class Task
{...}
// View-Model
class TaskViewModel : BindableBase
{
    private readonly Task _task;
    public TaskViewModel(Task task)
    {
        _task = task;
    }
    ...
}

让我们想象任务类有'Subject'属性,我们应该显示这个数据。所以根据MVVM,我应该:

创建View-Model中'Subject'属性的副本:

// 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方法来进行这样的设计,唯一的方法是调用原始的onPropertyChanged方法。

似乎SetProperty是BindableBase类的最大优点,但我们不能在MVVM的这种直接和常见的实现中使用它,这是非常奇怪的。所以我想也许我错过了什么或工作不正确的指定类。

你知道如何使用BindableBase为指定的设计和得到一些代码改进吗?

谢谢

BindableBase在经典MVVM设计中的使用

当前,您的ViewModelModel属性暴露给View。这是很好但是它变得相当荒谬,如果你的Model有很多属性需要暴露。你能想象为一个拥有20多个属性的Model创建属性吗?

相反,您应该在ViewModel中使用属性Model公开给View
public MyClass Model { get; private set; }

注意:这也可以实现INotifyPropertyChanged

你的Model中的属性应该实现INotifyPropertyChanged,或者在你的情况下,BindableBase

public class MyClass : BindableBase

你的View可以直接绑定到Model属性。这可能看起来像你打破了设计模式,但事实并非如此,你的View仍然不知道你的Model,但它只是假设它所期望的属性,因此你的类仍然是解耦的。

唯一的缺点是你的Model现在依赖于BindableBase,这不是世界末日,但如果你在一个情况下,你不能修改Model类,那么你目前的方法是走的路。

就像Mike Eason说的,公开你的模型是可以的,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;
    }
}

isvaluennew函数用于确定值是否不同。然后,您可以像下面这样使用它:

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.
            }
        }
    }
}

这是我能想到的最简单的方法来实现你所追求的。

我会让Subject属性的setter保持简单。

考虑先从你的模型中加载数据,然后将数据分配给你的视图模型属性。

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)
        }
    }
}

乌利希期刊指南

我决定在第一个评论之后添加一些解释。

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在里面执行。因此,调用SetPropertyOnPropertyChanged方法并不重要。