C# 绑定到模型或视图模型
我知道已经有关于这个主题的问题了,但这些问题在某种程度上是针对其他问题的,并没有提供结论性的答案 特别是这里的:,当然所以请不要太快结束这个问题。他们回答说“做这个,做那个”,而不是为什么 有人否认需要一个C# 绑定到模型或视图模型,c#,wpf,mvvm,binding,C#,Wpf,Mvvm,Binding,我知道已经有关于这个主题的问题了,但这些问题在某种程度上是针对其他问题的,并没有提供结论性的答案 特别是这里的:,当然所以请不要太快结束这个问题。他们回答说“做这个,做那个”,而不是为什么 有人否认需要一个视图模型,并这样说 “标准”方式是直接绑定到模型。这是我所否认的,并试图用技术论证来证明 从我在MVC,MVP,演示模型的背景来看,这很自然 让我使用视图模型也许我错过了一个重要的要点? 因此,对于我来说,默认情况是绑定到ViewModel,而不管模型是什么(也不管它是否实现了INotifyP
视图模型
,并这样说
“标准”方式是直接绑定到模型。这是我所否认的,并试图用技术论证来证明
从我在MVC
,MVP
,演示模型
的背景来看,这很自然
让我使用视图模型
也许我错过了一个重要的要点?
因此,对于我来说,默认情况是绑定到ViewModel
,而不管模型是什么(也不管它是否实现了INotifyPropertyChanged
)
我认为绑定到ViewModel
s有几个原因,包括
(如此处和此处所述)
1。从视图中删除逻辑
- 使逻辑单元可测试
- 减少代码冗余(必要时重复)
2。安全性
- 模型包含用户不应更改的属性
- 如果绑定到模型,则可能会发生自动但不需要的更新
3。松耦合
- 如果直接绑定到模型,则较低层和视图之间将存在耦合
- 更改模型会导致所有视图中的更改
- 视图不依赖于任何给定的模型
- 模型可以通过EF、一些DSL、批处理文件等轻松生成
4。发展速度
- 您可以从
原型ViewModel
层次结构开始并绑定到该层次结构
- 如果模型仍在开发中,您可以从原型模型开始
Model
和ViewModel
可以通过测试驱动开发,无论视图如何
视图
完全可以由具有强大设计背景的设计师或开发人员构建
5。解决了“棘手的同步问题”
- 对于任何给定的“棘手的同步”问题,都有很多解决方案,例如
- 汽车制造商
- 模型中的事件系统(模型激发事件,视图模型订阅)
6。整个项目的平等结构
- 有些地方必须创建ViewModel,如SelectedItem
- 混合绑定到Model和ViewModel很容易出错
- 新开发人员很难弄清楚项目的结构
- 在没有办法解决的情况下,以后再开始使用ViewModel会很混乱
7。可扩展性
- 让我们定义:如果不使用ViewModel,则它不是MVVM
- MVVM可以很容易地应用于许多数据源、许多视图
- 如果发现任何性能问题:延迟加载和缓存将进入ViewModel
8。关注点分离
- 掌握复杂性是软件的主要问题
- ViewModels的唯一责任是推动更改
- 向视图发送通知与将其推送到不同的进程或机器一样容易
- ViewModel,而不是模型/数据源上更改通知的视图注册表
- 数据源可以忽略向ViewModel发送导致更改的事件
相反的,这个家伙在比赛中丢了一些分数,包括
如果直接更新模型,视图模型将不知道触发属性更改事件。这会导致UI不同步。
这严重限制了在父视图模型和子视图模型之间发送消息的选项
如果模型有自己的属性更改通知,#1和2不是问题。相反,如果包装器VM超出范围,但模型没有超出范围,则必须担心内存泄漏
如果您的模型很复杂,有很多子对象,那么您必须遍历整个树,并创建第二个对象图来遮挡第一个对象图。这可能非常乏味,而且容易出错
包装的集合尤其难以处理。任何时候(UI或后端)从集合中插入或删除项时,都需要更新卷影集合以匹配。这种代码很难正确编写
那么,问题是:默认的绑定方式是什么?为什么?
我是否遗漏了需要使用ViewModel的要点
是否有任何想绑定到模型的真正原因
最重要的是为什么,而不是如何
所以,问题是:绑定的默认方式是什么?为什么
一般来说,我认为拥有一个ViewModel并绑定到它是默认的。“ViewModel”的存在是有原因的,它是MVVM模式的一部分
除了纯粹的数据之外,还存在需要ViewModel的其他原因。您还通常实现特定于应用程序的逻辑(即:不是模型的一部分,但在应用程序中是必需的)。例如,任何ICommand
实现都应该在ViewModel上,因为它与模型完全无关
有没有什么真正的理由想绑定到一个模型
在某些情况下,它可能更简单,特别是如果您的模型已经实现了INotifyPropertyChanged
。降低代码复杂性是一个有价值的目标,有其自身的优点。视图模型通常包含用于视图的成员(例如,IsSomethingSelected
,IsSomethingExpandedpublic 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(); }
}
}
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; }
}