Container editors
A container editor edits a property of an object stored in a WritableValue. The editor reads the property value from the object in the container and writes the new value back to the same object. When you put another object into the container, all editors bound to it show the properties of the new object.
Container editors require an SWT Composite, the parent widget that hosts the editor UI. The parent Composite is passed to the builder with the .parent(parent) method.
|
Add these bundles to Require-Bundle in the MANIFEST.MF of your plug-in:
com.amalgamasimulation.desktop.ui.editor com.amalgamasimulation.desktop.binding org.eclipse.core.databinding.observable org.eclipse.swt
1. How an editor works
Each editor is a section: a row with a label, an input control, and optional buttons.
Apart from that, container editors work like standalone editors. They are created by the same factory methods of the Sections class and use the same validation strategies, buttons, and properties. This article describes only the differences.
To add an editor:
-
Call one of the factory methods of the
Sectionsclass:stringEditor(),integerEditor(),doubleEditor(),comboBoxEditor()and others. Each method accepts aWritableValuethat holds the object being edited and returns a builder. -
Configure the editor step by step with the returned builder: set the parent
Composite, thevalueExtractor()function that reads the value from the object, the label, the validation strategy, and the value changed handler; optionally, add buttons and tooltips or set other properties. -
Call
.create()to build and display the editor.
The section handles the value as follows:
-
When the section is created, it reads the value of the object in the container and displays it.
-
When the user presses
Enteror the field loses focus, the editor validates the input. -
If the value is invalid, the editor shows an error icon, does not call the value changed handler, and restores the current value.
-
If the value is valid but unchanged, the handler is not called.
-
If the value is valid and different, the handler is called with the new value.
1.1. Refreshing an editor
When another object is put into the WritableValue, the section refreshes itself: it reads the value of the new object and recalculates the label, visibility, the enabled state and the buttons. The section does not track changes inside the object. When your code changes a value of the object in the container or a condition used by visible() or enabled(), refresh the section:
| Method | When to call |
|---|---|
|
The value or the label text has changed. Also refreshes visibility, the enabled state and the buttons. |
|
Only the condition passed to |
|
Only the condition passed to |
create() returns the section; store it in a field to refresh it later. In the following example, the total row displays a value computed from the object in the container and is refreshed by the handler of the quantity field:
WritableValue<Order> order = new WritableValue<>(new Order(2, 12.5), Order.class); // Order has a quantity and a price
ICustomSection totalSection;
Sections
.integerEditor(order)
.parent(parent)
.valueExtractor(Order::getQuantity)
.label("Quantity")
.validationStrategy(ValidationStrategies.integerPositive())
.handler((o, newValue) -> { o.setQuantity(newValue); totalSection.updateSection(); }) // the object in the container changed
.create();
totalSection = Sections
.customSectionBuilder(parent)
.addLabel(LabelProperties.label().text("Total"))
.addLabel(LabelProperties.label().text(() -> Formats.getDefaultFormats().customDecimals(order.getValue().getTotal(), 2)).font(boldFont)) // the text is a supplier
.createSection();
The examples in this article edit Truck objects. Any plain Java object with getters and setters works:
public class Truck {
private String name;
private double capacity;
private boolean active;
private Driver driver;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getCapacity() { return capacity; }
public void setCapacity(double capacity) { this.capacity = capacity; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
public Driver getDriver() { return driver; }
public void setDriver(Driver driver) { this.driver = driver; }
}
public class Driver {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
List<Truck> trucks = ...; // the trucks to edit
List<Driver> drivers = ...; // the drivers to choose from
WritableValue<Truck> selectedTruck = new WritableValue<>(); // holds the object being edited
The examples below use these variables.
2. Switching the edited object
Unlike standalone editors, a form of container editors can edit different objects of the same type, one at a time. To switch, put another object into the container.
The form below edits the Truck selected by the radio buttons. The parent is any Composite with a GridLayout, such as a dialog area. The editors are created once, and the radio buttons set the selected Truck as the container’s value:
for (Truck truck : trucks) {
Button radio = new Button(parent, SWT.RADIO);
radio.setText(truck.getName());
radio.addListener(SWT.Selection, e -> {
if (radio.getSelection()) {
selectedTruck.setValue(truck); // editors show the selected truck
}
});
}
SectionGroup group = new SectionGroup(parent, SWT.NONE); // aligns the labels
Sections
.stringEditor(selectedTruck)
.parent(group)
.valueExtractor(Truck::getName)
.label("Name")
.validationStrategy(ValidationStrategies.stringIsNotEmpty())
.handler(Truck::setName)
.create();
Sections
.doubleEditor(selectedTruck)
.parent(group)
.valueExtractor(Truck::getCapacity)
.label("Capacity")
.validationStrategy(ValidationStrategies.doublePositive())
.handler(Truck::setCapacity)
.create();
The handler writes the new value to the object as soon as the user commits it by pressing Enter or leaving the field. When the user selects the same Truck again, the editor shows the changed value. SectionGroup is described in the standalone editors article.
While the container holds null, for example before the user selects a Truck, the editors show empty fields, and anything typed into them is not saved.
3. Editors
Each editor’s configuration chain starts with a factory method of the Sections class that accepts the WritableValue with the object being edited. The table lists the required steps and settings of each editor. The steps are the same as for standalone editors, plus valueExtractor(). label(), visible() and enabled() also accept a function of the object in the container.
| Editor | Required steps | Input settings | Common settings |
|---|---|---|---|
|
|||
|
|
||
|
|
||
|
|||
|
|||
|
|
The combobox, autocomplete and enum editors need both types in the call, for example Sections.<Truck, Driver>comboBoxEditor(selectedTruck).
Creating a double editor for the capacity of the selected Truck:
Sections
.doubleEditor(selectedTruck) // the container with the object being edited
.parent(parent)
.valueExtractor(Truck::getCapacity) // reads the value from the object
.label("Capacity")
.validationStrategy(ValidationStrategies.doublePositive())
.handler(Truck::setCapacity) // gets the object and the new value
.create();
handler() accepts a BiConsumer of the object in the container and the new value, so a setter reference fits here. label(), enabled() and visible() can also take a function of the object, see Fine-tuning.
If the user input does not pass the validation strategy, the value is not written to the object, and the input control returns to the current value when the user leaves it.
Each editor below has the same settings as its standalone counterpart.
3.1. Text-based editors
3.1.1. Double editor
Sections.doubleEditor(WritableValue<T>) edits a Double value. The call chain is shown in the example above. Validation strategies are listed in the validation strategies table of the standalone editors article. Other settings are described in the standalone double editor.
3.1.2. Integer editor
Sections.integerEditor(WritableValue<T>) edits an Integer value. Validation strategies are listed in the validation strategies table of the standalone editors article. Other settings are described in the standalone integer editor.
3.1.3. String editor
Sections.stringEditor(WritableValue<T>) edits a String value. Validation strategies are listed in the validation strategies table of the standalone editors article. Other settings are described in the standalone string editor.
3.2. ComboBox editor
Sections.comboBoxEditor(WritableValue<T>) selects the value from a list. Java cannot infer the element type from the container, so explicitly pass both the type of the container object and the type of the element being edited. The format function accepts the object from the container and a list element, and returns the text shown for that element:
Sections
.<Truck, Driver>comboBoxEditor(selectedTruck) // the object type and the element type
.parent(parent)
.valueExtractor(Truck::getDriver)
.label("Driver")
.elements(drivers)
.format((truck, driver) -> driver.getName()) // gets the object and the element
.handler(Truck::setDriver)
.create();
See the standalone combobox editor and its settings for filtering, sorting and canBeNull().
3.3. Autocomplete editor
Sections.autoCompleteEditor(WritableValue<T>) is set up like the combobox editor: explicit types, elements(List) and a format function of the object and the element. The rest is described in the standalone autocomplete editor.
3.4. Enum editor
Sections.enumEditor(WritableValue<T>) also needs explicit types. It takes an array in elements() and a format function of the object and the element. See the standalone enum editor.
3.5. CheckBox editor
Sections.checkBoxEditor(WritableValue<T>) edits a boolean value with a checkbox. It has no validation strategy. See the standalone checkbox editor.
3.6. LocalDateTime editor
Sections.localDateTimeEditor(WritableValue<T>) edits a LocalDateTime value. date(), newLocalDateTime(), withoutDefaultButton() and canBeNull() work as in the standalone LocalDateTime editor.
3.7. LocalTime editor
Sections.localTimeEditor(WritableValue<T>) edits a LocalTime value. newLocalTime(), withoutDefaultButton() and canBeNull() work as in the standalone LocalTime editor.
3.8. Color editor
Sections.colorEditor(WritableValue<T>) edits a color value. withoutDefaultButton() and canBeNull() work as in the standalone color editor.
3.9. Real distribution editor
Sections.realDistributionEditor(WritableValue<T>) edits a RealDistribution value. withoutDefaultButton() and canBeNull() work as in the standalone real distribution editor.
3.10. Time Series editor
Sections.timeSeriesEditor(WritableValue<T>) edits a TimeSeries value. withoutDefaultButton() and canBeNull() work as in the standalone time series editor.
4. Fine-tuning
label(), enabled() and visible() accept a function of the object in the container:
ITextSection<Double> capacitySection = Sections
.doubleEditor(selectedTruck)
.parent(parent)
.valueExtractor(Truck::getCapacity)
.label(truck -> truck == null ? "Capacity" : "Capacity of " + truck.getName())
.validationStrategy(ValidationStrategies.doublePositive())
.handler(Truck::setCapacity)
.enabled(truck -> truck != null && truck.isActive()) // the field is enabled for active trucks only
.create(); // returns the section
While the container is empty, the functions receive null.
toolTip(), labelProperties(), textProperties(), comboBoxProperties() and addButton() work as described in the Fine-tuning chapter of the standalone editors article.
5. Buttons
Besides the buttons described in the standalone editors article, two new button types are described below.
5.1. Button with your own action
Buttons.containerButton() creates a button with your own action, like Buttons.simpleButton() in standalone editors, but the action also receives the object in the container. The action() method accepts the SWT event, the object in the container and the current value:
Sections
.doubleEditor(selectedTruck)
.parent(parent)
.valueExtractor(Truck::getCapacity)
.label("Capacity")
.validationStrategy(ValidationStrategies.doublePositive())
.handler(Truck::setCapacity)
.addButton(Buttons.<Truck, Double>containerButton() // the action gets the object and the value
.action((event, truck, value) -> {
MessageBox messageBox = new MessageBox(parent.getShell(), SWT.ICON_INFORMATION);
messageBox.setMessage("Capacity of " + truck.getName() + " = " + value);
messageBox.open();
}))
.create();
5.2. Element selection button
Buttons.selectElementFromTable() creates a button that opens a dialog with a table of elements. The element the user selects becomes the new value of the editor:
Sections
.<Truck, Driver>comboBoxEditor(selectedTruck)
.parent(parent)
.valueExtractor(Truck::getDriver)
.label("Driver")
.elements(drivers)
.format((truck, driver) -> driver.getName())
.handler(Truck::setDriver)
.addButton(Buttons.<Truck, Driver>selectElementFromTable()
.title("driver") // shown in the dialog title
.values(() -> drivers) // elements in the table
.columns(table -> table.column(Driver::getName).name("Name").width(200))) // table columns
.create();
filter() and comparator() limit and sort the elements in the table. customAction((truck, driver) → …) replaces the call to the editor’s handler when the user selects an element. The other button settings are described in the standalone editors article.
6. Common mistakes
Container editors share settings and buttons with standalone editors, so also check the common mistakes of standalone editors. The table below lists the mistakes specific to container editors.
| Symptom | Cause | Fix |
|---|---|---|
The fields are empty, and typed values are not saved. |
The container holds |
Put an object into the container with |
The field still shows the old value after your code changes the object. |
A section reacts only when another object is put into the container. |
Call |
The fields do not change after the same object is put into the container again. |
|
Call |
The editors ignore new objects. |
The code assigns a new |
Keep one container and call |
|
While the container is empty, the functions receive |
Check for |
|
While the container is empty, the action receives |
Check the object for |
A custom section keeps showing the old object after another object is put into the container. |
|
Refresh it from a listener: |
Compilation error |
Java cannot infer the element type of the combobox, autocomplete or enum editor from the container. |
Pass both types: |
The value does not change after the user selects an element in the dialog of |
The button has |
Write the element in |
The dialog of |
The button has no |
Pass the elements: |