Editable table
To create an editable table for a list of Person
class objects, the following code can be used:
List<Person> people = new ArrayList<>();
EditableTable<Person> peopleTable = Tables
.editable(people)
.parent(parent)
.create();
IObservableList<Car> carsObservable = new WritableList<>(cars, Car.class);
Click here to see the definition of the Person
class in the example project at GitHub.
In this code, the carsObservable
variable is also created to facilitate editing the person’s car, as we will see in the upcoming sections.
1. Text editor
1.1. String field type
Let’s create two columns with a simple text editor. Both accept String
values, and value changes are committed based on the validation strategy.
In the first column, we configure the name field to reject empty values: if an empty string is entered, the value update is discarded.
The second column uses ValidationStrategies.stringAny()
and thus accepts any string input (including empty values).
Multiple validation strategies exist for various data types: numbers, colors, dates and times.
For instance, you can restrict input to only positive numbers, only negative numbers, or specific value ranges, as demonstrated below (See "Double field type" section).
A more custom validation logic can be added by extending the ValidationStrategy
class.
Additionally, any field can be marked as non-editable when needed.
peopleTable
.column(Person::getName)
.name("Name")
.textEditor()
.strategy(ValidationStrategies.stringIsNotEmpty())
.handler((person, name) -> person.setName(name))
.build();
peopleTable
.column(Person::getCountry)
.name("Country")
.textEditor()
.strategy(ValidationStrategies.stringAny()) // data update strategy
.handler((person, country) -> person.setCountry(country)) // send cell value to certain field of object
.build();

1.2. Integer field type
We can configure integer values to accept only positive numbers.
peopleTable
.column(Person::getAge)
.name("Age")
.textEditor()
.strategy(ValidationStrategies.integerPositive())
.handler((person, age) -> person.setAge(age))
.build();

1.3. Double field type
There are also multiple ways to handle double values. Consider the middle column as an example — we can assign a specific interval of valid values (this works for integers too). Additionally, there’s a dedicated method for percentage values, restricting input to the 0–100 range (see the right column for implementation).
Both columns correspond to the same field of the Person object, but have different ranges of acceptable inputs.
Consider what happens if the value of 50 is typed in these columns.
For the left column, the value will be discarded, as 50 is outside the acceptable range 5..20.
However, when typed into the right column, it will be saved and displayed in both columns because constraints work only with data that is being input in the cell of a certain column, not with the field of the object in general.
|
peopleTable
.column(Person::getDiscount)
.name("Discount")
.textEditor()
.strategy(ValidationStrategies.doubleBetweenValues(5, 20))
.handler((person, discount) -> person.setDiscount(discount))
.build();
peopleTable
.column(Person::getDiscount)
.name("Discount, %")
.textEditor()
.strategy(ValidationStrategies.doublePercentage())
.handler((person, discount) -> person.setDiscount(discount))
.build();

2. Combo editor
A simple comboEditor
:
peopleTable
.column(Person::getCar)
.name("Car")
.format(car -> String.valueOf(car.number()))
.comboEditor()
.elements(carsObservable) // observable list of cars to choose from
.format(car -> String.valueOf(car.number())) // show car as car number
.handler((person, car) -> person.setCar(car)) // update person's car with the selected one
.build();

autoCompleteComboEditor
allows selecting combo box elements using the "↓ Down" and "↑ Up" keys or pressing Ctrl+Space to open the dropdown list.

The dynamicAutoCompleteComboEditor
provides functionality similar to autoCompleteComboEditor
, but with one key difference:
when the actual list size exceeds the maxComboBoxElementsCount(int)
argument value,
the dynamic auto complete combo-box becomes hidden, while other types of combo-box remain visible.
In this case, the dynamicAutoCompleteComboEditor
behaves exactly as a regular text editor that only accepts values from the combo list. This behavior is demonstrated in the image below:
-
the left column shows standard behavior without
maxComboBoxElementsCount(int)
applied, -
the right column uses the
maxComboBoxElementsCount(int)
method with an argument value of 2 for a list of 3 elements, causing the combo box to remain hidden.
In other aspects, the behavior of dynamicAutoCompleteComboEditor
repeats that of autoCompleteComboEditor
.
peopleTable
.column(Person::getCar)
.name("Car with max combo box elements count")
.format(car -> String.valueOf(car.number()))
.dynamicAutoCompleteComboEditor()
.elements(carsObservable)
.format(car -> String.valueOf(car.number()))
.handler((person, car) -> person.setCar(car))
.maxComboBoxElementsCount(2) // set max elements count
.build();

