user13998337
user13998337

Reputation:

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 :)

Upvotes: 1

Views: 594

Answers (1)

jewelsea
jewelsea

Reputation: 159291

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.

Upvotes: 7

Related Questions