MVVM
不熟悉 DataGridView 编程的程序员在读取和写入值时往往会直接摆弄单元格。虽然这可行,但效率不高:您的代码将难以阅读、难以调试、难以更改和难以调试。
在现代编程中,倾向于将数据(= 模型)与向操作员呈现数据的方式(= 视图)分开。适配器类,通常称为 ViewModel,用于将模型连接到视图。这三个类一起缩写为 MVVM。考虑阅读一些关于此的背景信息。
将模型与视图分离的好处之一是,您可以更改视图而无需更改模型:如果您想在图表而不是表格中显示数据,您的数据不会必须改变。或者,如果您想在数据中添加额外的列,但您不需要显示它们,则您的视图不必更改。同样:如果您打算从文件而不是数据库中获取数据:视图中没有变化。
将模型与视图分离的其他优点是,您可以在显示数据其他方面的表单中重用模型,您可以在不使用表单的情况下对模型进行单元测试,在开发过程中您可以模拟模型,以在没有真实数据的情况下显示您的表单。
MVVM 和 DataGridView
唉,您没有指定要在 DataGridView 中显示的内容,因此假设您要显示 Products 的表格:
型号
class Product
{
public int Id {get; set;}
public string Name {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
public int StockCount {get; set;}
public int LocationId {get; set;} // foreign key to location
...
}
当然,您的模型需要一个过程来获取必须显示的产品:
class MyModel
{
IEnumerable<Product> FetchProducts()
{
// TODO implement; out of scope of the question
}
... // other Model methods
}
所以在模型中,您不会有与数据显示方式有任何关系的方法。
视图模型
假设,在此特定表单(此视图)上,您不想显示所有产品,而只想显示缺货的产品。这是将模型耦合到视图的典型适配器方法
class MyViewModel
{
private MyModel Model => ...
IEnumerable<Product> FetchOutOfStockProducts()
{
return this.Model.FetchProducts()
.Where(product => product.StockCount == 0);
}
}
如果以后您不想展示完全缺货的产品,但又想展示几乎缺货的产品,您只需更改此程序(并且也许是名字,为了更好的可读性)。
如果您真的想将模型与视图分开,请考虑创建一个类DisplayedProduct,该类内部包含一个Product。这使您有机会添加或隐藏产品的属性。将来您可以更改产品,而无需更改使用该产品的 20 种表格。您可以创建在内部使用产品以外的其他东西的 DisplayedProducts。或者如果你想添加一个属性:
public decimal TotalStockValue => this.Product.StockCount * this.Product.Price;
您是否需要创建 DisplayedProduct 类取决于显示的数据与您的模型有多少不同、您认为产品会改变的频率、产品在不同表单上显示的频率以及您期望视图的频率改变。
观点
这是有趣的部分。使用 Visual Studio 设计器,您添加了 DataGridView 和列。您需要告诉 DataGridView 哪一列应该显示 Product 的哪个值。
您也可以使用 Visual Studio 设计器执行此操作。如果您想确保在输入错误时编译器会抱怨,请在构造函数中执行以下操作:
public MyForm()
{
InitializeComponent();
// define which columns should show which properties of Product:
this.columnId.DataPropertyName = nameof(Product.Id);
this.columnName.DataPropertyName = nameof(Product.Name);
this.columnPrice.DataPropertyName = nameof(Product.Price);
... // etc.
当然,对于无论如何都不会显示的值,您不需要列。模型和视图分离的另一个优点:如果将来在您不需要显示的列中添加了一个属性,您的代码仍然可以工作。
您的表单还有一个保存 ViewModel 的属性:
private MyViewModel ViewModel => ...
您可以在构造函数中填写它,或者在每次需要时创建一个新的。您需要哪种方法取决于创建 ViewModel 的工作量。
定义显示哪些产品:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridViewProduct.DataSource;
set => this.dataGridViewProduct.DataSource = value;
}
当然,您的表单需要一个过程来显示初始产品集:
private IEnumerable<Product> FetchInitialProductsToShow()
{
return this.ViewModel.FetchOutOfStockProducts();
}
现在您要做的就是在加载表单时显示产品:
private void OnFormLoading(object sender, ...)
{
this.DisplayedProducts = new BindingList<Product>(
this.FetchInitialProductsToShow().ToList());
}
如果您在创建数据网格视图时允许,操作员可以添加/删除/更改产品。当他完成更改后,他可以通过单击“确定”或“立即应用”按钮通知您:
private void OnButtonOk_Clicked(object sender, ...)
{
ICollection<Product> editedProducts = this.DisplayedProducts;
// find out which Products are added / removed / changed
this.ProcessEditedProducts(editedProducts);
}
顺便说一句,您是否注意到,因为我将模型与视图分开,所以您表单中的大多数过程都是“单行”?
两个不错的方法:获取当前产品,如果允许多选:获取所有选择的产品:
private Product CurrentProduct =>
(Product) this.DataGridViewProducts.CurrentRow?.DataBoundItem;
private IEnumerable<Product> SelectedProducts =>
this.DataGridViewProducts.SelectedRows
.Cast<DataGridViewrow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();