DataGrid with combobox column bug
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