EasyBind leverages lambdas to reduce boilerplate when creating custom bindings, provides a type-safe alternative to Bindings.select*
methods and adds provides enhanced bindings support for Optional
.
See below for how to install EasyBind in your project.
This is a maintained fork of the EasyBind library by Tomas Mikula, which sadly is dormant at the moment.
The simplest way is to use the EasyBind.wrap*
methods to create wrappers around standard JavaFX observable values or lists.
The wrapper then gives you access to all the features of EasyBind.
For example,
ObservableStringValue str = ...;
Binding<Integer> length = EasyBind.wrap(str)
.map(String::length);
creates a Binding
that holds the length of str
.
Similarly,
ObservableList<String> list = ...;
Binding<Boolean> allMatch = EasyBind.wrap(list)
.allMatch(String:isEmpty)
yields a Binding
that reflects whether all items in the list are empty strings.
In addition to the wrap*
methods, EasyBind also provides direct shortcuts for common functionality.
For example, the above binding could be more shortly written as EasyBind.map(String::length)
.
Creates a binding whose value is a mapping of some observable value.
ObservableStringValue str = ...;
Binding<Integer> length = EasyBind.map(str, String::length);
Creates a binding whose value is a combination of two or more observable values.
ObservableStringValue str = ...;
ObservableValue<Integer> start = ...;
ObservableValue<Integer> end = ...;
Binding<String> substring = EasyBind.combine(str, start, end, String::substring);
Type-safe alternative to Bindings.select*
methods.
Binding<Boolean> showing = EasyBind.select(control.sceneProperty())
.select(scene -> scene.windowProperty())
.selectObject(window -> window.showingProperty());
The resulting binding is updated whenever one of the properties in the selection graph changes.
Returns a mapped view of an ObservableList
.
ObservableList<String> items = ...;
ObservableList<Integer> lengths = EasyBind.map(items, String::getLength);
In the above example, lengths
is updated as elements are added and removed from items
.
By design, the elements of the new observable are calculated on the fly whenever they are needed (e.g. if get
is called).
Thus, this is prefect for light-weight operations.
If the conversion is a cost-intensive operation or if the elements of the list are often accessed, then using mapBacked
is a better option.
Here the elements of the list are converted once and then stored in memory.
Using reduce
you can aggregate an observable list of items into a single observable value.
ObservableList<String> items = ...;
ObservableValue<Boolean> totalLength = EasyBind.reduce(items, stream -> stream.mapToInt(String::length).sum());
More advanced, the combine
method turns an observable list of observable values into a single observable value. The resulting observable value is updated whenever elements are added or removed to or from the list, as well as when element values change.
Property<Integer> a = new SimpleObjectProperty<>(5);
Property<Integer> b = new SimpleObjectProperty<>(10);
ObservableList<Property<Integer>> list = FXCollections.observableArrayList();
Binding<Integer> sum = EasyBind.combine(
list,
stream -> stream.reduce((a, b) -> a + b).orElse(0));
assert sum.getValue() == 0;
// sum responds to element additions
list.add(a);
list.add(b);
assert sum.getValue() == 15;
// sum responds to element value changes
a.setValue(20);
assert sum.getValue() == 30;
// sum responds to element removals
list.remove(a);
assert sum.getValue() == 10;
You don't usually have an observable list of observable values, but you often have an observable list of something that contains an observable value. In that case, use the above map
methods to get an observable list of observable values, as in the example below.
Example: Disable "Save All" button on no unsaved changes
Assume a tab pane that contains a text editor in every tab. The set of open tabs (i.e. open files) is changing. Let's further assume we use a custom `Tab` subclass `EditorTab` that has a boolean `savedProperty()` indicating whether changes in its editor have been saved.Task: Keep the "Save All" button disabled when there are no unsaved changes in any of the editors.
ObservableList<ObservableValue<Boolean>> individualTabsSaved =
EasyBind.map(tabPane.getTabs(), tab -> ((EditorTab) tab).savedProperty());
ObservableValue<Boolean> allTabsSaved = EasyBind.combine(
individualTabsSaved,
stream -> stream.allMatch(saved -> saved));
Button saveAllButton = new Button(...);
saveAllButton.disableProperty().bind(allTabsSaved);
The concat
method combines two or more observable lists into one big list containing all items.
ObservableList<String> listA = ...;
ObservableList<String> listB = ...;
ObservableList<String> combinedList = EasyBind.concat(listA, listB);
Similarly, an observable list of observable lists can be combined into one big list containing all items of all lists as follows:
ObservableList<ObservableList<String>> listOfLists = ...;
ObservableList<String> allItems = EasyBind.flatten(listOfLists);
Often one wants to execute some code for each value of an ObservableValue
, that is for the current value and each new value. This typically results in code like this:
this.doSomething(observable.getValue());
observable.addListener((obs, oldValue, newValue) -> this.doSomething(newValue));
This can be expressed more concisely using the subscribe
helper method:
EasyBind.subscribe(observable, this::doSomething);
In case doSomething
should not be invoked immediately, EasyBind.listen(observable, this::doSomething)
should be used instead.
Using when
you can create bindings that should only be realized if a given observable boolean is true.
The method includeWhen
includes an element in a collection based on a boolean condition.
Say that you want to draw a line and highlight it when its hovered over. To achieve this, let's add .highlight
CSS class to the line node when it is hovered over and remove it when it is not:
EasyBind.includeWhen(edge.getStyleClass(), "highlight", line.hoverProperty());
One often faces the situation that observables take null
values.
The wrapNullable
provides a wrapper around the observable that provides convenient helper methods similar to the Optional
class.
interface ObservableOptionalValue<T> {
BooleanBinding isPresent();
BooleanBinding isEmpty();
Subscription listenToValues(SimpleChangeListener<? super T> listener);
Subscription subscribeToValues(Consumer<? super T> subscriber);
EasyBinding<T> orElseOpt(T other);
OptionalBinding<T> orElseOpt(ObservableValue<T> other);
OptionalBinding<T> filter(Predicate<? super T> predicate);
OptionalBinding<U> mapOpt(Function<? super T, ? extends U> mapper);
OptionalBinding<U> flatMapOpt(Function<T, Optional<U>> mapper);
PropertyBinding<U> selectProperty(Function<? super T, O> mapper);
...
}
Example:
BooleanBinding currentTabHasContent = EasyBind.wrapNullable(tabPane.getSelectionModel().selectedItemProperty())
.map(Tab::contentProperty)
.isPresent();
The EasyBind.valueAt(list, index)
and EasyBind.valueAt(map, key)
methods return a binding containing the item at the given position in the list or map.
The returned binding will be empty if the index/key points behind the list (or at a null
item).
Current stable release is 2.2.0
.
It contains many new features, but also breaks backwards compatibility to the 1.x
versions as many methods have been renamed; see the Changelog for details.
In case you are upgrading from the EasyBind
library developed by by Tomas Mikula, then the easiest option is to use version 1.2.2
which includes a few improvements and bug fixes while being compatible with older versions.
Group ID | Artifact ID | Version |
---|---|---|
com.tobiasdiez | easybind | 2.2.0 |
dependencies {
compile group: 'com.tobiasdiez', name: 'easybind', version: '2.2.0'
}
libraryDependencies += "com.tobiasdiez" % "easybind" % "2.2.0"
Download the JAR file and place it on your classpath.
Snapshot releases are deployed to Sonatype snapshot repository.
Group ID | Artifact ID | Version |
---|---|---|
com.tobiasdiez | easybind | 2.2.1-SNAPSHOT |
repositories {
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
dependencies {
compile group: 'com.tobiasdiez', name: 'easybind', version: '2.2.1-SNAPSHOT'
}
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
libraryDependencies += "com.tobiasdiez" % "easybind" % "2.2.1-SNAPSHOT"
Download the latest JAR file and place it on your classpath.