【问题标题】:mvvm solving nested models and viewmodelsmvvm 解决嵌套模型和视图模型
【发布时间】:2020-07-05 16:11:03
【问题描述】:

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

        public string Name
        {
            get { return _model.Name; }
            set
            {
                _model.Name = value.Trim();
                RaisePropertyChanged("Name");
            }
        }

但是如果我有一个模型列表也有一个模型列表,现在会发生什么。 示例:拥有待办事项列表的用户。那我现在能做什么?我的第一个想法是创建UserViewModelTodoViewModel。所以我有一个ObservableCollection<UserViewModel>,每个UserViewModel 都有一个ObservableCollection<TodoViewModel>

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

我太严格了吗?为什么我在这里需要 ViewModel?我在这里想念什么?为什么不在模型中实现INotifyPropertyChanged? Eventhandling 和 Commands 是不同的文件,我可以将参数绑定到它们。所以模型不知道视图,视图不需要视图模型?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

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

    **这是什么意思? **

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

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

    MVVM 模式的更多强迫症版本是 MVPVM(模型-视图-演示者-视图模型),其中命令和数据操作在演示者内完成,而不是在视图模型上完成,这使得视图模型严格作为数据绑定。

    【讨论】:

    • 但是使用你的 UserListUserListViewModel 方法,我将有两个对象用于“1”事物。如果我只有一个实现INotifyPropertyChanged 并将属性绑定到视图的UserList 模型,例如,用户添加的将是UserAddCommand.cs,我也可以将其绑定到视图。代码将是单独的。也许 MVPVM 更适合我的思维方式。
    • 不,我的意思是 UserList 作为您的视图。那是你在 WPF 中的 XAML 文件
    • 好的,谢谢。所以没有“真正的”模型(人们在学校学习的方式)。 ViewModel 拥有像 NameColorSize 这样的属性,而 DTO 可以是像 TodoSaveService 这样的服务类?
    • 是的,如果你真的坚持模型-视图-视图模型的概念,MVVM 中的模型是广泛的。您可以拥有一个 POCO(普通旧 CLR 对象),例如 User,其中包含用户属性,但您不应直接绑定到视图中的那些,而是公开访问 User 属性的视图模型属性,以便您确定应用了INotifyPropertyChangedTodoSaveService 应该在您的 UserAddCommand 中调用。不知道你为什么添加.cs,但据我所知,命令是作为视图模型属性公开的。
    • 在旁注中,您应该检查What is the Difference Between a DTO and a POCO?,因为这些术语在 .NET 编程中广泛使用。还请查看Design Patterns by Tutorials: MVVM 有时我们会感到困惑,因为即使它们是简单的概念,我们也不知道术语的定义
    【解决方案2】:

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

    在 MVVM 的“严格”实现中,模型没有属性。 她只有方法和事件。 ViewModel 调用公共方法并将所需的值传递给它。 之后模型的状态是否会改变 - ViewModel 不知道。 如果状态发生了变化,那么模型会引发一个事件。 收到此事件后,ViewModel 会读取必要的数据并更新其属性,这些属性用于在 View 中进行绑定。

    演示代码:

    型号

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

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

    视图模型

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

    【讨论】:

    • 我很困惑。它看起来像一个圈子通话,查看通话ViewModelName.Name = "NewName" -> Set() -> OnValueChange() -> model.SendName() -> NameChangedEvent.Invoke() -> NameChangedMethod() -> Name = "NewName -> Set()。还是我错过了什么?
    • 新旧含义的比较无处不在:在模型中和视图模型中。如果值没有改变,则不再进行调用。在这种情况下,使用赋值检查是一种典型的实现方式。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2011-05-20
      • 1970-01-01
      • 2010-12-07
      • 2011-07-29
      • 1970-01-01
      • 2017-04-10
      • 2018-03-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多