Search code examples
javajavafxcombobox

JavaFX Combobox show attribute of element


I am currently working on a game with Java and JavaFX. I am using a JavaFX ComboBox.

The following example should explain my problem.

Let's say I have a class "Animal" with the attributes "name", "age" and "color".

First file:

public class Animal {
    private String name;
    private int age;
    private String color;

    public Animal(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
}

Now I want to create a ComboBox with each animal I create.

Second file:

ComboBox<Animal> comboBoxAnimal = new ComboBox();
ObservableList<Animal> comboBoxItems = FXCollections.observableArrayList();

Animal dog = new Animal("Liam", 2, "Brown");
Animal cat = new Animal("Emily", 5, "Gray");
Animal bird = new Animal("Kian", 3, "Green");

comboBoxItems.addAll(dog, cat, bird);

comboBoxAnimal.setItems(comboBoxItems);

Currently I get only "Animal@xxxxxxxx" which is understandable because I have a ComboBox of Animals but want only the names (Strings) to be presented.

Just simply creating a ComboBox<String> won't solve the problem as I need a Combobox<Animal>.

How can I get a Combobox<Animal> but as elements show only the names of each Animal?

Thanks for your feedback :)


Solution

  • Two options:

    1. Use a cell factory.
    2. Use a string converter.

    The cell factory and string converter examples used in this answer produce the identical output:

    screenshot

    Cell Factory Implementation

    Use a cell factory, like in this answer:

    The linked answer is for a ListView, but the ComboBox is similar, as is a TableView or other virtualized controls that rely on cell factories for display.

    To configure cells for the ComboBox drop-down list and button, make calls to both setCellFactory and setButtonCell.

    This is the most flexible solution and allows for customization beyond just strings of text. Graphic nodes can be created to completely customize the visual representation of each combo box cell.

    Example Code

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.ListCell;
    import javafx.stage.Stage;
    
    public class AnimalComboApp extends Application {
    
        public record Animal(String name, int age, String color) {}
    
        public static class AnimalCell extends ListCell<Animal> {
            @Override
            public void updateItem(Animal animal, boolean empty) {
                super.updateItem(animal, empty);
    
                if (animal == null || empty) {
                    setText(null);
                } else {
                    setText(animal.name());
                }
            }
        }
    
        @Override
        public void start(Stage stage) throws Exception {
            ComboBox<Animal> comboBox = new ComboBox<>();
            comboBox.getItems().setAll(
                    new Animal("Liam", 2, "Brown"),
                    new Animal("Emily", 5, "Gray"),
                    new Animal("Kian", 3, "Green")
            );
    
            comboBox.setCellFactory(listView -> new AnimalCell());
            comboBox.setButtonCell(new AnimalCell());
            comboBox.getSelectionModel().select(0);
    
            stage.setScene(new Scene(comboBox));
            stage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    StringConverter Implementation

    An alternative to a cell factory definition is to provide the ComboBox with a StringConverter which can convert to and from a String and an object.

    The StringConverter requires fromString and toString to be implemented, but the fromString implementation can just return null unless you also want the user to be able to perform text edits to edit the combo box value.

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.ComboBox;
    import javafx.stage.Stage;
    import javafx.util.StringConverter;
    
    public class AnimalComboApp extends Application {
    
        public record Animal(String name, int age, String color) {}
    
        @Override
        public void start(Stage stage) throws Exception {
            ComboBox<Animal> comboBox = new ComboBox<>();
            comboBox.getItems().setAll(
                    new Animal("Liam", 2, "Brown"),
                    new Animal("Emily", 5, "Gray"),
                    new Animal("Kian", 3, "Green")
            );
    
            comboBox.setConverter(new StringConverter<>() {
                @Override
                public String toString(Animal animal) {
                    return animal.name();
                }
    
                @Override
                public Animal fromString(String string) {
                    return null;
                }
            });
    
            comboBox.getSelectionModel().select(0);
    
            Scene scene = new Scene(comboBox);
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Options NOT to use

    toString() implementation

    Do not override toString() to customize the cells, that is an anti-pattern.

    Use toString() for other purposes such as listing and debugging all the elements of the Animal class, and instead, use the appropriate methods, such as cell factories or string converters, for customizing the UI view of the Animal.

    Placing nodes in the combo box list

    You might also be tempted to try to put nodes directly in the ComboBox list (for example create Labels with the name of an object in each Label and then create a ComboBox<Label>).

    Don't do this, as advised by the ComboBox API documentation section: "A warning about inserting Nodes into the ComboBox items list", it will create bugs in your application.