I'm trying to bind separate ComboBox cells within a DataGridView to a custom class, and keep getting an error
DataGridViewComboBoxCell value is not valid
I'm currently assigning the data source for the cell to an IList<ICustomInterface> from a Dictionary I've got. Upon setting the data source however, the index for the ComboBoxCell isn't set, so it has an invalid value selected.
I'm trying to figure out how to get it to select a real value, e.g. the 0th item within the list it has been given to remove this error, or find another way to solve the problem. Anyone have any suggestions?
I managed to find the solution not long after posting the question. For anyone else:
The problem was that I was trying to assign the DataGridViewComboBoxCell.Value to an object, expecting that because the Cell was bound to a data source that it would automatically find the object in the source and update.
This wasn't actually the case, you actually need to set the value equal to that of the ValueMember property for it to correctly update the value and binding. I believe I was using a property 'Name' for both ValueMember and DisplayMember (controls how the renders in the cell) so setting the Value to interface.ToString() (rather than the interface instance) works for the majority of cases. Then I catch and ignore any DataError exceptions that occur while I'm changing the source around.
Here's my simple solution when using enums
ColumnType.ValueType = typeof (MyEnum);
ColumnType.DataSource = Enum.GetValues(typeof (MyEnum));
you can do that just after "InitializeComponent();"
Afters hours of trials, I finally found a solution that works.
// Create a DataGridView
System.Windows.Forms.DataGridView dgvCombo = new System.Windows.Forms.DataGridView();
// Create a DataGridViewComboBoxColumn
System.Windows.Forms.DataGridViewComboBoxColumn colCombo = new
System.Windows.Forms.DataGridViewComboBoxColumn();
// Add the DataGridViewComboBoxColumn to the DataGridView
dgvCombo.Columns.Add(colCombo);
// Define a data source somewhere, for instance:
public enum DataEnum
{
One,
Two,
Three
}
// Bind the DataGridViewComboBoxColumn to the data source, for instance:
colCombo.DataSource = Enum.GetNames(typeof(DataEnum));
// Create a DataGridViewRow:
DataGridViewRow row = new DataGridViewRow();
// Create a DataGridViewComboBoxCell:
DataGridViewComboBoxCell cellCombo = new DataGridViewComboBoxCell();
// Bind the DataGridViewComboBoxCell to the same data source as the DataGridViewComboBoxColumn:
cellCombo.DataSource = Enum.GetNames(typeof(DataEnum));
// Set the Value of the DataGridViewComboBoxCell to one of the values in the data source, for instance:
cellCombo.Value = "Two";
// (No need to set values for DisplayMember or ValueMember.)
// Add the DataGridViewComboBoxCell to the DataGridViewRow:
row.Cells.Add(cellCombo);
// Add the DataGridViewRow to the DataGridView:
dgvCombo.Rows.Add(row);
// To avoid all the annoying error messages, handle the DataError event of the DataGridView:
dgvCombo.DataError += new DataGridViewDataErrorEventHandler(dgvCombo_DataError);
void dgvCombo_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
// (No need to write anything in here)
}
That is all.
I had the same problem.
In my case the solution was to fill the data adapter of the Foreign key table. This was not being automatically filled and this was the cause of the problem.
In the Page_Load Event:
Me.TblUserTypesTableAdapter.Fill(Me.DonateDataSet.tblUserTypes)
For the sake of people not struggling as much as i did.
When binding the combo you are setting a DisplayMember (what the user will see)
and ValueMember (what your application will get).
After setting up these you need to set up the Value and this is where it fails.
Basically the TYPE of the value needs to be the same TYPE as the ValueMember.
So if your value member is an ID obviously its of type INT and you need to set your value to int for example Cell.Value = 1;.
I am having the same problem. After populating my ComboBox column in the (unbouod) DataGrid, I solved my problem by setting the ValueMember property of the DataGridViewComboBoxColumn
Apparently, just relying on the ToString() property of the objects in the ComboBox is not enough.
Actual code:
/// <summary>
/// Populate the dataGridSplitVolumes data grid with all existing qualifications for the plan.
/// </summary>
/// <param name="bonus"></param>
private void PopulateDataGridSplitVolumes(Bonus_Group bonus)
{
try
{
List<Qualification> qualifications = Qualification.Load(this.groupBonus.PlanID, this.ConnectionString);
foreach (Qualification qual in qualifications)
{
DataGridViewComboBoxColumn col = (DataGridViewComboBoxColumn)this.dataGridSplitVolumes.Columns[0];
col.Items.Add(qual);
}
SplitVolumeGrid_QualificationColumn.ValueMember = "Name";
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
throw ex;
}
}//PopulateDataGridSplitVolumes
use DataError Event handler,
private void shahriartableDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
//You don't have to write anything here !
}
and your DisplayMember and ValueMember data type should be the same, in my case I have CountryName for both. Everything works fine for me...!!
Here's a complete example with a basic form and DataGridView added via the designer:
Setup and bindings:
private void Form1_Load(object sender, EventArgs e)
{
var colors = new List<Code>()
{
new Code() {Value= "R", Text = "Red"},
new Code() {Value= "G", Text = "Green"},
new Code() {Value= "B", Text = "Blue"}
};
var users = new List<User>()
{
new User() {Name = "Briana", FavoriteColor = "B"},
new User() {Name = "Grace", FavoriteColor = "G"}
};
var colorCol = new DataGridViewComboBoxColumn();
colorCol.DataSource = colors;
colorCol.DisplayMember = "Text";
colorCol.ValueMember = "Value";
colorCol.DataPropertyName = "FavoriteColor";
dataGridView1.Columns.Add(colorCol);
dataGridView1.DataSource = users;
}
Some classes:
public class Code
{
public string Value { get; set; }
public string Text { get; set; }
}
public class User
{
public string Name { get; set; }
public string FavoriteColor { get; set; }
}
Set a null value to the cell:
dataGridView.CurrentRow.Cells[NAME].Value = null;
I was having the same problem. The message was 100% spot on. The values for the combobox were like: Exact, StartsWith... and I was trying to set the value Exactă (not Exact). This was happening automatically as I was reading the DataTable for the DataGridView from an .xml file with DataTable.ReadXml(...). The values in the .xml file were off.
Related
So I have a DataGridView which I am using as a “row selector” on a form, and a bunch of controls are bound to the bindingSource.
One of the bound controls is a ComboBox serving as a lookup which enables status choices for the rows, this is populated from a DataTable with data pulled from the DB.
The population of this box is without any issue.
When a given row is selected from the DGV the form controls display data from the given row as they should, however the “statusComboBox” is not quite playing the game.
If in the DGV, I choose a row that has a different status to one previously selected, it works as it should, however, if I choose a row with the same value to a previously selected row, instead of the box showing the DisplayMember it shows the ValueMember.
IT only seems to occur in the above scenario, where the rows selection only instigates a display response from the bound ComboBox providng a previous selection had a different “Status ID”. What have I dont wrong that would cause this behaviour?
So the form load looks like this
private void ProjectsForm_Load(object sender, EventArgs e)
{
InitBindingSource();
//// bind Selector
//ASMod$ this needs to be 'true' unless you explicitly declare columns
ProjectsDataGridView.AutoGenerateColumns = false;
ProjectsDataGridView.DataSource = ProjectsBindingSource;
GetData();
//Set GeneralStatusBox
Helpers.GeneralStatusInitLookup(statusComboBox, ProjectsBindingSource);
}
The ProjectBindingSource is initialised thus:
private void InitBindingSource()
{
ProjectsBindingSource = new BindingSource();
projectsBindingNavigator.BindingSource = ProjectsBindingSource;
ProjectsBindingSource.PositionChanged += new EventHandler(ProjectsBindingSource_PositionChanged);
}
A ProjectsAddDataBindings procedure, and the contained DataBindings.Add for the ComboBox (executed at the end of a GetData routine that additionally populated ProjectsBindingSource):
ProjectsAddDataBindings();
{
…
this.statusComboBox.DataBindings.Add("Text", ProjectsBindingSource, "GSID");
…
}
After the GetData block the GeneralStatusInitLookup populates the Lookup elements, in a helper class simply because it provides functionality to a number of different forms
public static void GeneralStatusInitLookup(System.Windows.Forms.ComboBox comboBox, BindingSource primaryBindingSource)
{
string statusFilter = "";
statusFilter = Helpers.GetStatusGroupFilter(EndeavourForm.FilterId);
if (statusFilter != "")
{
statusFilter = " WHERE " + statusFilter;
}
//// string statusFilter = ""; //// temp
string sql = "";
sql = "SELECT GSID, ShortName FROM GeneralStatus" + statusFilter + " ORDER BY Pos";
GeneralStatusDataTable = Helpers.Db.GetDataTable(sql);
comboBox.DataSource = GeneralStatusDataTable;
comboBox.DisplayMember = "ShortName";
comboBox.ValueMember = "GSID";
comboBox.DataBindings.Add(new Binding("SelectedValue", primaryBindingSource.DataSource, "GSID"));
}
And the DGV initiated row change is handled like this
private void ProjectsBindingSource_PositionChanged(object sender, EventArgs e)
{
try
{
// Update the database with the user's changes.
UpdateProjects();
statusComboBox.SelectedValue = (int)CurrentDataRowView.Row["GSID"];
}
catch (Exception)
{
}
}
private void UpdateProjects()
{
try
{
ProjectsDataAdapter.Update((DataTable)ProjectsBindingSource.DataSource);
DataHelper.CommitProposedChanges(projectsDataSet);
if (this.projectsDataSet.HasChanges() == true)
{
ProjectsBindingSource.EndEdit();
ProjectsDataAdapter.Update();
}
CurrentDataRowView = (DataRowView)ProjectsBindingSource.Current;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception)
{
throw;
}
}
Anyway I hope I haven't swamped readers with to much code, but frankly I cant see where this is going wrong. So any help would be greatly appreciated.
This was a simple solution in the end. Both the GeneralStatusInitLookup() and the ProjectsAddDataBindings() blocks made use of DataBindings.Add ... For the lookup table this was fine, but with the binding to the main table; the later, I had used "Text" as the propertyName parameter.
I have added a DataGridViewComboBox to a bound DataGridView (grdBOOK), the DataGridViewComboBox will replace column 3 to allow for user selection. I'm struggling to set the default of the DataGridViewComboBox equal to the value of column 3 so user selection is not required if the value is correct.
I pulled the code below from the net, but I get an error:
DataGridViewComboBoxCell value is not valid.
I thought a ComboBox cell could be treated as a normal DataGridView cell, but (see code below) an error is generated when a string is added to the ComboBox column? I've trawled the net and SO for a few days but nothing works, any suggestions please?
public void BOOK_COMBO2()
{
DataGridViewComboBoxCell cb_cell = new DataGridViewComboBoxCell();
DataGridViewComboBoxColumn cb_col = new DataGridViewComboBoxColumn();
// Contract field
cb_col.Items.AddRange("YEARLY", "MONTHLY", "");
cb_col.FlatStyle = FlatStyle.Flat;
cb_col.HeaderText = "newCONTRACT";
cb_col.Width = 50;
cb_col.ValueType = typeof(string);
// Add ComboBox and test
grdBOOK.Columns.Insert(5, cb_col);
grdBOOK.Rows[14].Cells[4].Value = "zzz"; // No error adding string to normal dgv column
grdBOOK.Rows[14].Cells[5].Value = "xxx"; // Error adding string to dgvcombobx column
//copy old values to new combobox and set as default
foreach (DataGridViewRow item in grdBOOK.Rows)
{
item.Cells[5].Value = item.Cells[3].Value;
}
//hide original column
grdBOOK.Columns[3].Visible = false;
}
After more research on the net, IMHO using a ContextMenuStrip is a better method of achieving this. Link here. A ContextMenuStrip has better methods, events, properties etc. I hope this helps others looking for a solution.
private void dataGridView1_DataError(object sender,
DataGridViewDataErrorEventArgs e)
{
// If the data source raises an exception when a cell value is
// commited, display an error message.
if (e.Exception != null &&
e.Context == DataGridViewDataErrorContexts.Commit)
{
MessageBox.Show("");
}
}
private void Form1_Load(object sender, EventArgs e)
{ dataGridView1.DataError +=
dataGridView1_DataError;}
I have a XtraGrid GridControl defined with 3 columns, 2 databound and one I have set to a RepositoryItemComboBox. The column is setup like:
this.gridColumn3.Caption = "Test";
this.gridColumn3.FieldName = "test";
this.gridColumn3.Name = "gridColumn3";
this.gridColumn3.UnboundType = DevExpress.Data.UnboundColumnType.String;
this.gridColumn3.Visible = true;
this.gridColumn3.VisibleIndex = 2;
The RepositoryItemComboBox is created like:
RepositoryItemComboBox cbo = new RepositoryItemComboBox();
cbo.Items.Add("1");
cbo.Items.Add("2");
cbo.Items.Add("3");
cbo.Items.Add("4");
cbo.Items.Add("5");
cbo.Items.Add("6");
cbo.Items.Add("7");
gridView1.Columns[3].ColumnEdit = cbo;
When viewing the grid, the combobox displays exactly as I want it. This issue is when trying to retrieve the value selected in the combobox. When a button, outside of the grid, is pressed the combobox value should be processed. I use the following code:
for (int i = 0; i < gridView1.DataRowCount; i++)
{
int ID = (int)gridView1.GetRowCellValue(i, "id");
string Test = gridView1.GetRowCellValue(i, "test").ToString();
ProcessCombo(ID, Test);
}
In the above code ID is retrieved as expected, but gridView1.GetRowCellValue(i, "test") returns null. What could I have missed out? Is this even the right way to approach this?
ComboBox editor is only exists when the focused cell is in editing state. So, ComboBox is created when you activate it and is destroyed when you go away. So, when your Button is pressed there are no ComboBox in your GridView.
If you want to get the ComboBox value then you need to use events of your RepositoryItemComboBox or editing events of your GridView. The link to the editor is stored in sender parameter of RepositoryItemComboBox events and in GridView.ActiveEditor property. The value of the ComboBox you can get by using ComboBox.EditValue property or by using GridView.EditingValue property. Also you can get the value by using EventArgs parameter of GridView editing events like ValidatedEditor, CellValueChanging, CellValueChanged.
For example:
private void RepositoryItemComboBox1_Closed(object sender, ClosedEventArgs e)
{
//Get the value from sender:
var comboBox = (ComboBoxEdit)sender;
object value = comboBox.EditValue;
//Get the value from active editor:
object activeEditorValue = gridView1.ActiveEditor.EditValue;
//Get the value from editing value:
object editingValue = gridView1.EditingValue;
}
Also, if you want to store your value and use it later then you can use ColumnView.CustomUnboundColumnData event.
Here is example:
private Dictionary<int, string> _comboBoxValues = new Dictionary<int, string>();
private void gridView1_CustomUnboundColumnData(object sender, CustomColumnDataEventArgs e)
{
if (e.Column.FieldName != "test")
return;
int id = (int)gridView1.GetListSourceRowCellValue(e.ListSourceRowIndex, "id");
if (e.IsSetData)
_comboBoxValues[id] = (string)e.Value;
else if (e.IsGetData && _comboBoxValues.ContainsKey(id))
e.Value = _comboBoxValues[id];
}
I decided to change the column to a bound field, passed in an empty string when populating the grid, and now I can access the data via:
string Test = gridView1.GetRowCellValue(i, "test").ToString();
Didn't want to do this, but it works so...
I'm using a RadGridView to display items. On a doubleclick the user comes to a detail view where he can edit the contents and then comes back (both usercontrols are displayed as tabs on screen and are thus existing simultaneously).
I'm using a working method to update the edited list in the grid view without reloading the whole gridview:
dataRow = (GridViewRowInfo)element.Data;
Worker displayedWorker = (Worker)edataRow.DataBoundItem;
Worker changedWorkerFromDataBase = GetWorkerFromDataBase(displayedWorker.WorkerNumber).FirstOrDefault();
List<Worker> tableDataSource = (List<Worker>)MyGridView.DataSource;
int indexInTableDataSource = tableDataSource.IndexOf(tableDataSource.Where(e => e.WorkerNumber == displayedWorker.WorkerNumber).First());
tableDataSource[indexInTableDataSource] = changedWorkerFromDataBase;
dataRow.InvalidateRow();
Like mentioned this code works, but when I change the IndexOf method to:
int indexInTableDataSource = tableDataSource.IndexOf(displayedWorker);
it doesn't work any longer as expected. The above code works once and only once after a second save the objects stored inside the MyGridView.DataSource and the one stored inside the dataRow.DataBoundItem are seen as being different resulting in the indexOf returning -1.
This behaviour surprised me quite a lot especially as the first method works without problems and I had thought that the dataBound item of the row is just a pointer towards the items inside the GridView datasource.
Thus my question here is: Can (after the update is done) I tell the row to update/refresh its databound item so that it is equal again to the one from the gridview (aka seen as the same object) ?
First off: If you have sorting/grouping enabled, the index in the datasource might not correspond with the index in the radgridview (see Childrows vs Rows).
I have worked on a similar scenario with a radgridview and a detailview.
To notify the radgridview that a propertyvalue is changed your Worker class needs to implement the INotifyPropertyChanged interface. About INotifyPropertyChanged
Change your datasource type from List to BindingList as this list has events raised when the list is changed. About BindingList
Now you should be able to alter the instance of your Worker class and the changes should be picked up immediatly in your radgridview.
The problem in your case is coming from the fact that for the grid's DataSource you are using List, which does not support notifications. If you switch to BindingList you will be good to go. Here is a sample:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
AddGrid();
BindingList<Worker> lst = new BindingList<Worker>() { new Worker() { ID = 1, Name = "Adam" }, new Worker() { ID = 2, Name = "Eva" } };
radGridView1.DataSource = lst;
}
class Worker
{
public int ID { get; set; }
public string Name { get; set; }
}
private void radButton1_Click(object sender, EventArgs e)
{
Worker displayedWorker = (Worker)radGridView1.CurrentRow.DataBoundItem;
BindingList<Worker> tableDataSource = (BindingList<Worker>)radGridView1.DataSource;
int indexInTableDataSource = tableDataSource.IndexOf(displayedWorker);
Worker changedWorkerFromDataBase = new Worker() { ID = 1, Name = "new name" };
tableDataSource[indexInTableDataSource] = changedWorkerFromDataBase;
}
More on the difference between List and BindingList can be found here: https://stackoverflow.com/a/2244039/298871
dataGridView.Rows.Add(
metaData.Offset.ToString("X2"),
metaData.Length,
metaData.Format, // This parameter goes to a ComboBox cell which throws an
metaData.Description, // exception above
null,
null);
What is the valid way to programmatically assign data to DataGridViewComboBoxCell?
To Solve this problem, just add “DataError” for DataGridView. That’s all, steps: Double click the datagridview and select the “dataerror” event from the event list.
The DataError event enables you to handle exceptions thrown in code that is called by the control during data processing operations.
thats it :)
To Solve this problem, add event of "DataError" for DataGridView and then
just write this: e.Cancel = true;
e.g:
private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
e.Cancel = true;
}
A little late to the party, but I think this can still be usefull to others. In my case I got this error because I added a combobox based on an enum like this:
// Combo
DataGridViewComboBoxColumn combo = new DataGridViewComboBoxColumn();
combo.DataSource = Enum.GetValues(typeof(PhoneNumberType));
combo.DataPropertyName = "PhoneNumberType";
combo.Name = "PhoneNumberType";
phoneNumberGrid.Columns.Add(combo);
Now when the DataGridView created a new (empty) row, this empty value couldn't be mapped to the combo box. So instead of ignoring the error, I set the default value for the combo box. I added the event DefaultValuesNeeded and there just set the value to one of the items from the enum. Like this:
private void phoneNumberGrid_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
{
// Prevent System.ArgumentException: DataGridViewComboBoxCell value is not valid
e.Row.Cells["PhoneNumberType"].Value = PhoneNumberType.Private;
}
.. and the exception went away.
I had a similar issue, where I populated my DataSource with a Dictionary<int,string> myDict, where I wanted to display the string but have an integer as an underlying value to use:
comboboxcolumn.DataSource = new BindingSource(myDict, null);
comboboxcolumn.ValueMember = "Key";
comboboxcolumn.DisplayMember = "Value";
what caused my trouble was that I correctly set: comboboxcolumn.ValueType = typeof(int);
but in the CellValueNeeded() Method, I then set e.Value = someValueOfMyDict; which is a string. Use the int key instead and you eliminate the error instead of catching it and leaving it unhandled.