The DataTable used as the DataSource of your DataGridView has an auto-increment Column. You cannot use this Column as the ProductId, which can be changed by an User via the ComboBox selector. It will make a mess (unless this was used just just to build a MCVE).
You can use this Column as the Primary Key - setting also Unique = true.
Add instead a int Column that represents the ProductId Key, which links the ProductName Column that is part of the DataTable set as the DataSource of the ComboBox Column.
Since the ValueMember property of the ComboBox is set to the ProductId value and the ComboBox Column is bound to the ProductId Column of the DataGridView DataTable, changing the SelectdItem of the ComboBox will change the value of the ProductId Column of the DataTable used as data source of your DataGridView.
Added to the BindProductsGrid() method:
dtGrid.PrimaryKey = new[] { pkColumn };
dtGrid.AcceptChanges(); (mandatory)
dgvProducts.Columns["ProductId"].ReadOnly = true; and
dgvProducts.AllowUserToAddRows = false; (since this seems to be the intention: let the User specify the Product only using the ComboBox selector)
The DataGridViewComboBoxColumn is added after the DataSource of the DataGridView is set. This because this Column is bound to the ProductId Column, as the corresponding Column of the DataGridView's DataTable.
It allows to add to the DataGridView two Columns bound to the same Column of the data source in code, without confusing the Control.
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;
}
The DataTable used as data source of the ComboBox has the same two Columns, just AcceptChanges() (optional) is added to the previous code:
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;
}
Now, some adjustments to make the Product selection more responsive:
(➨ note that the DataGridView is named dgvProducts)
The EditingControlShowing handler subscribes to the ComboBox Cell SelectedIndexChanged event
The ComboBox SelectedIndexChanged handler invokes asynchronously Validate(), to show the ComboBox selection immediately (no need to select another Cell to see it applied)
BeginInvoke(new Action(() => Validate()));
The DataGridView CellContentClick handler changes the style of the ComboBox to DataGridViewComboBoxDisplayStyle.ComboBox
CellLeave handler restores the ComboBox style to DataGridViewComboBoxDisplayStyle.Nothing, so it looks like a TextBox.
▶ If you instead want to just hide/show the ProductName column, you can do without the CellContentClick and CellLeave handlers and keep the initial ComboBox style.
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;
}
}
}
This how it works using the code shown here:
