关于c#:绑定到Model或ViewModel

Binding to Model or ViewModel

我知道已经有关于这个主题的问题,但是这些问题在某种程度上是特定于其他问题的,并没有提供确切的答案。

尤其是这里的问题:问题1,问题2,当然还有问题3,所以请不要太快地结束这个问题。他们回答说"做这个,做那个",而不是为什么!

有人否认需要一个ViewModel,并说"标准"方法是直接绑定到模型。这是我所否认的,并试图用技术论证来证明。

从我在MVCMVPPresentation Model的背景来看,这是很自然的。我要用一个ViewModel。也许我错过了重要的一点?

因此,对我来说,无论Model是什么(无论它是否实现INotifyPropertyChanged),默认都是绑定到ViewModel

我认为绑定到EDOCX1[0]有几个原因,包括(如本文所述,codeproject和另一篇文章所述)

1。从视图中删除逻辑

  • 使逻辑单元可测试
  • 减少代码冗余(需要时重复)

2。安全性

  • 模型包含用户不应更改的属性
  • 自动,但如果绑定到模型,可能会发生不需要的更新

三。松耦合

  • 如果直接绑定到模型,则下层和视图之间将存在耦合。
  • 更改模型会导致所有视图发生更改
  • 视图不依赖于任何给定的模型
  • 模型可以很容易地用ef、一些DSL、批处理文件等生成。

4。发展速度

  • 您可以从Prototype ViewModel层次结构开始并绑定到该层次结构
  • 如果模型仍在开发中,可以从Prototype Model开始。
  • 无论从哪个角度看,都可以开发测试驱动的ModelViewModel
  • View完全可以由设计人员或具有强大设计背景的开发人员建造。

5。棘手的同步问题解决了

  • 对于任何给定的"棘手的同步"问题,都有很多解决方案,例如
  • 自动装订机
  • 模型中的事件系统(模型激发事件,视图模型订阅)

6。整个项目的平等结构

  • 有些地方需要视图模型,比如selecteditem
  • 混合绑定到模型和视图模型很容易出错
  • 新开发人员很难搞清楚项目的结构。
  • 在没有办法解决的情况下,稍后开始引入ViewModel

7。可扩展性

  • 让我们定义:如果不使用ViewModel,则它不是MVVM
  • MVVM可以很容易地应用于许多数据源、许多视图
  • 如果发现任何性能问题:延迟加载和缓存将进入ViewModel

8。关注点分离

  • 掌握复杂性是软件的主要问题
  • 视图模型的唯一责任是推动变化。
  • 向视图发送通知与将其推送到其他进程或计算机一样容易。
  • 视图模型,而不是模型/数据源上更改通知的视图寄存器
  • 数据源可以忽略向导致更改的ViewModel发送事件

相反,另一个线程的家伙会转储一些点,包括

  • 如果直接更新模型,视图模型将不知道是否触发属性更改事件。这会导致UI不同步。这严重限制了在父视图模型和子视图模型之间发送消息的选项。

  • 如果模型有自己的属性更改通知,1和2不是问题。相反,如果包装虚拟机超出范围,但模型没有,则必须担心内存泄漏。

  • 如果您的模型很复杂,有很多子对象,那么您必须遍历整棵树,并创建第二个对象图来隐藏第一个对象。这可能非常乏味且容易出错。

  • 打包的集合尤其难以处理。每当有东西(UI或后端)从集合中插入或删除项目时,需要更新阴影集合以匹配。这种代码很难纠正。

  • 所以,问题是:默认的绑定方式是什么,为什么?

    我是否错过了一些需要建立视图模型的要点?

    有没有什么真正的理由要绑定到模型上?

    最重要的是为什么,而不是如何。


    视图模型通常包含用于视图的成员(如IsSomethingSelectedIsSomethingExpandedIsSomethingVisibleICommand的任何实现)。

    你有什么理由把这些东西都带到模特身上吗?当然没有。这就是视图模型存在的原因。


    So, the question is: what is the default way to bind and why?

    一般来说,我认为拥有一个视图模型并绑定到它是默认的。"viewModel"存在的原因之一就是它是MVVM模式的一部分。

    除了纯粹的数据之外,还有其他的原因需要一个视图模型。您还通常实现特定于应用程序的逻辑(即:不是模型的一部分,而是应用程序所必需的)。例如,任何ICommand实现实际上都应该在ViewModel上,因为它与模型完全无关。

    Are there any real reasons one would like to bind to a model?

    在某些情况下,它可能更简单,特别是当您的模型已经实现了INotifyPropertyChanged时。降低代码复杂性是一个有价值的目标,有其自身的优点。


    计数器参数:

  • Removing logic from the View
  • 从视图模型中删除逻辑同样有用。通过将验证、计算字段等逻辑推到模型中,您可以得到一个更轻、更清晰的视图模型。

    ?Make logic unit testable

    模型本身非常容易进行单元测试。您不必担心模仿库,也不必担心使用处理外部服务的视图模型。

    ?reduce code redundance (duplication where needed)

    多个视图模型可以共享同一个模型,从而减少验证、计算字段等的冗余。

  • Security
    ?The model contains properties that the user shall not change
  • 然后不要在用户界面上公开它们。

    ?Automatic, but unwanted updates can happen if Binding to model

    这一切都没有道理。如果你的虚拟机只是一个围绕你的模型的包装器,那么它无论如何都会把这些更新推下去。

  • Loose coupling
    ?If binding directly to the model, there will be a coupling between lower layers and View
  • 当您在它们之间推送一个包装器VM时,这种耦合不会神奇地消失。

    ?Changing the Model causes changes in all the Views

    更改模型会导致所有包装视图模型发生更改。更改视图模型也会导致所有视图发生更改。因此,模型仍然可以导致所有视图发生更改。

    ?The view is not depending on any given model

    无论视图模型是否包装模型,这都是正确的。它只看到属性,而不是实际的类。

    ?Model can be easily generated with EF, some DSL, batch files, on so on

    是的。通过一点工作,这些容易生成的模型可以包括有用的接口,比如inotifyDataErrorInfo、icChangeTracking和ieditableObject。

  • Speed of development
  • 绑定到模型提供了更快的开发,因为您不必映射所有属性。

    ?You can start with a Prototype ViewModel hierarchy and bind to that

    或者我可以从一个原型模型开始。通过添加包装器什么也得不到。

    ?If the model is still under development, you can start with a Prototype Model
    ?Model and ViewModel can be developed testdriven, no matter of the View

    同样,在模型周围添加一个包装器并不能获得任何东西。

    ?视图完全可以由设计师或具有强大设计背景的开发人员构建。

    同样,在模型周围添加一个包装器并不能获得任何好处。

  • "Tricky synchronizion" is solved
    ?There are plenty of solutions to any given"tricky synchronization" problem, e.g.
    ?AutoMapper
  • 如果使用automapper将数据复制到视图模型中,那么就不会使用mvvvm模式。您只是在使用视图和模型。

    ?Event system from the model (model fires event, ViewModel subscribes)

    你好,内存泄露。也就是说,除非您非常小心,并且放弃跨多个视图共享模型的能力。

  • Equal structure throughout the project
    ?There are points where have to a ViewModel, like SelectedItem
  • 无关紧要。没有人反对非包装视图模型。

    ?Mixing Binding to Model and ViewModel is errorprone

    不支持。

    ?It is harder for fresh developers to figure out structure of the project

    不支持。

    ?starting to bring ViewModel later when there is no way around it is messy

    无关紧要。同样,没有人反对使用非包装视图模型。

  • Scalability
    ?Lets define: If you do not use a ViewModel, it is not MVVM
  • 无关紧要。第三次,没有人反对使用非包装视图模型。

    ?MVVM can be easily adopted to lots of datasources, lots of views

    无关紧要。我们不是在争论是否使用MVVM,而是在争论如何最好地使用它。

    ?If you find any performance issues: Lazy loading and caching goes in the ViewModel

    同意,但不相干。没有人建议您将服务调用推送到模型中。

  • Separation of concerns
  • 这是包装视图模型下降最困难的地方。

    视图模型已经必须处理UI数据(例如,模式、选定项)并承载调用外部服务的ICommand。

    将所有的模型数据、验证逻辑、计算属性等推送到视图模型中会使操作更加繁琐。


    你的问题没有"正确"的答案。当然,WPF会很高兴地允许您绑定到您声明为"模型"对象的任何对象;框架根本不关心。您不必总是遵循MVVM模式,因为您在WPF中执行应用程序。上下文始终是您编写的任何软件的关键。如果你时间紧迫,需要一个快速的原型,无论如何绑定到模型和重构,如果/当你需要的时候。

    所以我想你真正要问的是"我什么时候应该使用MVVM模式?"

    答案当然是,"当你的问题符合模式时"。

    那么MVVM模式给了你什么呢?您已经列出了使用MVVM的几个原因,但是模式最重要的一个原因是松耦合——所有其他的排序都是这样的。

    MVVM模式的整个要点是确保您的模型是一个状态机,它不知道如何向用户显示数据或从用户那里获取数据。在某种程度上,您的模型是以对模型有意义的格式构造的纯数据,而不是以对人类有意义的格式构造的。您的视图模型负责在纯数据平台(模型)和用户输入平台(视图)之间进行转换。视图模型与视图和模型紧密耦合,但重要的是模型对视图一无所知。


    这里是一个简单的对象图。只是一些非常简单的模型,具有正常的属性更改和验证事件。

    那些认为模型需要包装在视图模型中的人会显示您的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

            OnErrorChanged(propertyName);
        }

        protected void OnErrorChanged(string propertyName)
        {
            if (ErrorsChanged != null)
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public virtual IEnumerable GetErrors(string propertyName)
        {
            return Enumerable.Empty<string>();
        }

        public virtual bool HasErrors
        {
            get { return false; }
        }
    }

    public class Customer : ModelBase
    {
        public Customer()
        {
            Orders.CollectionChanged += Orders_CollectionChanged;
        }

        void Orders_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems.Count > 0)
                foreach (INotifyPropertyChanged item in e.OldItems)
                    item.PropertyChanged -= Customer_PropertyChanged;

            if (e.NewItems.Count > 0)
                foreach (INotifyPropertyChanged item in e.NewItems)
                    item.PropertyChanged += Customer_PropertyChanged;

            OnPropertyChanged("TotalSales");
        }

        void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName =="Total")
                OnPropertyChanged("TotalSales");
        }

        public decimal TotalSales
        {
            get { return Orders.Sum(o => o.Total); }
        }

        private string _FirstName;
        public string FirstName
        {
            get { return _FirstName; }
            set
            {

                if (_FirstName == value)
                    return;
                _FirstName = value;
                OnPropertyChanged();
            }
        }

        private string _LastName;
        public string LastName
        {
            get { return _LastName; }
            set
            {

                if (_LastName == value)
                    return;
                _LastName = value;
                OnPropertyChanged();
            }
        }



        private readonly ObservableCollection<Order> _Orders = new ObservableCollection<Order>();
        public ObservableCollection<Order> Orders
        {
            get { return _Orders; }
        }

    }

    public class Order : ModelBase
    {
        public Order()
        {
            OrderLines.CollectionChanged += OrderLines_CollectionChanged;
        }

        void OrderLines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems.Count > 0)
                foreach (INotifyPropertyChanged item in e.OldItems)
                    item.PropertyChanged -= OrderLine_PropertyChanged;

            if (e.NewItems.Count > 0)
                foreach (INotifyPropertyChanged item in e.NewItems)
                    item.PropertyChanged += OrderLine_PropertyChanged;

            OnPropertyChanged("Total");
            OnErrorChanged("");
        }

        public override bool HasErrors
        {
            get { return GetErrors("").OfType<string>().Any() || OrderLines.Any(ol => ol.HasErrors); }
        }

        void OrderLine_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName =="Extension")
                OnPropertyChanged("Total");
        }

        public decimal Total
        {
            get { return OrderLines.Sum(o => o.Extension); }
        }

        private int _OrderNumber;
        private DateTime _OrderDate;

        public DateTime OrderDate
        {
            get { return _OrderDate; }
            set
            {
                if (_OrderDate == value)
                    return;
                _OrderDate = value;
                OnPropertyChanged();
            }
        }
        public int OrderNumber
        {
            get { return _OrderNumber; }
            set
            {
                if (_OrderNumber == value)
                    return;
                _OrderNumber = value;
                OnPropertyChanged();
            }
        }

        private readonly ObservableCollection<OrderLine> _OrderLines = new ObservableCollection<OrderLine>();
        public ObservableCollection<OrderLine> OrderLines
        {
            get { return _OrderLines; }
        }

    }

    public class OrderLine : ModelBase
    {
        private string _ProductName;
        private decimal _Quantity;
        private decimal _Price;
        public decimal Price
        {
            get { return _Price; }
            set
            {
                if (_Price == value)
                    return;
                _Price = value;
                OnPropertyChanged();
            }
        }
        public string ProductName
        {
            get { return _ProductName; }
            set
            {
                if (_ProductName == value)
                    return;
                _ProductName = value;
                OnPropertyChanged();
                OnPropertyChanged("Extension");
            }
        }
        public decimal Quantity
        {
            get { return _Quantity; }
            set
            {
                if (_Quantity == value)
                    return;
                _Quantity = value;
                OnPropertyChanged();
                OnPropertyChanged("Extension");
            }
        }
        public decimal Extension
        {
            get { return Quantity * Price; }
        }

        public override IEnumerable GetErrors(string propertyName)
        {
            var result = new List<string>();

            if ((propertyName =="" || propertyName =="Price") && Price < 0)
                result.Add("Price is less than 0.");
            if ((propertyName =="" || propertyName =="Quantity") && Quantity < 0)
                result.Add("Quantity is less than 0.");

            return result;
        }

        public override bool HasErrors
        {
            get { return GetErrors("").OfType<string>().Any(); }
        }
    }

    下面是一个典型的视图模型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class CustomerViewModel : ModelBase
    {
        public CustomerViewMode()
        {
            LoadCustomer = null; //load customer from service, database, repositry, etc.
            SaveCustomer = null; //save customer to service, database, repositry, etc.
        }

        private Customer _CurrentCustomer;

        public Customer CurrentCustomer
        {
            get { return _CurrentCustomer; }
            set
            {
                if (_CurrentCustomer == value)
                    return;
                _CurrentCustomer = value;
                OnPropertyChanged();
            }
        }
        public ICommand LoadCustomer { get; private set; }
        public ICommand SaveCustomer { get; private set; }

    }


    我同意里德的观点——视图模型是人们应该绑定的。我总是把模型想象成一组或多或少静态的值,这些值可能不会像ViewModel那样频繁或动态地变化。一般来说,我尝试将任何具有可以在模型编译时假定的值的东西,以及在运行时在ViewModel中确定的东西放在一起。

    视图本身不应该有任何超过最简单的逻辑。其余的应该是对ViewModel的引用。安全性有时是个问题,但我喜欢这样做只是为了代码的可读性和简洁性。当所有的美学事物都在视图中完成时,处理代码就容易得多,所有的数学、逻辑事物都隐藏在视图模型中,所有的硬数据都在一个单独的模型中。

    MVVM与MVC也有着密切的关系,其指导原则是模型和视图不应该直接看到彼此。再一次,对我来说,这是一件很清楚的事情。确定模型值应该如何更改的逻辑也应该在ViewModel/控制器中。这种观点本身不应该考虑。

    把这个视图想象成一个接待员:它是一个友好的界面(与用户对话)。ViewModel是前台后面办公室的会计,模型是他/她的一套参考书和笔记。如果接待员开始在会计账簿的空白处写东西,抹掉会计的笔记,改变记录,事情就会变得混乱起来。