Eclipse Fieldviewer
  Working with Eclipse Fieldviewer widgets.

Tutorial

Introduction

The main goal of Eclipse FieldViewer is to provide an easy to use abstraction for the display of structured data. While a table viewer displays all it's data at once (provided there is enough display space), a fieldviewer only shows one element (generally a java object) at a time. Unlike a table viewer the layout of individual fields is only limited to the developers/designers joice (and probably the constraints given by swt/jface).

In this tutorial a fictious example is used that creates a dialog displaying data for power generators. The layout of this dialog is inspired by a similar example in JGoodies::Form.

Example Dialog

Prerequisites

Details on how to obtain a working example will follow. The tutorial focuses on using Eclipse Fieldviewer in your own (already working?) code (perhaps an already created Dialog()).

Creating the Fieldviewer

Eclipse Fieldviewer inherits from StructuredViewer. If you are familliar with TableViewer() you certainly have no problem using a FieldViewer() as most of the tasks are very similar.

Creating a fieldviewer widget is the first task.

 1 FieldViewer ffv;
 2
 3 Composite top=new Composite(parent,SWT.NONE);
 4 ffv=new FieldViewer(top,SWT.BORDER|SWT.H_SCROLL|SWT.V_SCROLL);
 5 fv.getControl().setLayoutData(new GridData(SWT.FILL,SWT.FILL,true,true));
 6 top.setLayout(new GridLayout(1,true));
 7 
 8 initFieldViewer(ffv);
 9 ffv.setInput(model);

A FieldViewer is created like any other widget giving it a parent control and some flags. A FieldViewer uses an underlying Field control to render its items so you may also precreate the Field() and give it to the fieldviewer.

initFieldViewer() on line 8 contains all the necessary initialisations.

On line 9 the data to display in the viewer is set. This data can either be a single object or collection of objects (the signature of model must match the expectations of the ContentProvider - see below). In this example model is simply an array of PowerGen objects.

Providers

Providers allow a fairly generic viewer to adapt to different model data. The model given to FieldViewer.setInput() is first given to a ContentProvider() which returns a list of PowerGen elements (that is rows in the table analogy, with the difference that a fieldviewer only displays a single row at any one time).

A LabelProvider() is used to convert the raw value from an element to either a text string or an image. If the LabelProvider() also implements the interfaces IFieldColorProvider() and/or IFieldFontProvider() then fonts and colors of the corresponding entry fields may be set according to the contents of the elements data.

 1 private initFieldViewer(FormFieldViewer ffv) {
 2    ffv.setContentProvider(new FieldContentProviderAdapter() {
 3       public Object[] getElements(Object inputEelement) {
 4          return (PowerGen[])inputElement;
 5       }
 6    });
 7
 8    ffv.setLabelProvider(new FieldLabelProviderAdapter() {
 9       public String getFieldText(Object element, int idx) {
10          PowerGen t=(PowerGen)element;
11          if(idx==0) return t.id;
12          if(idx==1) return String.format("%.3f",t.power);
            ...more...
14       }
15       public String getFieldImage(Object element, int idx) {
16          PowerGen t=(PowerGen)element;
17          if(idx==0) return null;
            ...more...
19          if(idx==13) return getImage(t.hasHoles()?"holes":"noholes");
20       }
21    });
22
23    ...create layout...
24 }

On line 2 we create a new IFieldContentProvider. For this example we can use FieldContentProviderAdapter, a class that implements all interface methods but generally does nothing. The model data provided to FormFieldViewer.setInput() is used to return a list of elements. In our simple example this already an array of PowerGen-objects so we can directly return it. Real-world code will perhaps need a more sophisticated content provider.

The label provider on line 8 also uses a FieldLabelProviderAdapter to implement IFieldLabelProvider. The method getFieldText() on line 9 returns a text string or null. getFieldImage() on the other hand (line 15) returns an image for the given entry (if null is returned then no image is displayed).

The field layout is created using an IFieldLayoutProvider(). This provider is responsible for creating and positioning FieldItems. A FieldItem is a graphical control can can display either a text string (the string itself is selectable and can be copied) or an image or both (the "Holes"-entry in the screenshot at the top).