The enum editor works similarly to other combo editor types. Like with any other editor, a field can be marked as non-editable when needed.
peopleTable
.column(Person::getTown)
.name("Town")
.format(Town::getName)
.enumComboEditor()
.elements(Town.values()) // set combo list as enum values
.format(Town::getName) // set format of showing enum values in combo list
.handler((person, town) -> person.setTown(town)) // handle chosen enum value
.build();

2.1. Combo settings
We will now discuss setup methods that are common to all combo editor types.
We can include a null element in the combo list and define its display text.
Additionally, we can specify a custom comparator to control the sorting of combo list items.
This code demonstrates inclusion of a null element and combo elements sorting:
peopleTable
.column(Person::getCar)
.name("Car")
.format(car -> String.valueOf(car.number()))
.comboEditor()
.elements(carsObservable)
.format(car -> String.valueOf(car.number()))
.handler((person, car) -> person.setCar(car))
.enableNullElement("NONE") // add null element in combo list and specify its value
.comparator((p1, p2) -> p1.number() - p2.number()) // add comparator for combo list
.build();

The maximum combo list size can be set using maxComboBoxElementsCount(int)
.
When the actual list exceeds this limit, excess elements will be hidden from view.
peopleTable
.column(Person::getCar)
.name("Car")
.format(car -> String.valueOf(car.number()))
.comboEditor()
.elements(carsObservable)
.format(car -> String.valueOf(car.number()))
.handler((person, car) -> person.setCar(car))
.enableNullElement("NONE")
.comparator((p1, p2) -> p1.number() - p2.number())
.maxComboBoxElementsCount(2) // max combo list size
.build();

Combo elements can be filtered:
peopleTable
.column(Person::getCar)
.name("Car")
.format(car -> String.valueOf(car.number()))
.comboEditor()
.elements(carsObservable)
.format(car -> String.valueOf(car.number()))
.handler((person, car) -> person.setCar(car))
.enableNullElement("NONE")
.comparator((p1, p2) -> p1.number() - p2.number())
.maxComboBoxElementsCount(2)
.filter(car -> car.number() > 50) // add filter
.build();

Also, a field can be marked non-editable.
3. Auto complete text editor
For text input that only accepts predefined values (similar to a text editor but with strict validation), use autoCompleteTextEditor
. The GIF below demonstrates how only country names existing in the source list are accepted for updates. Note that this editor also supports marking fields as non-editable.
peopleTable
.column(Person::getCountry)
.name("Country")
.autoCompleteTextEditor()
.elements(new WritableList<>(List.of("Spain", "Italy", "France"), String.class)) // set IObservableList of possible values
.format(string -> string) // in this case observable values are string by itself
.handler((person, string) -> person.setCountry(string)) // set chosen value as certain field
.build();

4. Checkbox editor
Create a column with checkbox editor for boolean field values:
peopleTable
.column(Person::isPreferential)
.name("Privilege")
.checkBoxEditor()
.handler((person, value) -> person.setPreferential(value)) // set action after checkbox press
.canEdit(person -> person.getAge() > 25) // some cells can be uneditable depends on table item value
.build();

Also we can change checkbox view.
peopleTable
.column(Person::isPreferential)
.name("Privilege")
.checkBoxEditor()
.handler((person, value) -> person.setPreferential(value))
.canEdit(person -> person.getAge() > 25)
.format(value -> value ? "YES" : "NO") // change checkbox view
.build();

5. Non-editable cells
All editor types include the canEdit()
method, which dynamically controls cell ability to being edited based on row values. For example, in the previous section, we prevented 'privilege' field edits when the person’s age is under 25. The demonstration below shows this logic in action:

6. Button action editor
Cell buttons can trigger custom actions when clicked. Like all editors, these button cells support the non-editable flag to disable interactions when needed.
peopleTable
.column(Person::getCountry)
.name("Country")
.buttonActionEditor()
.handler(person -> person.setCountry("Russia")) // action to do after button pressing
.buttonText("Change country") // set custom button text
.buttonToolTip("Change country to \"Russia\"")
.build();

