Windows Forms 2.0 has a new general purpose data display
control called a DataGridView. It is intended to be easier to use and more
powerful than the original DataGrid control.
The following experience may help you when it comes to
adding data validation logic to a rather simple application of the DataGridView
control.
An obvious (but WRONG) place to start is the
CellValidating event of the DataGridView object. Here is a fairly reusable
implementation of a CellValidating event delegate method:
this.grid.CellValidating
+= new DataGridViewCellValidatingEventHandler(grid_CellValidating);
void
grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {
DataGridViewCell
cell = grid.Rows[e.RowIndex].Cells[e.ColumnIndex];
TypeConverter
tc = TypeDescriptor.GetConverter(cell.ValueType);
try {
object
newValue = tc.ConvertFrom(e.FormattedValue);
object
oldValue = cell.Value;
try {
cell.Value = newValue;
} finally
{
cell.Value = oldValue;
}
} catch (Exception ex) {
grid.Rows[e.RowIndex].ErrorText =
ex.Message;
SystemSounds.Beep.Play();
e.Cancel = true;
}
}
There are a few general things to point out about this
method:
·
It uses a TypeConverter to determine if the new value
(e.FormattedValue) can be converted to the type of value stored in the cell.
Using a TypeConverter is a good general approach because FormattedValue isn’t
always a string. It can, in theory, be just about any type.
·
It provisionally assigns the new value to the cell’s value to
trigger any argument checking done by the underlying DataSource’s property
setter. If there is a value range restriction on the data, you should be checking
it at the first public interface level, not in some form specific UI code.
·
A finally block is used to restore the current value of the cell
after the provisional assignment. There are several things not to like about
this technique. The setter can have side effects (not good coding practice, but
it happens). The setter can be resource intensive.
·
Validation errors result in exceptions being thrown. The
exception’s message is set as the grid row’s ErrorText property and a beep
sound is generated. The ErrorText property will cause a small error
notification icon to appear in the row header with a tool tip set to the
exception’s message.
·
The Validating event doesn’t happen when a value is simply
assigned to a grid cell. For example if your paste-from-clipboard method
attempts to assign the contents of the clipboard to a grid cell, you’ll have to
implement separate validation logic there and validation failure notification
logic.
When you use the ErrorText property, don’t forget to reset
it after the problem has been fixed. The simplest way to do this is with the
Validated event:
this.grid.CellValidated
+= new DataGridViewCellEventHandler(grid_CellValidated);
void
grid_CellValidated(object sender, DataGridViewCellEventArgs e) {
grid.Rows[e.RowIndex].ErrorText = String.Empty;
}
The RIGHT place to put your validation logic is in the
DataSource’s property setters, throw an ArgumentValueException for bad values,
and use the DataError event instead of the CellValidating event to handle the
exceptions:
this.grid.DataError
+= new DataGridViewDataErrorEventHandler(grid_DataError);
DateTime
lastDataError = DateTime.MinValue;
void
grid_DataError(object sender, DataGridViewDataErrorEventArgs e) {
e.Cancel = true;
e.ThrowException = false;
// A single error
may cause secondary calls to this handler, ignore these.
if
(grid.Rows[e.RowIndex].ErrorText != String.Empty
&& null == e.Exception) return;
TimeSpan
sinceLast = DateTime.Now - lastDataError;
lastDataError = DateTime.Now;
string
message;
if (null == e.Exception)
message = "Bad
value.";
else {
message = e.Exception.Message;
if
(e.Exception is System.Reflection.TargetInvocationException)
if
(e.Exception.InnerException is FormatException)
message = "Bad value format. Check the type of value
entered.";
}
grid.Rows[e.RowIndex].ErrorText = message;
if
(sinceLast.TotalSeconds < 1.0)
MessageBox.Show(message
+ "\r\n\r\nPress Ctrl-Z to restore previous
value.", "Bad Value");
else
SystemSounds.Beep.Play();
}
The DataError event has a richer (more complicated)
calling context than the CellValidating event, but this is a good thing. It
allows you to be consistent and focused about your error handling and
notification.
A few comments about the grid_DataError method:
·
The e.Context field of the event arguments is a DataGridViewDataErrorContext,
which is a flags style enumerator that tells you the general context of the
data error. You may want to specialize your handling based on the context in
production situations.
·
In addition to the Cancel property there is a ThrowException
property. Generally you can set these to true and false respectively because
you’re going to handle the problem and take care of notifying the user.
·
A single error may cause a secondary call to the handler. This is
the kind of thing that may get fixed by the time 2.0 goes into production. For
now, ignore the second call by noticing that the exception property is null and
an error message has already been set.
·
Many exception messages are fairly cryptic from an ordinary
user’s perspective. Do whatever massaging is necessary for your application.
·
In addition to setting the ErrorText property for a small visual
indicator of an error condition, either play a beep sound or, if this error
followed the last by less than a second, put up a dialog to display the message
directly and require the user to acknowledge it.
Hope this helps you…