Menu

ManualLayerBindingValidation

Remo

Binding and Validation Layer

This layer combines the model adapter layer with the widget wrapper layer and unfolds the power of rcpforms.

It is highly recommended to use at least this layer for creating forms; the lower layers are mainly provided for already existing projects which do not want to buy e.g. into Eclipse Databinding or they already have their own widget wrappers but want to use databinding support, as well as keeping a clean separation of concerns.

In this layer the concept of bindable parts is introduced (interface IBindablePart, RCPFormPart), which are the building blocks of forms. A form is a container of n bindable parts, each part is bound to a model or a part of a model tree called a submodel. Bindable parts and models can be put into a hierarchy using the composite pattern (not yet in v0.7). Bindable parts are associated with submodels via an id.

ValidationManager

The class ValidationManager is the central class in the binding and validation layer. It binds a bindable part (or a composite bindable part) to a model (or a composite model).

The Validation Manager offers several convenience methods to bind wrapper to model attributes which hide a lot of nitsy details of using eclipse databinding directly. It automatically creates the observable which suits a control, creates a range adapter for combo boxes or radio groups, inserts converters into the update strategies and listens to all created bindings for changes.

The ValidationManager additionally keeps track of validators, places error markers on controls if validations fail and updates the validation state when attributes change which the validator depends on.

Your form can consist of an arbitrary number of IBindableParts (currently only RCPFormPart) which are bound to the same number of models. Multiple form parts can share the same model or you can use different models, whatever you prefer.

If you have several parts which need to share part-spanning validations, error management and dirty state, you should use one ValidationManager for all.
Usually one form will consist of several form parts, e.g. for each section one. You would use one ValidationManager per form.
For a multi-page editor or a wizard you might want to use one ValidationManager for all pages, this is possible too.

For each form part bound with a validation manager it associates the bindings created by this form part, the widgets which were bound and the validators registered for the form part. Thus you can rebind each form part independently and the ValidationManager will keep track of error markers and so on.

For one validation manager you need one standard Eclipse IMessageManager which is used to manage the error messages for the ValidationManager.

Value Binding

The main method to bind a wrapper to a model attribute should be called in your IBindablePart in the bind() method.

You bind a wrapper to a model attribute and the binding will update values in both directions if they change; e.g. setting the attribute value will update the text field and vice versa.

Usually the default behavior will do fine; if you need to modify the default behavior, you should subclass ValidationManager and override one of the methods
IObservableValue getObservableValue(RCPWidget widget) and IConverter getConverter(RCPWidget widget, boolean modelToTarget) to use different converters dependent on the widget type or different ObservableValues for binding.

The idea is to default as much as possible, so your application developers need not set up a date converter in each form separately, but this is done in one central place for the whole application, here in the ValidationManager.

State Binding

State Binding binds not against the value represented by the widget, but a widget state like mandatory, read-only, visible, enabled. The Presentation Model approach enforces using binding instead of hooking listeners to all events; this leads to a much clearer architecture and much simpler and easier testable code.

Mostly states of UI controls are defined by business logic, e.g. if your account is overdrawn, all controls needed to withdraw are disabled. Your model needs to maintain a boolean withDrawEnabled flag dependent on business logic; you can test this in a simple unit test directly on the model without needing a gui at all.

Then you bind the enabled state of all widget wrappers involved in withdrawal against the boolean and voila, it works. State Binding only works correctly on widget wrappers, if you use state binding directly with SWT controls (using Eclipse databinding), wrappers will overwrite the state set on the control.

Widget wrappers have hierarchical states and state intelligence, which SWT controls do not have, e.g. disabling a parent will automatically disable the children and remove all mandatory markers as well. Opposed to value binding, state binding works only one-way, since the user cannot interact with widget state; you set it in the model, the widget will follow. If you set it directly on the widget, it will be overwritten on the next model change or forced update. Thus never set states directly on the widget if you use state binding.

List Binding

List binding is available for controls supporting a viewer and synchronize the selection of the viewer with the contents of the list; all selected elements in the viewer will be put into the list and vice versa. You can use it with Tables and Checkbox Tables, but be aware that for editable tables you should use checkboxes for row selection, since row selection will conflict with table editors. Use WritableList for these lists since they provide efficient change notifications, which e.g. JavaBean indexed property do not provide.

