C# mvvm解决嵌套模型和视图模型

C# mvvm解决嵌套模型和视图模型,c#,wpf,mvvm,C#,Wpf,Mvvm,我试图理解何时使用模型,何时查看模型等等。尤其是嵌套列表。所以我的viewmodels处理事件和命令,并在basic中修改模型。。。因此,在我的基本示例中,它看起来主要像是viewmodel引用了模型和委托属性 public string Name { get { return _model.Name; } set { _model.Name = value.T

我试图理解何时使用模型,何时查看模型等等。尤其是嵌套列表。所以我的viewmodels处理事件和命令,并在basic中修改模型。。。因此,在我的基本示例中,它看起来主要像是viewmodel引用了模型和委托属性

        public string Name
        {
            get { return _model.Name; }
            set
            {
                _model.Name = value.Trim();
                RaisePropertyChanged("Name");
            }
        }
但是如果我有一个模型列表,它也有一个模型列表,那么现在会发生什么呢。 示例:具有待办事项列表的用户。那我现在能做什么呢?我的第一个想法是创建
UserViewModel
&
TodoViewModel
。因此,我有一个
observeCollection
,每个
UserViewModel
都有一个
observeCollection

每次添加Todo时,我都会创建
Todo
TodoViewModel
将Todo对象绑定到
TodoViewModel
等等?这听起来像是一个大过载的对象


我要严格吗?为什么这里需要ViewModels?我错过了什么?为什么不在模型中实施InotifyProperty更改?事件处理和命令是不同的文件,我可以将参数绑定到它们。所以模型不知道视图,视图也不需要视图模型?

我不确定最好的方法,但是如果模型需要一些特殊的逻辑来处理,我会为模型创建特殊的视图模型。若它并没有特殊的逻辑,或者使用它需要两行代码,那个么我在高级视图模型中做所有的工作,就像你们案例中的用户一样。然后,如果它变大了,我会重构它,并将代码移到嵌套视图模型中。

我不确定最好的方法,但如果模型需要一些特殊的逻辑来使用它,我会为它创建特殊的视图模型。若它并没有特殊的逻辑,或者使用它需要两行代码,那个么我在高级视图模型中做所有的工作,就像你们案例中的用户一样。然后,如果它变大,我会重构它,并将代码移动到嵌套视图模型。

视图模型仅用于视图。它充当模型和视图之间的粘合代码

**这是什么意思**

例如,您有一个
用户列表
页面或表单(视图)。然后您将拥有一个
UserListViewModel
,它保存所有DTO(数据传输对象)。您不应直接将这些DTO作为属性公开,而应作为带有属性的字段公开,这些属性遵循数据绑定的
INotifyPropertyChanged
实现。在视图模型中,您可以向视图公开命令(委托),以执行诸如http请求、数据处理、I/O等操作。但是,对于DTO的任何更改,您应该正确地通知视图,以便视图知道发生了什么,如果他们需要知道的话

不要将INotifyPropertyChanged应用于视图模型中的每个模型或DTO,因为正如您所说,这只是另一种开销


MVVM模式的一个更具OCD的版本是MVPVM(模型-视图-演示者-视图模型),其中命令和数据操作在演示者内部完成,而不是在视图模型上完成,视图模型严格地作为数据绑定的模型。

视图模型仅用于视图。它充当模型和视图之间的粘合代码

**这是什么意思**

例如,您有一个
用户列表
页面或表单(视图)。然后您将拥有一个
UserListViewModel
,它保存所有DTO(数据传输对象)。您不应直接将这些DTO作为属性公开,而应作为带有属性的字段公开,这些属性遵循数据绑定的
INotifyPropertyChanged
实现。在视图模型中,您可以向视图公开命令(委托),以执行诸如http请求、数据处理、I/O等操作。但是,对于DTO的任何更改,您应该正确地通知视图,以便视图知道发生了什么,如果他们需要知道的话

不要将INotifyPropertyChanged应用于视图模型中的每个模型或DTO,因为正如您所说,这只是另一种开销


