Here. This should save you a ton of work.
In short: to validate a bound control, there are three events you want to handle.
- The
Control.Validating
event, which validates the data when the user leaves the control,
- The
Control.Validated
event, which updates the data source if and only if it's properly validated, and
- The
Binding.Parse
event, which validates the data when anything else changes the data in the control (i.e. your code).
To insure that only valid data gets written to your data source, automatic updating of the bound data is turned off when the Binding
is created - this way, data only gets written to the data source during the Control.Validated
event.
Both the Validating
and the Parse
events put the validation error message into an ErrorProvider
attached to the bound Control
; you may have some other way that you want to present error messages, in which case you'll need to change both of those events.
You also may want to handle the binding's Format
event, to control how the data in the field is presented in the bound control.
I haven't rewritten this code to make it generic, because I don't want to introduce any errors. So I'll explain what fc
and cm
are, insofar as you need to know to get this to work.
fc
is an instance of a class that wraps my application's bound Control
objects. It has a Control
property (the wrapped control, obviously), and then Binding
and ErrorProvider
properties whose use is shown below. Note that since this code sets up the data binding for you, you don't set up the control's binding in the form designer. You don't, strictly speaking, need this class in order to make this code work, but it does simplify the code somewhat. (All of the code here is from a static method on the fc
class to which the BindingSource
has been passed, as shown.)
cm
is an instance of a class that contains metainformation about the data column that I'm binding the control to, notably:
ColumnName
, its name in the data source (I'm obviously binding to a DataColumn
),
PropertyName
, the name of the control property it's bound to (e.g. "Text"
),
NullValue
, as described in the documentation for Binding.NullValue
,
Format
, the method that formats the column's internal value for display in the bound control, and
Parse
, the method for parsing input into the column's internal value. The actual validation logic for a column lives here.
This is C#, obviously, so you'll need to mess with it in order to get it to work in VB, but the differences should simply be matters of syntax.
// add an ErrorProvider to the control so that we have a place to display
// error messages
fc.ErrorProvider = new ErrorProvider {BlinkStyle = ErrorBlinkStyle.NeverBlink};
// create the Binding. DataSourceUpdateMode.Never bypasses automatic updating
// of the data source; data only gets written to the data source when the
// column is successfully validated.
fc.Binding = fc.Control.DataBindings.Add(
cm.PropertyName,
bindingSource,
cm.ColumnName,
true,
DataSourceUpdateMode.Never,
cm.NullValue);
// this is called whenever the Binding pushes data back to the data source;
// it parses the data in the control into an object that's returned in e.Value.
fc.Binding.Parse += delegate(object sender, ConvertEventArgs e)
{
string property = fc.Binding.PropertyName;
object unparsedValue = fc.Control.GetType().GetProperty(property).GetValue(fc.Control, null);
string message;
// note that we don't actually care about the parsed value if message
// is not null (i.e. if the value is invalid). by convention it's null,
// but it won't ever get written back to the data source.
object parsedValue = cm.Parse(unparsedValue, out message);
if (message != null)
{
fc.ErrorProvider.SetError(fc.Control, message);
}
else
{
fc.ErrorProvider.Clear();
}
e.Value = parsedValue ?? DBNull.Value;
};
// this is called whenever the user leaves the Control.
fc.Control.Validating += delegate
{
string property = fc.Binding.PropertyName;
object value = fc.Control.GetType().GetProperty(property).GetValue(fc.Control, null);
string message;
cm.Parse(value, out message);
if (message != null)
{
fc.ErrorProvider.SetError(fc.Control, message);
}
else
{
fc.ErrorProvider.Clear();
}
};
// this, combined with the DataSourceUpdateMode of Never, insures that the Control's
// value only gets pushed out to the data source after validation is successful.
fc.Control.Validated += delegate {
fc.Binding.WriteValue();
};
// this is called whenever the Binding pulls data from the data source into
// the bound Control
fc.Binding.Format += delegate(object sender, ConvertEventArgs e)
{
e.Value = cm.Format(e.Value);
};