1. Table UI
1.1. FilterTable
The FilterTable is a JTable subclass central to the framework.
// See FilterTableModel example
FilterTableModel<Person, String> tableModel = createFilterTableModel();
// Create the columns, specifying the identifier and the model index
List<FilterTableColumn<String>> columns = List.of(
FilterTableColumn.builder()
.identifier(Person.NAME)
.modelIndex(0)
.build(),
FilterTableColumn.builder()
.identifier(Person.AGE)
.modelIndex(1)
.build());
FilterTable<Person, String> table =
FilterTable.builder()
.model(tableModel)
.columns(columns)
.doubleClick(Control.command(() ->
tableModel.selection().item().optional()
.ifPresent(System.out::println)))
.autoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS)
.build();
1.1.1. Columns
FilterTableColumnModel<String> columns = table.columnModel();
// Reorder the columns
columns.visible().set(Person.AGE, Person.NAME);
// Print hidden columns when they change
columns.hidden().addConsumer(System.out::println);
// Hide the age column
columns.visible(Person.AGE).set(false);
// Only show the age column
columns.visible().set(Person.AGE);
// Reset columns to their default location and visibility
columns.reset();
1.1.2. Search
FilterTableSearchModel search = table.search();
// Search for the value "43" in the table
search.predicate().set(value -> value.equals("43"));
RowColumn searchResult = search.results().current().get();
System.out.println(searchResult); // row: 1, column: 1
// Print the next available result
search.results().next().ifPresent(System.out::println);
2. Input Controls
2.1. Control
State somethingEnabledState = State.state(true);
CommandControl control = Control.builder()
.command(() -> System.out.println("Doing something"))
.caption("Do something")
.mnemonic('D')
.enabled(somethingEnabledState)
.build();
JButton somethingButton = new JButton(control);
Control.ActionCommand actionCommand = actionEvent -> {
if ((actionEvent.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
System.out.println("Doing something else");
}
};
CommandControl actionControl = Control.builder()
.action(actionCommand)
.caption("Do something else")
.mnemonic('S')
.build();
JButton somethingElseButton = new JButton(actionControl);
2.2. ToggleControl
State state = State.state();
ToggleControl toggleStateControl = Control.builder()
.toggle(state)
.build();
JToggleButton toggleButton = Components.toggleButton()
.toggle(toggleStateControl)
.text("Change state")
.mnemonic('C')
.build();
Value<Boolean> booleanValue = Value.nonNull(false);
ToggleControl toggleValueControl = Control.builder()
.toggle(booleanValue)
.build();
JCheckBox checkBox = Components.checkBox()
.toggle(toggleValueControl)
.text("Change value")
.mnemonic('V')
.build();
2.3. Controls
Controls controls = Controls.builder()
.control(Control.builder()
.command(this::doFirst)
.caption("First")
.mnemonic('F'))
.control(Control.builder()
.command(this::doSecond)
.caption("Second")
.mnemonic('S'))
.control(Controls.builder()
.caption("Submenu")
.control(Control.builder()
.command(this::doSubFirst)
.caption("Sub-first")
.mnemonic('b'))
.control(Control.builder()
.command(this::doSubSecond)
.caption("Sub-second")
.mnemonic('u')))
.build();
JMenu menu = Components.menu()
.controls(controls)
.build();
Control firstControl = Control.builder()
.command(this::doFirst)
.caption("First")
.mnemonic('F')
.build();
Control secondControl = Control.builder()
.command(this::doSecond)
.caption("Second")
.mnemonic('S')
.build();
Controls twoControls = Controls.builder()
.controls(firstControl, secondControl)
.build();
JPanel buttonPanel = Components.buttonPanel()
.controls(twoControls)
.build();
3. Input Components
Binding model data to UI components is accomplished by linking a Value instance to an instance of its subclass ComponentValue, which represents a value based on an input component.
//a nullable integer value, initialized to 42
Value<Integer> integerValue =
Value.nullable(42);
//create a spinner linked to the value
JSpinner spinner =
Components.integerSpinner()
.link(integerValue)
.build();
//create a NumberField component value, basically doing the same as
//the above, with an extra step to expose the underlying ComponentValue
ComponentValue<Integer, NumberField<Integer>> numberFieldValue =
Components.integerField()
//linked to the same value
.link(integerValue)
.buildValue();
//fetch the input field from the component value
NumberField<Integer> numberField = numberFieldValue.component();
3.1. Text
3.1.1. TextField
Value<String> stringValue = Value.nullable();
JTextField textField =
Components.stringField()
.link(stringValue)
.preferredWidth(120)
.transferFocusOnEnter(true)
.build();
Value<Character> characterValue = Value.nullable();
JTextField textField =
Components.characterField()
.link(characterValue)
.preferredWidth(120)
.transferFocusOnEnter(true)
.build();
3.2. Numbers
3.2.1. Integer
Value<Integer> integerValue = Value.nullable();
NumberField<Integer> integerField =
Components.integerField()
.link(integerValue)
.valueRange(0, 10_000)
.groupingUsed(false)
.build();
3.2.2. Long
Value<Long> longValue = Value.nullable();
NumberField<Long> longField =
Components.longField()
.link(longValue)
.groupingUsed(true)
.build();
3.3. Date & Time
3.3.1. LocalTime
Value<LocalTime> localTimeValue = Value.nullable();
TemporalField<LocalTime> temporalField =
Components.localTimeField()
.link(localTimeValue)
.dateTimePattern("HH:mm:ss")
.build();
3.4. Boolean
3.4.1. CheckBox
//non-nullable so use this value instead of null
boolean nullValue = false;
Value<Boolean> booleanValue =
Value.builder()
.nonNull(nullValue)
.value(true)
.build();
JCheckBox checkBox =
Components.checkBox()
.link(booleanValue)
.text("Check")
.horizontalAlignment(SwingConstants.CENTER)
.build();
3.5. Selection
3.5.1. ComboBox
Value<String> stringValue = Value.nullable();
DefaultComboBoxModel<String> comboBoxModel =
new DefaultComboBoxModel<>(new String[] {"one", "two", "three"});
JComboBox<String> comboBox =
Components.comboBox()
.model(comboBoxModel)
.link(stringValue)
.preferredWidth(160)
.build();
FilterComboBoxModel
Supplier<Collection<String>> items = () ->
List.of("One", "Two", "Three");
FilterComboBoxModel<String> model =
FilterComboBoxModel.builder()
.items(items)
.nullItem("-")
.build();
JComboBox<String> comboBox =
Components.comboBox()
.model(model)
.mouseWheelScrolling(true)
.build();
// Hides the 'Two' item.
model.items().visible().predicate()
.set(item -> !item.equals("Two"));
// Prints the selected item
model.selection().item()
.addConsumer(System.out::println);
// Refreshes the items using the supplier from above
model.items().refresh();
Completion
Completion provides a way to enable completion for combo boxes.
The available completion modes are:
Combo boxes created via Components have completion enabled by default, with MAXIMUM_MATCH being the default completion mode.
The default completion mode is controlled via the Completion.COMPLETION_MODE configuration value.
Normalization
Strings are normalized by default during completion, that is, accents are removed, i.e. á, í and ú become a, i and u. To enable accented character sensitivity, normalization can be turned off, either globally via the Completion.NORMALIZE configuration value or individually via the combo box builder.
FilterComboBoxModel<String> model =
FilterComboBoxModel.builder()
.items(List.of("Jon", "Jón", "Jónsi"))
.nullItem("-")
.build();
JComboBox<String> comboBox =
Components.comboBox()
.model(model)
// Auto completion
.completionMode(Completion.Mode.AUTOCOMPLETE)
// Accented characters not normalized
.normalize(false)
.build();
3.6. Custom
3.6.1. TextField
In the following example we link a value based on a Person class to a component value displaying text fields for a first and last name.
class Person {
final String firstName;
final String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return lastName + ", " + firstName;
}
}
class PersonPanel extends JPanel {
final JTextField firstNameField = new JTextField();
final JTextField lastNameField = new JTextField();
public PersonPanel() {
setLayout(new GridLayout(2, 2));
add(new JLabel("First name"));
add(new JLabel("Last name"));
add(firstNameField);
add(lastNameField);
}
}
class PersonPanelValue extends AbstractComponentValue<Person, PersonPanel> {
public PersonPanelValue(PersonPanel component) {
super(component);
//We must call notifyListeners() each time this value changes,
//that is, when either the first or last name changes.
component.firstNameField.getDocument()
.addDocumentListener((DocumentAdapter) e -> notifyListeners());
component.lastNameField.getDocument()
.addDocumentListener((DocumentAdapter) e -> notifyListeners());
}
@Override
protected Person getComponentValue() {
return new Person(component().firstNameField.getText(), component().lastNameField.getText());
}
@Override
protected void setComponentValue(Person value) {
component().firstNameField.setText(value == null ? null : value.firstName);
component().lastNameField.setText(value == null ? null : value.lastName);
}
}
Value<Person> personValue = Value.nullable();
PersonPanel personPanel = new PersonPanel();
Value<Person> personPanelValue = new PersonPanelValue(personPanel);
personPanelValue.link(personValue);
4. ProgressWorker
ProgressWorker is a SwingWorker extension, providing a fluent API for constructing background task workers for a variety of task types.
All handlers get called on the EventDispatchThread.
Note
|
Like SwingWorker, ProgressWorker instances can not be reused. Tasks, on the other hand, can be made stateful and reusable if required. |
4.1. Task
// A non-progress aware task, producing no result
ProgressWorker.Task task = () -> {
// Perform the task
};
ProgressWorker.builder()
.task(task)
.onException(exception ->
Dialogs.exception()
.owner(applicationFrame)
.show(exception))
.execute();
4.2. ResultTask
// A non-progress aware task, producing a result
ProgressWorker.ResultTask<String> task = () -> {
// Perform the task
return "Result";
};
ProgressWorker.builder()
.task(task)
.onResult(result ->
showMessageDialog(applicationFrame, result))
.onException(exception ->
Dialogs.exception()
.owner(applicationFrame)
.show(exception))
.execute();
4.3. ProgressTask
// A progress aware task, producing no result
ProgressWorker.ProgressTask<String> task = progressReporter -> {
// Perform the task
progressReporter.report(42);
progressReporter.publish("Message");
};
ProgressWorker.builder()
.task(task)
.onProgress(progress ->
System.out.println("Progress: " + progress))
.onPublish(message ->
showMessageDialog(applicationFrame, message))
.onException(exception ->
Dialogs.exception()
.owner(applicationFrame)
.show(exception))
.execute();
4.4. ProgressResultTask
// A reusable, cancellable task, producing a result.
// Displays a progress bar in a dialog while running.
var task = new DemoProgressResultTask();
ProgressWorker.builder()
.task(task.prepare(142))
.onStarted(task::started)
.onProgress(task::progress)
.onPublish(task::publish)
.onDone(task::done)
.onCancelled(task::cancelled)
.onException(task::failed)
.onResult(task::finished)
.execute();
static final class DemoProgressResultTask implements ProgressResultTask<Integer, String> {
private final JProgressBar progressBar = progressBar()
.indeterminate(false)
.stringPainted(true)
.string("")
.build();
// Indicates whether the task has been cancelled
private final AtomicBoolean cancelled = new AtomicBoolean();
// A Control for setting the cancelled state
private final Control cancel = Control.builder()
.command(() -> cancelled.set(true))
.caption("Cancel")
.mnemonic('C')
.build();
// A panel containing the progress bar and cancel button
private final JPanel progressPanel = borderLayoutPanel()
.center(progressBar)
.east(button()
.control(cancel))
.build();
// The dialog displaying the progress panel
private final JDialog dialog = Dialogs.builder()
.component(progressPanel)
.owner(applicationFrame)
// Trigger the cancel control with the Escape key
.keyEvent(KeyEvents.builder()
.keyCode(VK_ESCAPE)
.action(cancel))
// Prevent the dialog from closing on Escape
.disposeOnEscape(false)
.build();
private int taskSize;
@Override
public int maximum() {
return taskSize;
}
@Override
public Integer execute(ProgressReporter<String> progressReporter) throws Exception {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < taskSize; i++) {
Thread.sleep(50);
if (cancelled.get()) {
throw new CancelException();
}
result.add(i);
reportProgress(progressReporter, i);
}
return result.stream()
.mapToInt(Integer::intValue)
.sum();
}
// Makes this task reusable by resetting the internal state
private DemoProgressResultTask prepare(int taskSize) {
this.taskSize = taskSize;
progressBar.getModel().setMaximum(taskSize);
cancelled.set(false);
return this;
}
private void reportProgress(ProgressReporter<String> reporter, int progress) {
reporter.report(progress);
if (progress < taskSize * 0.5) {
reporter.publish("Going strong");
}
else if (progress > taskSize * 0.5 && progress < taskSize * 0.85) {
reporter.publish("Half way there");
}
else if (progress > taskSize * 0.85) {
reporter.publish("Almost done");
}
}
private void started() {
dialog.setVisible(true);
}
private void progress(int progress) {
progressBar.setValue(progress);
}
private void publish(List<String> strings) {
progressBar.setString(strings.get(0));
}
private void done() {
dialog.setVisible(false);
}
private void cancelled() {
showMessageDialog(applicationFrame, "Cancelled");
}
private void failed(Exception exception) {
Dialogs.exception()
.owner(applicationFrame)
.show(exception);
}
private void finished(Integer result) {
showMessageDialog(applicationFrame, "Result : " + result);
}
}