Tip: Validation with a MultiValidator

In the last blog entry regarding databinding, we learned how to create a custom observable for a DateTime widget. Starting with Eclipse 3.5 there is a new approach to creating custom observables as described by Matthew Hall in the comments to that post. Which ever way you choose, we will now use the observable to showcase another cool databinding feature: Crossvalidation. Crossvalidation was introduced with Eclipse 3.4 and describes the process of validating one IObservable based on the value or state of another IObservable. A classic example is the input  of a date ranges where the start of the period can not be after the end of the period.

databinding cross validation 300x91 Tip: Validation with a MultiValidator

As we can see on the screenshot above, the status message informs us of a violated validation constrain. So, how is such  kind of crosscutting validation achieved? First of we need some kind of model, to hold the start and end value. Next we need the two DateTime widgets and the corresponding DateTimeObservableValue. The observables can than be tied to the bean model, holding the start and end values. In order to create a crosscutting validation for the dates, we introduce a custom PeriodValidator extending a MultiValidator, which is in fact an implementation of a ValidationStatusProvider.The MultiValidator lets us implement a validate() method, returning an IStatus corresponding to our validation results. Since we are implicitly implementing a ValidationStatusProvider, the state of our validation can be bound to any party interested in the validation result. Here is the implementation of the PeriodValidator, comparing the start and end date (ignore the shortcomings of the Date class):

public class PeriodValidator extends MultiValidator {

private final IObservableValue start;
private final IObservableValue end;

public PeriodValidator(final IObservableValue start, final IObservableValue end) {
this.start = start;
this.end = end;
}

@Override
protected IStatus validate() {
Date startDate = (Date) this.start.getValue();
Date endDate = (Date) this.end.getValue();
IStatus status = ValidationStatus.ok();

if ((this.start != null) && (this.end != null)) {
     if (startDate.after(endDate)) {
          status = ValidationStatus.error(”The start date has to be before the end date.”);
     }
}
return status;
}
}

As we can see, there is not much magic going on in the PeriodValidator. Lets have a look at the broader context, in which this MultiValidator is applied. The following code demonstrates the overall setup of the databinding. Note how the validation status (wrapped in an IObservableValue) is obtained from the PeriodValidator and bound to the status Text widget.

private void createDatabinding() {

DateTimeObservableValue startObservable = new DateTimeObservableValue(this.dateTimeStart);
DateTimeObservableValue endObservable = new DateTimeObservableValue(this.dateTimeEnd);
DataBindingContext context = new DataBindingContext();

// bind start and end
UpdateValueStrategy modelToTarget = new UpdateValueStrategy(
UpdateValueStrategy.POLICY_UPDATE);
UpdateValueStrategy targetToModel = new UpdateValueStrategy(
UpdateValueStrategy.POLICY_UPDATE);
context.bindValue(
     startObservable, 
     BeansObservables.observeValue(this.period,Period.PROP_START), targetToModel, modelToTarget);

context.bindValue(endObservable, 
     BeansObservables.observeValue(this.period, Period.PROP_END), targetToModel, modelToTarget);

// bind status
PeriodValidator periodValidator = new PeriodValidator(startObservable, endObservable);
modelToTarget = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
modelToTarget.setConverter(new Converter(IStatus.class, String.class) {

public Object convert(final Object arg) {

if (arg instanceof IStatus) {
     IStatus status = (IStatus) arg;
     return status.getMessage();
}

return null;
}
});

targetToModel = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
context.bindValue(
     SWTObservables.observeText(this.status), 
     periodValidator.getValidationStatus(), targetToModel, modelToTarget);
}

The validation status of the period validator is updated everytime any of the DateTime widgets is changed. In turn the validation message is updated to reflect the validation state.

You can download the the entire project containing the sample snippets here.

Also note that you will need to adjust the elements on the classpath. See the .project file for the required libs.

I hope you found these hints valuable and am looking forward to your comments and suggestions.

One Response to “Tip: Validation with a MultiValidator”

  1. Matthew Hall says:

    Another cool thing about MultiValidator is that it helps you suspend target-to-model updates until validation passes. In the above example, we can prevent the model period start and end from being updated with invalid values. If you create the PeriodValidator after startObservable and endObservable but before the bindings, you can:

    context.bindValue(
        periodValidator.observeValidatedValue(startObservable),
        BeansObservables.observeValue(this.period, Period.PROP_START) );
    context.bindValue(
        periodValidator.observeValidatedValue(endObservable),
        BeansObservables.observeValue(this.period, Period.PROP_END) );

One response so far

Written by . Published in Categories: Planet Eclipse

Author:
Published:
Feb 27th, 2009
Follow:

Google+