MVVM模式的一个更加OCD的版本是MVPVM(Model View Presenter View Model),其中命令和数据操作在Presenter内完成,而不是在视图模型上完成,这使得视图模型严格地作为数据绑定的模型。

如果严格遵循MVVM的概念,那么,连接值和模型的属性的实现就不正确了。 此实现假定ViewModel具有关于模型内部结构和功能的“知识”。 而ViewModel不应该有这样的知识

在MVVM的“严格”实现中,模型没有属性。 她只有方法和事件。 ViewModel调用public方法并将所需的值传递给它。 之后模型的状态是否会更改-ViewModel不知道。 如果状态已更改,则模型将引发事件。 收到此事件后,ViewModel将读取必要的数据并更新其属性,这些属性用于视图中的绑定

演示代码:

型号

public delegate void NameChangedHandler(object sender, string newName);
public class ModelName
{

    public event NameChangedHandler NameChangedEvent;

    private string name;
    public void SendName(string name)
    {
        // Some business logic to handle the accepted value.
        // You can transfer to the server, change other values ​​and the like.
        // In this example is simply stored in a private field.
        name = name.Trim();
        if (this.name != name)
        {
            this.name = name;
            NameChangedEvent?.Invoke(this, this.name);
        }
    }
}
public class ViewModelName : BaseINPC
{
    private ModelName model = new ModelName();
    public ViewModelName()
        => model.NameChangedEvent += NameChangedMethod;

    private void NameChangedMethod(object sender, string newName) 
        => Name = newName;

    private string _name;
    public string Name { get => _name; set => Set(ref _name, value); }

    protected override void OnValueChange<T>(ref T oldValue, T newValue, string propertyName)
    {
        base.OnValueChange(ref oldValue, newValue, propertyName);

        if (propertyName == nameof(Name))
            model.SendName(Name);
    }
}
INPC的基本实施

/// <summary>Base class implementing INotifyPropertyChanged.</summary>
public abstract class BaseINPC : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>Called AFTER the property value changes.</summary>
    /// <param name="propertyName">The name of the property.
    /// In the property setter, the parameter is not specified. </param>
    public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    /// <summary> A virtual method that defines changes in the value field of a property value. </summary>
    /// <typeparam name = "T"> Type of property value. </typeparam>
    /// <param name = "oldValue"> Reference to the field with the old value. </param>
    /// <param name = "newValue"> New value. </param>
    /// <param name = "propertyName"> The name of the property. If <see cref = "string.IsNullOrWhiteSpace (string)" />,
    /// then ArgumentNullException. </param> 
    /// <remarks> If the base method is not called in the derived class,
    /// then the value will not change.</remarks>
    protected virtual void Set<T>(ref T oldValue, T newValue, [CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrWhiteSpace(propertyName))
            throw new ArgumentNullException(nameof(propertyName));

        if ((oldValue == null && newValue != null) || (oldValue != null && !oldValue.Equals(newValue)))
            OnValueChange(ref oldValue, newValue, propertyName);
    }

    /// <summary> A virtual method that changes the value of a property. </summary>
    /// <typeparam name = "T"> Type of property value. </typeparam>
    /// <param name = "oldValue"> Reference to the property value field. </param>
    /// <param name = "newValue"> New value. </param>
    /// <param name = "propertyName"> The name of the property. </param>
    /// <remarks> If the base method is not called in the derived class,
    /// then the value will not change.</remarks>
    protected virtual void OnValueChange<T>(ref T oldValue, T newValue, string propertyName)
    {
        oldValue = newValue;
        RaisePropertyChanged(propertyName);
    }

}
///实现INotifyPropertyChanged的基类。
公共抽象类BaseINPC:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
///在属性值更改后调用。
///属性的名称。
///在属性设置器中,未指定参数。
public void RaisePropertyChanged([CallerMemberName]字符串propertyName=”“)
=>PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName));
///定义属性值的值字段中的更改的虚拟方法。
///属性值的类型。
///引用具有旧值的字段。
///新的价值观。
///属性的名称。如果,
///然后是一个例外。
///如果派生类中未调用基方法,
///那么该值将不会更改。
受保护的虚拟无效集(参考T oldV