The standard layout of a FieldViewer uses GridLayout to position it's widgets (although a LayoutProvider is free to override and use a better suited layout algorithm). As a convenience for grid layouts LabeledFieldItem creates both a label and a FieldItem.

 1    ffv.setLayoutProvider(new IFieldLayoutProvider() {
 2       public void createFieldItems(Field field, Composite parent) {
 3          GridLayout gd=(GridLayout)parent.getLayout();
 4          gd.numColumns=5;
 5          
 6          Label sep;
 7          sep=new Label(parent, SWT.NONE);
 8          sep.setText("Segment");
 9          sep.setLayoutData(new GridData(SWT.FILL,SWT.CENTER,true,false,5,1));
10
11          new LabeledFieldItem(field,SWT.BORDER,"Identifier");
12
13          ...more...
14       }
15    });

The method createFieldItems() on line 2 is called with the parent control (a Field is analoguous to a Table in a TableViewer) and a composite into which to place new controls (you can use any swt/jface components here, but only FieldItems allow data binding later on).

The layout of this example is done using standard techniques. FieldItems generate the controls needed to display the bound data. In this case a LabeledFieldItem on line 11 generates a label titled "Identifier" and a text-field that displays the id of a data element.

Note: This simple example does not exactly generate the same output as the screenshot above - "Segment" is generated by a LabelSeparator, a selfmade class inspired by the JGoodies::Forms article. The background color of the fields is explicitly set in createField to visually denote that the fields are not editable. Making fields editable is described below.

Navigation

The element shown can be changed with one of the following calls:

showFirstElement();
showNextElement();
showPreviousElement();
showLastElement()
showElement();

In a real application actions will be bound to these methods so the user can skip between the elements of his dataset.

Sorting and Filtering

Both sorting and filtering use the standard methods provided by StructuredViewer. As the user only sees one element of the dataset at any one time both functions are probably not very intutive to use. They need additional user interaction elements (menus, etc) that are very application specific and not part of this simple example.

Editing Fields

To make a field editable two steps are necessary: add an ICellModifier and the corresponding CellEditor. Both interfaces are the same as in the standard TableViewer.

 1    ffv.setCellModifier(new ICellModifier() {
 2       public boolean canModify(Object element, String property) {
 3          return true;
 4       }
 5
 6       public Object getValue(Object element, String property) {
 7          PowerGen t=(PowerGen)element;
 8          if(property.equals("id")) return t.id;
 9          ...more...
10       }
11
12       public void modify(Object element, String property, Object value) {
13          PowerGen t=(PowerGen)element;
14          if(property.equals("id")) t.id=(String)value;
15          ...more...
16       }
17    });
18
19    fv.setFieldProperties(new String[] {"id","power",...more...});

A cell modifier is responsible for moving a field value from an element into a cell editor and from the cell editor back to the field value. Additionally it can prohibit editing of a given field if this should depend on the state of the active element. If a field should never be editable it is generally better to set null as the CellEditor for the field.

setFieldProperties in line 19 adds a translator between field indices (in creation order) and a field name. This field name is used as property in the calls to the CellModifier.

 1    ffv.setCellEdtiors(new CellEditor[] {
 2       new TextCellEditor(), // id
 3       new TextCellEditor(), // power
 4       ...more...
 5       new CheckboxCellEditor(),
 5    });

CellEditors are the same as in standard TableViewers. They are added to the FieldViewer in the sequence of field creation. Null may be used to denote a readonly field.

Conclusion

The above example should give you a start with the Eclipse FieldViewer. Some of the concepts are not very intuitive at first (why use FieldItem()s (and even CellEditors)when a simple Text()-widget can achive the same effect? A FieldViewer is a more generic concept and the better part of it comes into play if you start to write your own layout providers (eg a provider that is configurable from a XML-file) or if you begin using more sophisticated entry fields that inherit from the simple FieldItem (eg a DecoratedFieldItem using decorations to show annotations or error icons).

In general a FieldViewer is worth the time if you have more than one dialog to design, have layout/design constraints over a big project (forcing all the dialogs to look the same) or if you enhance FormFieldViewer with additional routines (eg some sort of database frontend).