1

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

symondyri
  • 23
  • 3
  • You could ```@Override``` the ```toString()``` method in your animal class. – Volt4 Jul 22 '22 at 19:09
  • 3
    @Volt4 overriding toString for application reasons is utterly __wrong__ either use a custom cell or a StringConverter – kleopatra Jul 22 '22 at 23:19

1 Answers1

8

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.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • a StringConverter would do – kleopatra Jul 22 '22 at 23:20
  • 1
    Yep, added that as an option to the answer. – jewelsea Jul 22 '22 at 23:43
  • thanks for the StringConverter :) Though: for ComboBox (and other controls having a converter) using it should be the first option if customizing the text representation is the only requirement. If there's api to reach a goal prefer using it over re-inventing the wheel ;) – kleopatra Jul 23 '22 at 09:20
  • Listing toString as one of several options is at least unfortunate: the lazy reader might stop at that bullet (cool-thats-something-i-know-will-do) without going to the end of the question (which explains the why not). You might consider removing the toString from the list and move it completely to the end of the question (along with the you-might-be-tempted paragraph). – kleopatra Jul 23 '22 at 09:21
  • hmm .. just re-read combo api doc: it also suggests a custom cellFactory as _the_ means to configure the content, mentioning stringConverter only in the context of editing. While I think the doc is .. suboptimal, you are in good company doing the same ;) – kleopatra Jul 23 '22 at 09:51
  • Thanks for your respronses. The StringConverter Implementation worked perfectly :) – symondyri Jul 23 '22 at 16:13
  • 1
    "You might consider removing the toString from the list and move it completely to the end of the question (along with the you-might-be-tempted paragraph)" -> question text reorganized as suggested. – jewelsea Jul 25 '22 at 18:34
  • thanks for the edit - I think now the answer is perfect, we should add it to the FAQ :) – kleopatra Jul 25 '22 at 22:42