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

匿名类型没有设置 属性,因此如果操作员编辑显示的单元格之一,您将无法自动更新数据源。

我不确定将数据模型与其显示方式交织在一起是否明智。多年来,人们倾向于将数据与显示分开。这种分离通常以 ViewModel 等术语命名,或者以极端形式命名: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 文件,它也允许您更改它,将来可能需要这个。

所以:然而,分离模型和视图最初需要一些额外的输入,但如果您希望您的软件必须在多个版本中存活,我的建议是分离!