1
Answer

DataGrid with combobox column bug

samnet

samnet

19y
6.5k
1
Hi all, Thanks in advance for any help you can give. My situation: I've created a custom class (with some borrowing from MSDN) DataGridComboBoxColumn that allows a combobox to be placed in a datagrid column. The class essentially inherits from DataGridTextBoxColumn (although there is one custom class between). Generally speaking everything works beautifully, I bind a dataset to the combobox, it lets the user pick from a list of items, then sets the value in the datagrid. I've run into one bug though, if the user wishes to add a row to the datagrid they must enter a value in some non-DataGridComboBoxColumn column for the new row to appear. In other words if a user selects a combox value in the bottom, "add-a-row" row, the SetColumnValueAtRow is not sufficient to force the datagrid to actually add a new row. This is a problem because I have some datagrids that only contain DataGridComboBoxColumns, so the user can't add rows to those datagrids. I've tried a workaround (see code in SetColumnValueAtRow) where I add a row to the datasource table then rebind the datagrid, but it seems to have no effect... any thoughts? The code: -------- Notes: DataGridShowEditableColumn is just DataGridTextBoxColumn with Paint overwritten. --------------------- #define DEBUG using System; using System.Data; using System.Drawing; using System.Collections; using System.Windows.Forms; namespace AFG.ProductionTracker { //code borrowed from MSDN //http://msdn.microsoft.com/msdnmag/issues/03/08/DataGrids/default.aspx public class DataGridComboBoxColumn : DataGridShowEditableColumn { // Hosted combobox control private ComboBox comboBox; private CurrencyManager cm; private int iCurrentRow; private int origRowCount; // Constructor - create combobox, // register selection change event handler, // register lose focus event handler public DataGridComboBoxColumn() { this.cm = null; // Create combobox and force DropDownList style this.comboBox = new ComboBox(); this.comboBox.DropDownStyle = ComboBoxStyle.DropDownList; // Add event handler for notification when combobox loses focus this.comboBox.Leave += new EventHandler(comboBox_Leave); } // Property to provide access to combobox public ComboBox ComboBox { get { return comboBox; } } // On edit, add scroll event handler, and display combobox protected override void Edit(System.Windows.Forms.CurrencyManager source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly, string instantText, bool cellIsVisible) { base.Edit(source, rowNum, bounds, readOnly, instantText, cellIsVisible); if (!readOnly && cellIsVisible) { // Save current row in the DataGrid and currency manager // associated with the data source for the DataGrid this.iCurrentRow = rowNum; this.cm = source; // Add event handler for DataGrid scroll notification this.DataGridTableStyle.DataGrid.Scroll += new EventHandler(DataGrid_Scroll); // Site the combobox control within the current cell this.comboBox.Parent = this.TextBox.Parent; Rectangle rect = this.DataGridTableStyle.DataGrid.GetCurrentCellBounds(); this.comboBox.Location = rect.Location; this.comboBox.Size = new Size(this.TextBox.Size.Width, this.comboBox.Size.Height); // Set combobox selection to given text this.comboBox.SelectedIndex = this.comboBox.FindStringExact(this.TextBox.Text); // Make the combobox visible and place on top textbox control this.comboBox.Show(); this.comboBox.BringToFront(); this.comboBox.Focus(); } } // Given a row, get the value member associated with a row. Use the // value member to find the associated display member by iterating // over bound data source protected override object GetColumnValueAtRow(System.Windows.Forms.CurrencyManager source, int rowNum) { // Given a row number in the DataGrid, get the display member object obj = base.GetColumnValueAtRow(source, rowNum); // Iterate through the data source bound to the ColumnComboBox CurrencyManager cm = (CurrencyManager) (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]); // Assumes the associated DataGrid is bound to a DataView or // DataTable DataView dataview = ((DataView)cm.List); int i; for (i = 0; i < dataview.Count; i++) { if (obj.Equals(dataview[i][this.comboBox.ValueMember])) break; } if (i < dataview.Count) return dataview[i][this.comboBox.DisplayMember]; return DBNull.Value; } // Given a row and a display member, iterate over bound data source to // find the associated value member. Set this value member. protected override void SetColumnValueAtRow(System.Windows.Forms.CurrencyManager source, int rowNum, object value) { //workaround for datagrid not adding a row if(this.origRowCount != source.Count && this.origRowCount != 0) { DataTable dt = new DataTable(); dt = (DataTable)this.DataGridTableStyle.DataGrid.DataSource; DataRow addedRow = dt.NewRow(); //hard code values to test addedRow["EmployeeID"] = 2; addedRow["SalesPerson"] = "SAMRAP"; addedRow["JobFunctionID"] = 1; addedRow["Role"] = "Marketer"; this.DataGridTableStyle.DataGrid.DataSource = dt; } this.origRowCount = source.Count; #if DEBUG Console.WriteLine(this.origRowCount + " Rows"); #endif object s = value; // Iterate through the data source bound to the ColumnComboBox CurrencyManager cm = (CurrencyManager) (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]); // Assumes the associated DataGrid is bound to a DataView or // DataTable DataView dataview = ((DataView)cm.List); int i; for (i = 0; i < dataview.Count; i++) { if (s.Equals(dataview[i][this.comboBox.DisplayMember])) break; } //if value was changed... if(!s.Equals(this.TextBox.Text)) { // If set item was found return corresponding value, otherwise return DbNull.Value #if DEBUG Console.WriteLine("Item changed: " + this.cm.Position); Console.WriteLine("Row " + rowNum.ToString() + " of " + this.cm.List.Count.ToString()); #endif if(i < dataview.Count) {s = dataview[i][this.comboBox.ValueMember];} else {s = DBNull.Value;} //call base function to set display and value base.SetColumnValueAtRow(source, rowNum, s); } } // On DataGrid scroll, hide the combobox private void DataGrid_Scroll(object sender, EventArgs e) { this.comboBox.Hide(); } // On combobox losing focus, set the column value, hide the combobox, // and unregister scroll event handler private void comboBox_Leave(object sender, EventArgs e) { DataRowView rowView = (DataRowView) this.comboBox.SelectedItem; string s = (string) rowView.Row[this.comboBox.DisplayMember]; //fixes bug where ListManager position != currentRow from double Edit call if(this.cm.Position == this.iCurrentRow) SetColumnValueAtRow(this.cm, this.iCurrentRow, this.comboBox.Text); Invalidate(); this.comboBox.Hide(); this.DataGridTableStyle.DataGrid.Scroll -= new EventHandler(DataGrid_Scroll); } } } Thanks, Sam
Answers (1)