无法将 DataGridViewComboBoxColumn 中的值设置为绑定的 DataGridView

Cannot set values from a DataGridViewComboBoxColumn to a bound DataGridView

我有一个最初加载数据并显示它的 DataGridView。
当用户单击“编辑”按钮时,我通过隐藏其中一个列来添加 DataGridViewComboBoxColumn。

private DataTable BindCombo()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("ProductId", typeof(int));
    dt.Columns.Add("ProductName", typeof(string));
    dt.Rows.Add(1, "Product1");
    dt.Rows.Add(2, "Product2");
    return dt;
}

private void BindGrid()
{
   DataTable dtGrid = new DataTable();
   DataColumn column = new DataColumn("ProductId")
   {
      DataType = System.Type.GetType("System.Int32"),
      AutoIncrement = true,
      AutoIncrementSeed = 1,
      AutoIncrementStep = 1
   };
   dtGrid.Columns.Add(column);
   dtGrid.Columns.Add("ProductName", typeof(string));

   dtGrid.Rows.Add(null, "Product1");
   dtGrid.Rows.Add(null, "Product2");

   dataGridView1.DataSource = dtGrid;
 }

 private void Form1_Load(object sender, EventArgs e)
 {
    BindGrid();
 }

这是我尝试添加 ComboBox 列的 Button.Click 事件:

 private void btnEdit_Click(object sender, EventArgs e)
 {
     dataGridView1.AllowUserToAddRows = true;
     dataGridView1.ReadOnly = false;
     dataGridView1.Columns[1].Visible = false;
     DataGridViewComboBoxColumn col1 = new DataGridViewComboBoxColumn
     {
         DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton,
         HeaderText = "Product Name",
         DataSource = BindCombo(),
         ValueMember = "ProductId",
         DisplayMember = "ProductName",
         DataPropertyName = "ProductId"
     };
     dataGridView1.Columns.Add(col1);
     dataGridView1.AllowUserToAddRows = false;
    }

当我点击下拉列表时,没有任何反应:

用作DataGridView 的DataSource 的DataTable 有一个自增列。您不能将此列用作 ProductId,它可以由用户通过 ComboBox select 或更改。它会弄得一团糟(除非它只是用来构建 MCVE)。
您可以将此列用作主键 - 还可以设置 Unique = true.

改为添加代表 ProductId 键的 int 列,它链接 ProductName 列,该列是 DataTable 的一部分,设置为 ComboBox 列的数据源。
由于ComboBox的ValueMember 属性设置为ProductId值并且ComboBox Column绑定到DataGridView DataTable的ProductId Column,改变ComboBox的SelectdItem将更改用作 DataGridView 数据源的 DataTable 的 ProductId 列的值。

添加到BindProductsGrid()方法:

  • dtGrid.PrimaryKey = new[] { pkColumn };
  • dtGrid.AcceptChanges();(必填项)
  • dgvProducts.Columns["ProductId"].ReadOnly = true;
  • dgvProducts.AllowUserToAddRows = false;(因为这似乎是意图:让用户仅使用 ComboBox select 或)
  • 指定产品

在设置DataGridView的DataSource后添加DataGridViewComboBoxColumn。这是因为此列绑定到 ProductId 列,作为 DataGridView 的 DataTable 的相应列。
它允许在代码中向 DataGridView 添加两个绑定到数据源同一列的列,而不会 混淆 控件。

private void BindProductsGrid()
{
    var dtGrid = new DataTable();
    var pkColumn = new DataColumn("ID") {
        DataType = typeof(int),
        AutoIncrement = true,
        AutoIncrementSeed = 1,
        AutoIncrementStep = 1,
        Unique = true
    };

    var productColumn = new DataColumn("ProductId") {
        DataType = typeof(int),
        Caption = "Product Id"
    };

    dtGrid.Columns.Add(pkColumn);
    dtGrid.Columns.Add(productColumn);

    dtGrid.Rows.Add(null, 1);
    dtGrid.Rows.Add(null, 2);
    dtGrid.Rows.Add(null, 3);
    dtGrid.Rows.Add(null, 4);

    dtGrid.PrimaryKey = new[] { pkColumn };
    dtGrid.AcceptChanges();

    dgvProducts.DataSource = dtGrid;

    dgvProducts.Columns["ID"].Visible = false;
    dgvProducts.Columns["ProductId"].ReadOnly = true;

    var productName = new DataGridViewComboBoxColumn {
        DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing,
        Name = "ProductName",
        HeaderText = "Product Name",
        ValueMember = "ProductId",
        DisplayMember = "ProductName",
        DataSource = BindCombo(),
        DataPropertyName = "ProductId",
        DisplayIndex = 2
    };
    dgvProducts.Columns.Add(productName);
    dgvProducts.AllowUserToAddRows = false;
}

作为ComboBox数据源的DataTable有两个相同的Column,只是在前面的代码中加入了AcceptChanges()(可选):

private DataTable BindCombo()
{
    var dt = new DataTable();
    dt.Columns.Add("ProductId", typeof(int));
    dt.Columns.Add("ProductName", typeof(string));

    dt.Rows.Add(1, "Product1");
    dt.Rows.Add(2, "Product2");
    dt.Rows.Add(3, "Product3");
    dt.Rows.Add(4, "Product4");
    dt.AcceptChanges();
    return dt;
}

现在,进行一些调整以使产品 selection 更 响应:
(➨ 注意 DataGridView 被命名为 dgvProducts

  • EditingControlShowing 处理程序订阅 ComboBox 单元格 SelectedIndexChanged 事件

  • ComboBox SelectedIndexChanged 处理程序异步调用 Validate(),以立即显示 ComboBox selection(无需 select 另一个 Cell 即可查看它应用了)

    BeginInvoke(new Action(() => Validate()));
    
  • DataGridView CellContentClick 处理程序将 ComboBox 的样式更改为 DataGridViewComboBoxDisplayStyle.ComboBox

  • CellLeave 处理程序将 ComboBox 样式恢复为 DataGridViewComboBoxDisplayStyle.Nothing,因此它看起来像一个 TextBox。

▶ 如果您只想 hide/show ProductName 列,您可以不使用 CellContentClickCellLeave 处理程序并保留初始的 ComboBox 样式。

private void dgvComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
    (sender as ComboBox).SelectedIndexChanged -= dgvComboBox_SelectedIndexChanged;
    BeginInvoke(new Action(() => Validate()));
}

private void dgvProducts_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    if (e.Control is ComboBox cbo) {
        cbo.SelectedIndexChanged += dgvComboBox_SelectedIndexChanged;
    }
}

private void dgvProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    if (e.RowIndex > 0 && e.ColumnIndex == 2) {
        if (dgvProducts[2, e.RowIndex] is DataGridViewComboBoxCell cbox) {
            cbox.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox;
        }
    }
}

private void dgvProducts_CellLeave(object sender, DataGridViewCellEventArgs e)
{
    if (e.RowIndex < 0) return;
    if (dgvProducts.Columns[e.ColumnIndex].Name == "ProductName") {
        if (dgvProducts["ProductName", e.RowIndex] is DataGridViewComboBoxCell cbox) {
            cbox.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
        }
    }
}

这是使用此处显示的代码的工作原理: