My interface has two input fields: A combobox to select a Country
and a checkbox.
public class Country {
private String name;
public String getName() {
return name;
}
}
I just want to enable the checkbox, if a specific value is selected in the combobox (e.g. Germany
).
BooleanBinding noCountryBinding = Binding.isNull(cmbCountry.valueProperty());
BooleanBinding isGermanyBinding = Binding.equal(cmbCountry.getSelectionModel().selectedProperty().get().getName(), "Germany"); // <- This does not work, what can I do instead?
cbxFreeShipping.disableProperty().bind(Bindings.or(noCountryBinding, Bindings.not(isGermanyBinding));
The first binding on its own works fine, but I cannot figure out how to make the second binding rely on a String property of the combobox items. I tried a different approach by implementing a listener on the combobox, but of course it only triggers when the selected item changes.
My interface has two input fields: A combobox to select a Country
and a checkbox.
public class Country {
private String name;
public String getName() {
return name;
}
}
I just want to enable the checkbox, if a specific value is selected in the combobox (e.g. Germany
).
BooleanBinding noCountryBinding = Binding.isNull(cmbCountry.valueProperty());
BooleanBinding isGermanyBinding = Binding.equal(cmbCountry.getSelectionModel().selectedProperty().get().getName(), "Germany"); // <- This does not work, what can I do instead?
cbxFreeShipping.disableProperty().bind(Bindings.or(noCountryBinding, Bindings.not(isGermanyBinding));
The first binding on its own works fine, but I cannot figure out how to make the second binding rely on a String property of the combobox items. I tried a different approach by implementing a listener on the combobox, but of course it only triggers when the selected item changes.
Share Improve this question edited Nov 18, 2024 at 16:18 user1438038 asked Nov 18, 2024 at 15:54 user1438038user1438038 6,0997 gold badges62 silver badges103 bronze badges3 Answers
Reset to default 4In JavaFX 19 and later, you can use ObservableValue.map(...)
:
var selectedCountryName = cmbCountry.getSelectionModel().selectedItemProperty().map(Country::getName);
// or
// var selectedCountryName = cmbCountry.valueProperty().map(Country::getName);
var germanyNotSelected = selectedCountryName.map(country -> ! "Germany".equals(country)).orElse(true);
cbxFreeShipping.disableProperty().bind(germanyNotSelected);
The selectedCountryName
will contain null
if nothing is selected (or if the selected Country
instance returns null
from getName()
).
selectedCountryName.map(country -> ! "Germany".equals(country))
will be null
if selectedCountryName
is null, and otherwise contain the result of !"Germany".equals(country)
. The orElse(true)
call returns a value containing true
if selectedCountryName
contains null
.
Here is a complete example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HelloApplication extends Application {
static class Country {
private final String name;
Country(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
@Override
public void start(Stage stage) {
ComboBox<Country> cmbCountry = new ComboBox<>();
cmbCountry.getItems().addAll(
new Country("France"),
new Country("Germany"),
new Country("Spain")
);
cmbCountry.setCellFactory( _ -> createCountryCell());
cmbCountry.setButtonCell(createCountryCell());
CheckBox cbxFreeShipping = new CheckBox("Free Shipping");
var selectedCountryName = cmbCountry.getSelectionModel().selectedItemProperty().map(Country::getName);
var germanyNotSelected = selectedCountryName.map(country -> ! "Germany".equals(country)).orElse(true);
cbxFreeShipping.disableProperty().bind(germanyNotSelected);
VBox root = new VBox(5, cmbCountry, cbxFreeShipping);
Scene scene = new Scene(root, 400, 400);
stage.setScene(scene);
stage.show();
}
private ListCell<Country> createCountryCell() {
return new ListCell<>() {
@Override
protected void updateItem(Country item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getName());
}
}
};
}
public static void main(String[] args) {
launch();
}
}
You're best off using Bindings.createBooleanBinding()
using just the ComboBox.valueProperty()
. Then you can just write a Supplier
that evaluates the current value of ComboBox.valueProperty()
as a simple, nullable String
.
This is Kotlin, but the concepts are identical:
class ComboBoxExample0 : Application() {
private val countries = FXCollections.observableArrayList(
Country("Germany"),
Country("France"), Country("Denmark")
)
override fun start(stage: Stage) {
val scene = Scene(createContent(), 280.0, 300.0)
stage.scene = scene
stage.show()
}
private fun createContent(): Region = VBox(20.0).apply {
val comboBox = ComboBox<Country>().apply {
items = countries
}
children +=
CheckBox("This is a CheckBox").apply {
disableProperty().bind(
Bindings.createBooleanBinding(
{ (comboBox.value == null) || (comboBox.value.name == "Germany") },
comboBox.valueProperty()
)
)
}
children += comboBox
padding = Insets(40.0)
}
}
data class Country(val name: String)
fun main() = Application.launch(ComboBoxExample0::class.java)
The key point here being that in the Bindings.createBinding()
call, the dependency is on comboBox.valueProperty()
meaning that the Binding
will be invalidated whenever comboBox.valueProperty()
changes. Then the code in the Supplier
will just look at comboBox.value
, which is equivalent to comboBox.valueProperty().getValue()
, which is just a (nullable) String.
You can do it with the Fluent API, but then you'll need to use ObservableValue.map()
to grab Country.name
as an ObservableValue
. But then you'll need to cast it to ObservableStringValue
in order to use the Fluent API, as the mapping will return an ObservableValue<String>
. It's not difficult, but the Bindings.createBooleanBinding()
approach is cleaner.
For the sake of completeness, I would like to add both solutions that have been submitted so far and that I have tested. First off, both approaches work! Since @DaveB posted Kotlin code, here is a Java equivalent:
BooleanBinding noFreeShipping = Bindings.createBooleanBinding(() -> {
var noCountry = (null == cmbCountry.getValue());
var isGermany = (null != cmbCountry.getValue() && "Germany".equals(cmbCountry.getValue().getName()));
return (noCountry || !isGermany);
}, cmbCountry.valueProperty());
cbxFreeShipping.disableProperty().bind(noFreeShipping);
In my opinion, this implementation is easier to read and understand.
The solution submitted by @James_D works, too, and it is slightly shorter:
var selectedCountryName = cmbCountry.valueProperty().map(Country::getName);
var isNotGermany = selectedCountryName.map(country -> ! "Germany".equals(country)).orElse(true);
cbxFreeShipping.disableProperty().bind(isNotGermany);
However, it feels a little harder to read for me, due to the nature of map()
. Furthermore, the case where no country is selected, is not so explicit. Not sure which solution is better from a technical point of view, but I think that I'll stick with the first implementation. - Nonetheless, I appreciate both answers!