7. Button-on-top editor
The buttonOnTop
editor functions like buttonActionEditor
, but displays buttons persistently (without requiring cell focus).
You can use an icon instead of text for the button.
Like all editors, fields can be marked as non-editable.
peopleTable
.column(Person::getCountry)
.name("Country")
.buttonOnTop()
.handler(person -> person.setCountry("Germany")) // action to do after button pressing
.buttonWidth(20) // button width
.buttonToolTip("Change country to \"Germany\"") // button tool tip
.icon(homeImage) // set icon instead of default button text
.build();

8. Date and time editor
Create a column with a date-time editor that works exclusively with the LocalDateTime
class. First, let’s examine date-time display formatting.
Amalgama Platform provides the Formats
class. Calling Formats.getDefaultFormats()
enables access to standard date-time formats, currency, and other unit measurements based on the current locale.
Additionally, you can mark fields as non-editable and customize the button appearance.
peopleTable
.column(Person::getPurchaseDate)
.name("Purchase date")
.format(Formats.getDefaultFormats()::dayMonthLongYearHoursMinutes) // instead of using simple toString view of LocalDateTime object
.localDateTimeEditor()
.handler((person, date) -> person.setPurchaseDate(date)) // handle date assigning
.build();

Use the clearExistButton()
method to add a 'Clear' button in the dialog window to reset the date to its default value (which is the current date-time by default).
The default date-time value can be configured via the newDateCreator()
method.
Note that cells can be restricted to date-only or time-only editing - in this case, we’ve selected date-only editing, which is why all cells in the following image show the default time of 12 a.m. (0:00).
peopleTable
.column(Person::getPurchaseDate)
.name("Purchase date")
.format(Formats.getDefaultFormats()::dayMonthLongYearHoursMinutes)
.localDateTimeEditor()
.handler((person, date) -> person.setPurchaseDate(date))
.clearExistButton() // add clear button to dialog window
.newDateCreator(() -> LocalDateTime.of(2025, 1, 1, 10, 10)) // set default value
.typeDialog().onlyDate() // only date without time
.build();

Editor of LocalTime
has the same methods as localDateTimeEditor
.
A local time editor can be created with the localTimeEditor()
method.
A field can be marked non-editable. Button view can be customized with an icon.
peopleTable
.column(Person::getArriveTime)
.name("Arrive time")
.format(Formats.getDefaultFormats()::hoursMinutes) // set format
.localTimeEditor()
.handler((person, time) -> person.setArriveTime(time)) // handle time setting
.build();

9. Color editor
A color editor column works with the Color
class.
A field can be marked non-editable.
peopleTable
.column(Person::getFavouriteColor)
.name("Favourite color")
.backgroundColor(Person::getFavouriteColor)
.colorEditor()
.handler((person, color) -> person.setFavouriteColor(color)) // handle color editing
.build();

10. Single object selection dialog editor
An alternative to combo editors is the object selection dialog editor.
A field can be marked non-editable. Button view can be customized with an icon.
peopleTable
.column(Person::getCar)
.name("Car")
.format(car -> String.valueOf(car.number()))
.objectSelectionDialogEditor()
.elements(carsObservable) // observable list of cars to choose from
.columns(dialogTable -> dialogTable.column(Car::number).name("Number")) // create dialog table of cars
.handler((person, car) -> {
person.setCar(car);
MessageBox messageBox = new MessageBox(parent.getShell(), 1 << 1);
messageBox.setMessage(String.format("The car with number %d is selected", car.number()));
messageBox.open();
}) // handle chosen car value as field of person
.dialogTitle("car") // set dialog title
.build();

11. Multiple objects selection dialog editor
Create a column using multiObjectsSelectionDialogEditor
for objects containing collection fields.
Like other editors, this one supports marking fields as non-editable and customizing the button appearance.
peopleTable
.column(Person::getCars)
.name("Cars")
.format(this::listFormat) // set function which perform list as string
.<Car>multiObjectsSelectionDialogEditor() // assign list elements type as generic
.elements(carsObservable) // observable list of all cars available to choose
.selectedElements(chosenCars) // list of chosen cars
.columns(dialogTable -> {
dialogTable.column(Car::number).name("Number");
}) // create dialog table
.handler((person, list) -> {
person.setCars(list);
MessageBox messageBox = new MessageBox(parent.getShell(), 1 << 1);
messageBox.setMessage("The cars are selected");
messageBox.open();
}) // handle list of chosen cars as corresponding field
.dialogTitle("available cars") // assign dialog title (optional)
.build();
