Databind and validate a TableViewer?

flumins picture flumins · Jul 29, 2010 · Viewed 14.2k times · Source

I use the org.eclipse.core.databinding framework to bind some Text fields in an SWT application. I add an update strategy to validate the data and to set the value on the model only when the user click on the save button:

    UpdateValueStrategy toModel = new UpdateValueStrategy(UpdateValueStrategy.POLICY_CONVERT);
    if (validator != null) {
        toModel.setAfterGetValidator(validator);
    }

    UpdateValueStrategy fromModel = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);

    binding = bindingContext.bindValue(SWTObservables.observeText(this, SWT.Modify),
                    BeansObservables.observeValue(pVO, propertyName), toModel, fromModel);

This piece of code works really well.

But how can I do the same on a TableViewer?

I want it to work so that when I add something in the IHM, the model stay unchanged until I call getBindingContext().updateModels();

Answer

qrtt1 picture qrtt1 · Aug 13, 2010

You do not need use the JFace Databinding Framework in TableViewer. Manipulation the structured data is simpler then SWT controls, such TableViewer, ListViewer and TreeViewer. You can use those viewer in the same way:

  • create viewer
  • set content provider
  • set label provider (suggested)
  • set filter (optional)
  • set sorter (optional)

After the viewer created, just invoke viewer.setInput(data) to put all the things to your viewer.

There are a list of model:

TableViewer tableViewer = new TableViewer(parent); 

Table table = tableViewer.getTable(); 
table.setHeaderVisible(true);      
table.setLinesVisible(true);`

for (int i = 0; i < COLUMN_NAMES.length; i++) {
    TableColumn tableColumn = new TableColumn(table, SWT.LEFT);
    tableColumn.setText(COLUMN_NAMES[i]);
    tableColumn.setWidth(COLUMN_WIDTHS[i]);
}

tableViewer.setContentProvider(new ModelContentProvider());
tableViewer.setLabelProvider(new ModelLabelProvider());
tableViewer.setInput(models);

The magic happens in the content provider:

class ModelContentProvider implements IStructuredContentProvider {

    @SuppressWarnings("unchecked")
    @Override
    public Object[] getElements(Object inputElement) {
        // The inputElement comes from view.setInput()
        if (inputElement instanceof List) {
            List models = (List) inputElement;
            return models.toArray();
        }
        return new Object[0];
    }

/* ... other methods */

}

Each model will become a TableItem and the model in the TableItem(item.getData()).

However, a table composed by many columns, you need the LabelProvider to help you mapping the property of model to the TableItem:

class ModelLabelProvider extends LabelProvider implements
        ITableLabelProvider {

    @Override
    public Image getColumnImage(Object element, int columnIndex) {
        // no image to show
        return null;
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {
        // each element comes from the ContentProvider.getElements(Object)
        if (!(element instanceof Model)) {
            return "";
        }
        Model model = (Model) element;
        switch (columnIndex) {
        case 0:
            return model.getFoo();
        case 1:
            return model.getBar();
        default:
            break;
        }
        return "";
    }
}

The propagation of models to viewer is easy. If you will propagate viewer to the binded model, using the CellEditor is simple as well. To use CellEditor, you need set the column properties, cell editors and cell modifier to TableViewer:

tableViewer.setColumnProperties(COLUMNS_PROPERTIES);
tableViewer.setCellEditors(new CellEditor[] {
        new TextCellEditor(table), new TextCellEditor(table) });
tableViewer.setCellModifier(new ModelCellModifier(tableViewer));

The CellModifier likes this:

class ModelCellModifier implements ICellModifier {
    TableViewer viewer;

    public ModelCellModifier(TableViewer viewer) {
        this.viewer = viewer;
    }

    @Override
    public boolean canModify(Object element, String property) {
        // property is defined by viewer.setColumnProperties()
        // allow the FOO column can be modified.
        return "foo_prop".equals(property);
    }

    @Override
    public Object getValue(Object element, String property) {
        if ("foo_prop".equals(property)) {
            return ((Model) element).getFoo();
        }
        if ("bar_prop".equals(property)) {
            return ((Model) element).getBar();
        }
        return "";
    }

    @Override
    public void modify(Object element, String property, Object value) {
        if ("foo_prop".equals(property)) {
            TableItem item = (TableItem) element;
            ((Model) item.getData()).setFoo("" + value);

            // refresh the viewer to show the changes to our user.
            viewer.refresh();
        }
    }
}

Everything is simple but there are many steps to make all together.