Radio Group Binding

Radio Groups are special in that a group of controls refers to one model value. Class RadioGroupManager associates n radio buttons with n model values,
The method

    public Binding bindValue(RadioGroupManager rgManager, Object bean, Object... properties)

will bind the radio group manager to the property. The radio group manager will automatically sync selection in both directions; this enables you to use radio buttons without needing a RCPGroup widget or a RCPRadioGroup convenience wrapper. See the Sandbox example application for various ways to create radio buttons and manage them.
The RCPRadioGroup control offers easier out-of-the-box behavior to set up radio groups; internally it still sets up a RadioGroupManager to handle things; if you bind a RCPRadioGroup, actually its RadioGroupManager is bound.
An interesting feature is the automatic creation of radio buttons using a range adapter; this lets you dynamically create radio buttons for all values in the range; e.g. for enum-like values which are extensible by the user, e.g. Expense Category might be a good example, or salutation, which is a mainly fixed nr of choices, but might be extended by administrators. In business applications enum-like values are usually maintained in a database and only editable by a small set of administrators.

Validators

A validator implements the IModelValidator interface, it must use the AbstractModelValidator base class to be robust against future interface changes.
It declares the properties validation state depends on and implements the validate method on the model returning an IStatus containing the error message.

You register it for a part with the validationManager and the validation manager will do the rest. Validation will always be up to date if you change properties programmatically, load them from a server, the user changes the properties using ui controls and so on.
Thats it !

        validationManager.addValidator(part3, new AbstractModelValidator()
        {
            public Object[] getProperties()
            {
                return new String[]{AddressModel.P_ValidFrom, AddressModel.P_ValidTo};
            }

            public IStatus validate(Object value)
            {
                AddressModel model = (AddressModel) value;
                IStatus result = ok();
                if (model.getValidFrom() != null && model.getValidTo() != null
                        && model.getValidFrom().after(model.getValidTo()))
                {
                    result = error("From Date must be earlier than To Date");
                }
                return result;
            }
       });

Automatic Setup of Range Adapters

If you use the bindValue method for a RCPControl which supports ranges (combo and radio group),
the ModelAdapter will be queried for a range adapter for the type of the attribute bound.
This range adapter will be used to create radio group buttons or combo entries.

See Range Adapter

Example setting up a ValidationManager

In RCPFormFactory there are some convenience methods setting stuff up, this is a good example of how to install a ValidationManager manually.
The RCPForm of the form layer (not in v0.7) will do this all for you, but still it's a good idea to know how things work.

    /**
     * convenience method to create a form displaying n form parts and binding it to n corresponding
     * models
     *
     * @param validationManager binding and validation manager to use for this form
     * @param initializer will be called after all form parts are created and bound
     */
    public ManagedForm createForm(final String title, final Composite parent,
                                  final RCPFormPart[] formParts, final Object[] models,
                                  final ValidationManager validationManager, final Runnable initializer)
    {
        Validate.isTrue(parent != null);
        Validate.isTrue(formParts != null);
        Validate.isTrue(models != null);
        Validate.isTrue(formParts.length == models.length,
                "FormParts and Models must correspond 1:1");
        ManagedForm managedForm = null;
        try
        {
            managedForm = createManagedForm(parent, title, getToolkit(Display.getCurrent()));
            final FormToolkit toolkit = getToolkit(parent.getDisplay());
            final ManagedForm mf = managedForm;
            Realm.runWithDefault(Realm.getDefault(), new Runnable()
            {
                public void run()
                {
                    ValidationManager vm;
                    if (validationManager == null)
                    {
                        vm = createBindingAndValidationManager(title);
                    }
                    else
                    {
                        vm = validationManager;
                    }
                    vm.initialize(mf.getMessageManager());
                    for (int i = 0; i < formParts.length; i++)
                    {
                        formParts[i].createUI(toolkit, mf.getForm().getBody());
                        vm.bindPart(formParts[i], models[i]);
                    }
                    // call initializer if one was passed
                    if (initializer != null)
                    {
                        initializer.run();
                    }
                }
            });
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return managedForm;
    }

Related

Documentation: ManualCoreConcepts
Documentation: ManualLayerModelAdapterRangeAdapter
Documentation: ManualLayers

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.