Link to home
Start Free TrialLog in
Avatar of baffy2000
baffy2000Flag for United Kingdom of Great Britain and Northern Ireland

asked on

How do I get the error column in DataGridView

Hi all,

I'm using a datagridview in C#, bound to a bindingsource which selects data from SQL Server. So far, so standard. I set a lot of this up using the wizards, so a lot of the binding was done for me, which means that it knows, for example, when one of my columns does not allow nulls.

So, what happens at runtime, is I start entering a new record in the gridview, enter a bit of data so that I've got a partial record, then tab to the end of the row and tab off, which causes the grid to try to commit the record. It fails (because one of the fields contains null), and I trap the error in the DataError event of the gridview, like so:

private void dgvGasSafeDetails_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            string colName = dgvGasSafeDetails.Columns[e.ColumnIndex].HeaderText;
            MessageBox.Show(e.Exception.Message, colName + " Error");
            e.Cancel = true;            
        }

My Problem is that 'e.Exception.Message' contains something like:

Column 'Asset ID' does not allow nulls.

but e.ColumnIndex does not refer to the 'Asset ID' column. It refers to the column I'm in when the error occurs (which is the last column, since I'm tabbing off the end of the record). So 'colName' in the above event handler is NOT the name of the column where the error is.

What I want to know is which column has raised the error. i.e. I want something that tells me 'Asset ID'. I could parse the text of the error, but this is too flaky - the error may not always be in this format.

How do I get the column where the error occurred?
Avatar of Kelvin McDaniel
Kelvin McDaniel
Flag of United States of America image

Here's what I've got working...
private bool _canEvaluate = false;

private void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
	_canEvaluate = true;
}

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
	if (!_canEvaluate) return;

	var dc = {your_dataset_name}.Tables[0].Columns[e.ColumnIndex];

	if (dc.AllowDBNull) return;

	var _targetCell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];

	if (_targetCell.Value == null || string.IsNullOrEmpty(_targetCell.Value.ToString()))
		MessageBox.Show(string.Format("Column \"{0}\" cannot be null.", dc.ColumnName));
}

Open in new window

Worked with it a little bit more and I think this is even better...

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
	if (!_canEvaluate) return;

	var dc = {your_dataset_name}.Tables[0].Columns[e.ColumnIndex];

	if (dc.AllowDBNull) return;

	var _targetCell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];

	if (_targetCell.Value != null && !string.IsNullOrEmpty(_targetCell.Value.ToString())) return;

	MessageBox.Show(string.Format("Column \"{0}\" cannot be null.", dc.ColumnName));
	_targetCell.ErrorText = string.Format("Column \"{0}\" cannot be null.", dc.ColumnName);
}

Open in new window

Avatar of baffy2000

ASKER

Hi azarc,

Thanks for your response. This is cool, but it only accounts for the "is null" error. There are a bunch of errors which could be returned, such as numbers too big in numeric columns, strings in numeric columns, strings too long in string columns, invalid dates in date columns, and I expect a whole bunch of others I haven't thought of yet.

And this is the point - I don't want to know what the error is - I just want to know *where* it is. I already have an error handler by virtue of the grid's own DataError event, but it's not telling me what I need to know (the column the error refers to) in a form I can use.


Then simply wrap the contents of this method in a try..catch block and strip out the parts you don't want to use.

If an exception is thrown you'll know which column it refers to... and then you can then use several strategies to get at the name of the column, such as an external string property to hold the name of the column.

Make sense?
ASKER CERTIFIED SOLUTION
Avatar of baffy2000
baffy2000
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I would like to assign 200 points to azarc, but it is not at all clear how I would do this.
I disagree. :)

It's actually telling you both where exactly the error occurred, as well as what the error was, because it's running its check as soon as a particular cell's value is changed. I believe you'll also be able to get the column's name out of e (though you may have to hunt to find it).

If you want to wait until you tab off the row to show the errors for the row then here's psuedocode for a solution:

1. Add a Shared List<string>() to the class to hold any errors found

2. Use the CellValueChanged() event of the DataGridView to evaluate each cell's change.

3. Wrap the code inside CellValueChanged() with a try...catch block; the catch's sole purpose is to add the Exception.Message to the list in #1.

4. In the DataError() method, check the list in #1; if list.Count > 0 then show appropriately.

Make sense?
And actually, that #1 has a typo... corrected, entire set should be as follows:

1. Add a private static List<string>() to the class to hold any errors found

2. Use the CellValueChanged() event of the DataGridView to evaluate each cell's change.

3. Wrap the code inside CellValueChanged() with a try...catch block; the catch's sole purpose is to add the Exception.Message to the List in #1.

4. In the DataError() method, check the list in #1; if List.Count > 0 then show appropriately.
:-) I like your persistence!

Ok. I can see where you're coming from. Let me tell you what I'd like to achieve. Bottom line is this - if the user has just received a message saying "Asset ID cannot be null", I would like to be able to put them in the Asset ID column so they can correct it.

And your code is useful, but it is not the full story. For example, it only logs an error on the CellValueChanged event. But a classic error will be the user not entering anything in a cell which requires something to be entered. In this case, I will not have picked up the error because the cell did not change. And still I will not be able to tell, in code, which cell contains the error.

Which means that there will be more code to write, and experience tells me that I will have still more work to do after that. Which means I'm writing a whole heap of code to tell me something that the object itself already knows! The DataGridView knows where the error occurred! Why can't it tell me?

If there is no way around this other than repeating a whole bunch of error checking code that is obviously already part of the DataGridView itself, then my question is answered, i.e. You Can't. But I'd like to know that for sure before I embark on the pain of doing it myself.