Reputation: 8636
I am having a DataGridView that initially loads data and shows it.
When an user clicks an Edit Button, I am adding a DataGridViewComboBoxColumn by hiding one of the Columns.
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();
}
Here is the Button.Click
event where I am trying to add a ComboBox Column:
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;
}
When I click on drop down, nothing happens:
Upvotes: 1
Views: 622
Reputation: 32248
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;
anddgvProducts.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:
Upvotes: 1