In this How To article I demonstrate implementing the ChoiceBox<T> and ComboBox<T> controls from the javafx.controls package of the JavaFX GUI framework which are commonly used to present a series of available choices for a user to select from.
The JavaFX docs for the ChoiceBox<T> class describe it as a control that is used to display a list of a few predefined choices for a user to select from. This is useful for providing both flexibility of multiple selection options as well as constraining input to a controlled set of choices. The ChoiceBox is backed by the special JavaFX collection known as an ObservableList<T> which works in concert with the JavaFX paradigm of the SelectionModel exposed via a ObjectProperty<T> value field. The ChoiceBox<T> and the ObservableList<T> that backs the control along with the ObjectProperty<T> value are Generics that must be of the same generic type definition.
In this first example I demonstrate how to construct a ChoiceBox of Strings representing common JVM based languages.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
showJvmLangsChoiceBox();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
contentVBox.getChildren().clear();
var jvmLangsChoiceBox = new ChoiceBox<String>(jvmChoices);
// bind ChoiceBox's currently selected valueProperty to the Label's textProperty
// to make it clear that you can access the selected item of a ChoiceBox
var valueLabel = new Label();
valueLabel.textProperty().bind(jvmLangsChoiceBox.valueProperty());
contentVBox.getChildren().add(
new HBox(10,
new Label("ChoiceBox<String> JVM Languages"),
jvmLangsChoiceBox,
valueLabel
)
);
}
}
The key things in this example are that an ObservableList<String> collection is created and populated with the names of popular JVM languages. The method showJvmLangsChoiceBox() constructs a ChoiceBox<String> object with the collection of JVM languages then binds the value property to a textProperty field of a Label control so it displays the currently selected JVM language String in the ChoiceBox. Those two controls are combined with an informative Label within an HBox and added to the Scene graph for display.
Often times you will want to use a Java type other than a String with a ChoiceBox. This requires providing a way to represent the object as a String for display in the control. In this next example I demonstrate using a custom class named Inventor which represents the Inventor of the previously used JVM languages. Below is the updated App.java source file that now implements the Inventor class that provides an overriden toString method which the ChoiceBox will utilize to display the contents of the Inventor class.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static ObservableList<Inventor> inventors = FXCollections.observableArrayList(
new Inventor("Java", "James", "Gosling"),
new Inventor("Kotlin", "Jet", "Brains"),
new Inventor("Groovy", "James", "Strachan"),
new Inventor("Scala", "Martin", "Odersky"),
new Inventor("Clojure", "Richard", "Hickey")
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
showInventorsChoiceBox();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBox() {
contentVBox.getChildren().clear();
var inventorsChoiceBox = new ChoiceBox<Inventor>(inventors);
// note that since the ChoiceBox is not a custom Inventor type the valueProperty
// must make a call to asString() when bound to the label
var valueLabel = new Label();
valueLabel.textProperty().bind(inventorsChoiceBox.valueProperty().asString());
contentVBox.getChildren().add(
new HBox(10,
new Label("ChoiceBox<Inventor> Inventors"),
inventorsChoiceBox,
valueLabel
)
);
}
}
class Inventor {
String language;
String firstName;
String lastName;
Inventor(String language, String firstName, String lastName) {
this.language = language;
this.firstName = firstName;
this.lastName = lastName;
}
String getLanguage() {
return language;
}
String getFirstName() {
return firstName;
}
String getLastName() {
return lastName;
}
@Override
public String toString() {
return language + ": " + firstName + " " + lastName;
}
}
The alternative way of providing a string representation of Java types within a ChoiceBox control is to use a StringConverter. A StringConverter can be used through the converter field of the ChoiceBox class. Below is an updated version which uses StringConverter's toString method to return the same String representation previously supplied from the Inventor class's toString.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static ObservableList<Inventor> inventors = FXCollections.observableArrayList(
new Inventor("Java", "James", "Gosling"),
new Inventor("Kotlin", "Jet", "Brains"),
new Inventor("Groovy", "James", "Strachan"),
new Inventor("Scala", "Martin", "Odersky"),
new Inventor("Clojure", "Richard", "Hickey")
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
// showInventorsChoiceBox();
showInventorsChoiceBoxWithStringConverter();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBoxWithStringConverter() {
contentVBox.getChildren().clear();
var inventorsChoiceBox = new ChoiceBox<Inventor>(inventors);
inventorsChoiceBox.setConverter(new StringConverter<Inventor>() {
@Override
public String toString(Inventor obj) {
if (obj != null) {
return obj.getLanguage() + ": " + obj.getFirstName() + " " + obj.getLastName();
}
return "";
}
@Override
public Inventor fromString(String string) {
return null;
}
});
var valueLabel = new Label();
valueLabel.textProperty().bind(inventorsChoiceBox.valueProperty().asString());
contentVBox.getChildren().add(
new HBox(10,
new Label("ChoiceBox<Inventor> Inventors"),
inventorsChoiceBox,
valueLabel
)
);
}
}
class Inventor {
// omitting for brevity
}
There is significant overlap of functionality between ChoiceBox and ComboBox<T> controls with the ComboBox providing a bit more functionality and flexibility. The ComboBox is often used when you expect to have larger collections to choose from and the ComboBox can be configured to be editable affording the flexibility to enter in your own alternative choice. Below I provide an implementation to display the JVM language choices as a ComboBox.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
// showInventorsChoiceBox();
// showInventorsChoiceBoxWithStringConverter();
showJvmLangsComboBox();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBoxWithStringConverter() {
// omitting for brevity
}
static void showJvmLangsComboBox() {
contentVBox.getChildren().clear();
var jvmLangsComboBox = new ComboBox<String>(jvmChoices);
var valueLabel = new Label();
valueLabel.textProperty().bind(jvmLangsComboBox.valueProperty());
contentVBox.getChildren().add(
new HBox(10,
new Label("ComboBox<String> JVM Languages"),
jvmLangsComboBox,
valueLabel
)
);
}
}
Again, since the ComboBox and ChoiceBox are so similar the code to implement the Inventory backed ObservableList in a ComboBox is almost indecipherable from what was used in the ChoiceBox<Inventory> example with the one obvious difference being that a ComboBox is actually used.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static ObservableList<Inventor> inventors = FXCollections.observableArrayList(
new Inventor("Java", "James", "Gosling"),
new Inventor("Kotlin", "Jet", "Brains"),
new Inventor("Groovy", "James", "Strachan"),
new Inventor("Scala", "Martin", "Odersky"),
new Inventor("Clojure", "Richard", "Hickey")
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
// showInventorsChoiceBox();
// showInventorsChoiceBoxWithStringConverter();
// showJvmLangsComboBox();
showInventorsComboBoxWithStringConverter();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBoxWithStringConverter() {
// omitting for brevity
}
static void showJvmLangsComboBox() {
// omitting for brevity
}
static void showInventorsComboBoxWithStringConverter() {
contentVBox.getChildren().clear();
var inventorsComboBox = new ComboBox<Inventor>(inventors);
inventorsComboBox.setConverter(new StringConverter<Inventor>() {
@Override
public String toString(Inventor obj) {
if (obj != null) {
return obj.getLanguage() + ": " + obj.getFirstName() + " " + obj.getLastName();
}
return "";
}
@Override
public Inventor fromString(String string) {
return null;
}
});
var valueLabel = new Label();
valueLabel.textProperty().bind(inventorsComboBox.valueProperty().asString());
contentVBox.getChildren().add(
new HBox(10,
new Label("ComboBox<Inventor> Inventors"),
inventorsComboBox,
valueLabel
)
);
}
}
class Inventor {
String language;
String firstName;
String lastName;
Inventor(String language, String firstName, String lastName) {
this.language = language;
this.firstName = firstName;
this.lastName = lastName;
}
String getLanguage() {
return language;
}
String getFirstName() {
return firstName;
}
String getLastName() {
return lastName;
}
@Override
public String toString() {
return language + ": " + firstName + " " + lastName;
}
}
As mentioned previously the ComboBox control provides the ability to present the user with an editable TextField input control for a user to enter an alternative value not present in the list of choices. This is accomplished by setting the editable field to true via ComboBox's setEditable(boolean) method and implementing a StringConverter which is needed to set the SelectionModel's valueProperty to the Object reprenting the text typed into the TextField as shown below.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
// showInventorsChoiceBox();
// showInventorsChoiceBoxWithStringConverter();
// showJvmLangsComboBox();
// showInventorsComboBoxWithStringConverter();
showEditableComboBox();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity }
static void showInventorsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBoxWithStringConverter() {
// omitting for brevity }
static void showJvmLangsComboBox() {
// omitting for brevity
}
static void showInventorsComboBoxWithStringConverter() {
// omitting for brevity
}
static void showEditableComboBox() {
contentVBox.getChildren().clear();
var jvmLangsEditableComboBox = new ComboBox<String>(jvmChoices);
// set to editable
jvmLangsEditableComboBox.setEditable(true);
// provide StringConverter
jvmLangsEditableComboBox.setConverter(new StringConverter<String>() {
@Override
public String toString(String obj) {
if (obj != null) {
return obj;
}
return "";
}
@Override
public String fromString(String string) {
return string;
}
});
var valueLabel = new Label();
valueLabel.textProperty().bind(jvmLangsEditableComboBox.valueProperty().asString());
contentVBox.getChildren().add(
new HBox(10,
new Label("ComboBox<String> JVM Languages"),
jvmLangsEditableComboBox,
valueLabel
)
);
}
}
A common additional use case I've had while using the editable ComboBox is the need to update the ObservableList that backs the ComboBox to include the item the user has typed in. So in my example of JVM Languages when the user enters a language that is not included in the list of choices, such as Jython, I want to add the item to the list when the user presses the enter key. To do this I utilize the fromString method of my StringConverter implementation to add the object to the ObservableList<Inventor> collection backing the ComboBox.
package com.thecodinginterface.dropdowns;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
static ObservableList<String> jvmChoices = FXCollections.observableArrayList(
"Java",
"Kotlin",
"Groovy",
"Scala",
"Clojure"
);
static VBox contentVBox = new VBox();
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
var root = new BorderPane();
// showJvmLangsChoiceBox();
// showInventorsChoiceBox();
// showInventorsChoiceBoxWithStringConverter();
// showJvmLangsComboBox();
// showInventorsComboBoxWithStringConverter();
// showEditableComboBox();
showEditableComboBoxWithAutoAdd();
root.setCenter(contentVBox);
primaryStage.setScene(new Scene(root, 400, 100));
primaryStage.show();
}
static void showJvmLangsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBox() {
// omitting for brevity
}
static void showInventorsChoiceBoxWithStringConverter() {
// omitting for brevity
}
static void showJvmLangsComboBox() {
// omitting for brevity
}
static void showInventorsComboBoxWithStringConverter() {
// omitting for brevity
}
static void showEditableComboBox() {
// omitting for brevity
}
static void showEditableComboBoxWithAutoAdd() {
contentVBox.getChildren().clear();
var jvmLangsEditableComboBox = new ComboBox<String>(jvmChoices);
// set to editable
jvmLangsEditableComboBox.setEditable(true);
// provide StringConverter
jvmLangsEditableComboBox.setConverter(new StringConverter<String>() {
@Override
public String toString(String obj) {
if (obj != null) {
return obj;
}
return "";
}
@Override
public String fromString(String string) {
if (!jvmChoices.contains(string)) {
jvmChoices.add(string);
}
return string;
}
});
var valueLabel = new Label();
valueLabel.textProperty().bind(jvmLangsEditableComboBox.valueProperty().asString());
contentVBox.getChildren().add(
new HBox(10,
new Label("ComboBox<String> JVM Languages"),
jvmLangsEditableComboBox,
valueLabel
)
);
}
}
thecodinginterface.com earns commision from sales of linked products such as the books above. This enables providing continued free tutorials and content so, thank you for supporting the authors of these resources as well as thecodinginterface.com
In this How To article I have demonstrated the ChoiceBox and ComboBox controls from the JavaFX UI framework. These controls are useful when you want to display a list of choices for a user to select from but, can also be extended to provide additional functionality of adding new items to the list of choices.