int[]或List<int>到WinForms中DataGridView的双向数据绑定
Two-way data binding of int[] or List<int> to DataGridView in WinForms
此解决方案有助于填充 dgv,但由于匿名类型是不可变的,因此无法编辑此类网格,但即便如此,我认为它对双向绑定也没有帮助
List<int> values = GetValues();
var dataGridView = new DataGridView();
dataGridView.DataSource = values.Select(el => new { Value = el }).ToList();
使用包装器 class 允许我编辑单元格,但更改不会反映在原始集合中
public class Wrapper<T>
{
public T Value { get; set; }
public Wrapper(T value) => Value = value;
}
...
dataGridView.DataSource = new BindingList<Wrapper<int>>(values.Select(value => new Wrapper<int>(value)).ToList());
那么你是如何让它发挥作用的呢?
双向数据绑定基于数据源的更改通知工作。这里您的数据源是 List<int>
,List<T>
和 int
都不会引发更改通知,因此双向数据绑定在这里没有意义。
假设您想要实现 BindingList<int>
与 DataGridView
的双向数据绑定,您可以执行以下操作:
BindingList<int> originalBindingList;
private void Form1_Load(object sender, EventArgs e)
{
originalBindingList = new BindingList<int>(new List<int> { 1, 2, 3 });
dataGridView1.DataSource = new ListDataSource<int>(originalBindingList);
}
然后:
DataGridView
将显示 Value
列和包含 originalBindingList
值的行。
- 如果您更改
DataGridView
中的值,originalBindingList
中的值也会更改。
- 如果您在代码中
originalBindingList
更改值,DataGridView
将刷新值。
这里是 ListDataSource:
public class ListDataSource<T> : BindingSource
{
public ListDataSource(BindingList<T> original)
{
for (int i = 0; i < original.Count; i++)
{
this.Add(new Item(original, i));
}
original.ListChanged += (obj, args) =>
this.OnListChanged(new ListChangedEventArgs(
args.ListChangedType, args.NewIndex));
}
private class Item : INotifyPropertyChanged
{
IList<T> source;
int index;
public Item(IList<T> source, int index)
{
this.source = source;
this.index = index;
}
public T Value
{
get { return source[index]; }
set
{
source[index] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
匿名类型没有设置 属性,因此如果操作员编辑显示的单元格之一,您将无法自动更新数据源。
我不确定将数据模型与其显示方式交织在一起是否明智。多年来,人们倾向于将数据与显示分开。这种分离通常以 View 和 Model 等术语命名,或者以极端形式命名:MVVM。
这种分离让您更容易理解您的代码,如果您想以不同的方式显示数据(一个只显示一列数据的列表框,或者一个图表而不是 table,则可以重用您的数据) ETC)。对你的模型进行单元测试要容易得多,如果你需要对你的模型进行小的改动,你的显示不必改变,只要你提供相同的界面。
很多优点。唯一的缺点:更多的打字。但话又说回来:没有人说 OO 编程的初始版本会减少打字。重用和更容易的更改将从 OO 中受益。
所以是的,如果您有一个显示客户的 DataGridView,则必须创建一个 class 客户,这是一种获取客户初始序列的方法(从数据库?从互联网?);一种显示客户和处理已编辑客户的方法。
通常您将具有如下所示的函数和 classes。示例:在产品订单系统中,显示一系列 OrderLines:
class OrderLine {...}
IEnumerable<OrderLine> FetchOrderLines(){...}
void ProcessOrder(Customer customer, IEnumerable<OrderLine> orderLines) {...}
以及与您的 DataGridView 的交互:
BindingList<OrderLine> DisplayedOrderLines
{
get => (BindingList<OrderLine>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
Customer CurrentOrderLine => (OrderLine)this.dataGridView1.CurrentRow.DataBoundItem;
IEnumerable<OderLine> SelectedOrderLines => this.dataGridView1.SelectedRows
.Select(row => row.DataBoundItem)
.Cast<OrderLine>();
// if you want to detect which displayed lines are chnged:
IEqualityComparer<OrderLine> OrderLineComparer => ...
用法:
void OnButtonOk_Clicked(object sender, ...)
{
ICollection<OrderLine> orderLines= this.Displayedustomers;
// if needed: detect which Customers are added / removed / changed
// for this, use the original data and the OrderLineComparer
this.ProcessOrder(this.Customer, orderLines);
}
想想看:您唯一需要的额外编辑是 class OrderLine
.
模型与视图的分离将大大节省您的单元测试:您可以测试模型中的所有内容,但无需启动表单即可测试数据的显示。
为不同的用途重用您的模型也更容易:您可以使用 class OrderLine 将其保存在存储库中,这可以为您隐藏它是保存在数据库中,还是保存在 XML 文件,它也允许您更改它,将来可能需要这个。
所以:然而,分离模型和视图最初需要一些额外的输入,但如果您希望您的软件必须在多个版本中存活,我的建议是分离!
此解决方案有助于填充 dgv,但由于匿名类型是不可变的,因此无法编辑此类网格,但即便如此,我认为它对双向绑定也没有帮助
List<int> values = GetValues();
var dataGridView = new DataGridView();
dataGridView.DataSource = values.Select(el => new { Value = el }).ToList();
使用包装器 class 允许我编辑单元格,但更改不会反映在原始集合中
public class Wrapper<T>
{
public T Value { get; set; }
public Wrapper(T value) => Value = value;
}
...
dataGridView.DataSource = new BindingList<Wrapper<int>>(values.Select(value => new Wrapper<int>(value)).ToList());
那么你是如何让它发挥作用的呢?
双向数据绑定基于数据源的更改通知工作。这里您的数据源是 List<int>
,List<T>
和 int
都不会引发更改通知,因此双向数据绑定在这里没有意义。
假设您想要实现 BindingList<int>
与 DataGridView
的双向数据绑定,您可以执行以下操作:
BindingList<int> originalBindingList;
private void Form1_Load(object sender, EventArgs e)
{
originalBindingList = new BindingList<int>(new List<int> { 1, 2, 3 });
dataGridView1.DataSource = new ListDataSource<int>(originalBindingList);
}
然后:
DataGridView
将显示Value
列和包含originalBindingList
值的行。- 如果您更改
DataGridView
中的值,originalBindingList
中的值也会更改。 - 如果您在代码中
originalBindingList
更改值,DataGridView
将刷新值。
这里是 ListDataSource:
public class ListDataSource<T> : BindingSource
{
public ListDataSource(BindingList<T> original)
{
for (int i = 0; i < original.Count; i++)
{
this.Add(new Item(original, i));
}
original.ListChanged += (obj, args) =>
this.OnListChanged(new ListChangedEventArgs(
args.ListChangedType, args.NewIndex));
}
private class Item : INotifyPropertyChanged
{
IList<T> source;
int index;
public Item(IList<T> source, int index)
{
this.source = source;
this.index = index;
}
public T Value
{
get { return source[index]; }
set
{
source[index] = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
匿名类型没有设置 属性,因此如果操作员编辑显示的单元格之一,您将无法自动更新数据源。
我不确定将数据模型与其显示方式交织在一起是否明智。多年来,人们倾向于将数据与显示分开。这种分离通常以 View 和 Model 等术语命名,或者以极端形式命名:MVVM。
这种分离让您更容易理解您的代码,如果您想以不同的方式显示数据(一个只显示一列数据的列表框,或者一个图表而不是 table,则可以重用您的数据) ETC)。对你的模型进行单元测试要容易得多,如果你需要对你的模型进行小的改动,你的显示不必改变,只要你提供相同的界面。
很多优点。唯一的缺点:更多的打字。但话又说回来:没有人说 OO 编程的初始版本会减少打字。重用和更容易的更改将从 OO 中受益。
所以是的,如果您有一个显示客户的 DataGridView,则必须创建一个 class 客户,这是一种获取客户初始序列的方法(从数据库?从互联网?);一种显示客户和处理已编辑客户的方法。
通常您将具有如下所示的函数和 classes。示例:在产品订单系统中,显示一系列 OrderLines:
class OrderLine {...}
IEnumerable<OrderLine> FetchOrderLines(){...}
void ProcessOrder(Customer customer, IEnumerable<OrderLine> orderLines) {...}
以及与您的 DataGridView 的交互:
BindingList<OrderLine> DisplayedOrderLines
{
get => (BindingList<OrderLine>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
Customer CurrentOrderLine => (OrderLine)this.dataGridView1.CurrentRow.DataBoundItem;
IEnumerable<OderLine> SelectedOrderLines => this.dataGridView1.SelectedRows
.Select(row => row.DataBoundItem)
.Cast<OrderLine>();
// if you want to detect which displayed lines are chnged:
IEqualityComparer<OrderLine> OrderLineComparer => ...
用法:
void OnButtonOk_Clicked(object sender, ...)
{
ICollection<OrderLine> orderLines= this.Displayedustomers;
// if needed: detect which Customers are added / removed / changed
// for this, use the original data and the OrderLineComparer
this.ProcessOrder(this.Customer, orderLines);
}
想想看:您唯一需要的额外编辑是 class OrderLine
.
模型与视图的分离将大大节省您的单元测试:您可以测试模型中的所有内容,但无需启动表单即可测试数据的显示。
为不同的用途重用您的模型也更容易:您可以使用 class OrderLine 将其保存在存储库中,这可以为您隐藏它是保存在数据库中,还是保存在 XML 文件,它也允许您更改它,将来可能需要这个。
所以:然而,分离模型和视图最初需要一些额外的输入,但如果您希望您的软件必须在多个版本中存活,我的建议是分离!