Search code examples
javajavafxcomboboxfxml

JavaFx Combobox value acting wierd


I have been learning javafx and I was trying combobox but cant seem to get output out of combobox properly. When I try to use the value from combobox as String it gives me ClassCastException: java.lang.Integer cannot be cast to java.lang.String, and when I try to use the value as an int or an Integer(tried both) it gives me the opposite ClassCastException: java.lang.String cannot be cast to java.lang.Integer.

I have tried getting the value using

comboBox.getSelectionModel().getSelectedItem();

and also with

comboBox.getValue();

I have tried converting the values explicitly with valueOf, parseInt, and toString. Using getClass also gives a ClassCastException: java.lang.String cannot be cast to java.lang.Integer..

This is the combobox I have been using:

<ComboBox fx:id="comboBox"  editable="true" promptText="Enter Period in Days"  >
    <items>
        <FXCollections fx:factory="observableArrayList">
            <String fx:id="week" fx:value="7" />
            <String fx:id="fortnite" fx:value="14" />
            <String fx:id="month" fx:value="30" />
            <String fx:id="monthx3" fx:value="90" />
            <String fx:id="year_2" fx:value="180" />
            <String fx:id="year" fx:value="365"/>
        </FXCollections>
    </items>
</ComboBox>

How do I get a value out of this combobox? What am I doing wrong?


Solution

  • If you're using some type different to String and want to keep the ComboBox editable, you need to assign a StringConverter to the ComboBox.converter property that is able to convert the String to the item type of the ComboBox. Otherwise you'll get the ClassCastException when the ComboBox tries to parse the input of the combobox TextField.

    Note: Adding an fx:id attribute to a element in the fxml does not result in a combination of the fx:id and the object created for the element to be used. Instead all it does is allow you to inject the instance to a field in the controller or reference the instance later in the fxml.

    Since it seems like you want to keep 2 pieces of information (String and int) neither String nor Integer may work for you. You could create a custom type:

    public class NamedDuration {
        private final int days;
        private final String name;
    
        public NamedDuration(@NamedArg("days") int days, @NamedArg("name") String name) {
            this.days = days;
            this.name = name;
        }
    
        public int getDays() {
            return days;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public String toString() {
            return name;
        }
    
    }
    
    <ComboBox fx:id="comboBox" editable="true" onAction="#comboChange" promptText="Enter Period in Days">
        <items>
            <FXCollections fx:factory="observableArrayList">
                <NamedDuration name="week" days="7"/>
                <NamedDuration name="fortnite" days="14"/>
                <NamedDuration name="month" days="30"/>
                <NamedDuration name="monthx3" days="90"/>
                <NamedDuration name="year_2" days="180"/>
                <NamedDuration name="year" days="365"/>
            </FXCollections>
        </items>
    </ComboBox>
    

    Controller class

    public class FXML2Controller {
    
        @FXML
        private ComboBox<NamedDuration> comboBox;
    
        @FXML
        private void comboChange() {
            NamedDuration duration = comboBox.getValue();
            if (duration != null) {
                System.out.format("%d days = %s\n", duration.getDays(), duration.getName());
            }
        }
    
        @FXML
        private void initialize() {
            // set converter to convert between String and NamedDuration
            comboBox.setConverter(new StringConverter<NamedDuration>() {
    
                @Override
                public String toString(NamedDuration object) {
                    return object == null ? "" : object.getName();
                }
    
                @Override
                public NamedDuration fromString(String string) {
                    if (string == null || string.isEmpty()) {
                        return null;
                    }
    
                    // try matching names
                    for (NamedDuration nd : comboBox.getItems()) {
                        if (nd.getName().equalsIgnoreCase(string)) {
                            return nd;
                        }
                    }
    
                    // try matching number
                    int days;
                    try {
                        days = Integer.parseInt(string);
                    } catch (NumberFormatException ex) {
                        return null;
                    }
                    for (NamedDuration nd : comboBox.getItems()) {
                        if (days == nd.getDays()) {
                            return nd;
                        }
                    }
    
                    return null;
                }
    
            });
        }
    
    }