28

I have a combobox which shows list of User objects. I have coded a custom cell factory for the combobox:

@FXML ComboBox<User> cmbUserIds;
cmbUserIds.setCellFactory(new Callback<ListView<User>,ListCell<User>>(){
                @Override
                public ListCell<User> call(ListView<User> l){
                    return new ListCell<User>(){
                        @Override
                        protected void updateItem(Useritem, boolean empty) {
                            super.updateItem(item, empty);
                            if (item == null || empty) {
                                setGraphic(null);
                            } else {
                                setText(item.getId()+"    "+item.getName());
                            }
                        }
                    } ;
                }
            });

ListView is showing a string(id+name), but when I select an item from listview, Combobox is showing toString() method return value i.e address of object. I can't override toString() method, because the User domain object should be same as the one at server. How to display id in combobox? Please suggest

EDIT1

I tried below code. Now combo box shows id when I select a value from the listview.

cmbUserIds.setConverter(new StringConverter<User>() {
              @Override
              public String toString(User user) {
                if (user== null){
                  return null;
                } else {
                  return user.getId();
                }
              }

            @Override
            public User fromString(String id) {
                return null;
            }
        });

The selected value in combo box is cleared when control focus is lost. How to fix this?

EDIT2:

@FXML AnchorPane root;
@FXML ComboBox<UserDTO> cmbUsers;
List<UserDTO> users;
public class GateInController implements Initializable {
@Override   
public void initialize(URL location, ResourceBundle resources) {
        users = UserService.getListOfUsers();
        cmbUsers.setItems(FXCollections.observableList(users));
        cmbUsers.getSelectionModel().selectFirst();
        // list of values showed in combo box drop down
        cmbUsers.setCellFactory(new Callback<ListView<UserDTO>,ListCell<UserDTO>>(){
            @Override
            public ListCell<UserDTO> call(ListView<UserDTO> l){
                return new ListCell<UserDTO>(){
                    @Override
                    protected void updateItem(UserDTO item, boolean empty) {
                        super.updateItem(item, empty);
                        if (item == null || empty) {
                            setGraphic(null);
                        } else {
                            setText(item.getUserId()+"    "+item.getUserNm());
                        }
                    }
                } ;
            }
        });
        //selected value showed in combo box
        cmbUsers.setConverter(new StringConverter<UserDTO>() {
              @Override
              public String toString(UserDTO user) {
                if (user == null){
                  return null;
                } else {
                  return user.getUserId();
                }
              }

            @Override
            public UserDTO fromString(String userId) {
                return null;
            }
        });
    }
}
developer
  • 463
  • 2
  • 9
  • 19

2 Answers2

34

Just create and set a CallBack like follows:

@FXML ComboBox<User> cmbUserIds;

Callback<ListView<User>, ListCell<User>> cellFactory = new Callback<ListView<User>, ListCell<User>>() {

    @Override
    public ListCell<User> call(ListView<User> l) {
        return new ListCell<User>() {

            @Override
            protected void updateItem(User item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty) {
                    setGraphic(null);
                } else {
                    setText(item.getId() + "    " + item.getName());
                }
            }
        } ;
    }
}

// Just set the button cell here:
cmbUserIds.setButtonCell(cellFactory.call(null));
cmbUserIds.setCellFactory(cellFactory);
PHPirate
  • 7,023
  • 7
  • 48
  • 84
Chris River
  • 404
  • 4
  • 4
  • 2
    how does the call to setButtonCell() work? it looks like it is calling the callFactory right away with null and then passing the return to setButtonCell(), but that doesn't make sense for how it works – simpleuser Aug 03 '17 at 18:09
  • 2
    Very useful answer. Could you explain what's happening behind the scenes? – Saminda Peramuna Feb 12 '18 at 08:26
  • @simpleuser If you look at the above code, the return value of call is a ListCell. So what you do here is setting your custom ListCell (which overrides the updateItem method) as button cell. – leyren May 22 '19 at 09:36
11

You need to provide a functional fromString() Method within the Converter!

I had the same problem as you have and as I implemented the fromString() with working code, the ComboBox behaves as expected.

This class provides a few of my objects, for dev-test purposes:

public class DevCatProvider {

    public static final CategoryObject c1;
    public static final CategoryObject c2;
    public static final CategoryObject c3;

    static {
        // Init objects
    }

    public static CategoryObject getCatForName(final String name) {
        switch (name) {
            case "Kategorie 1":
                return c1;

            case "Cat 2":
                return c2;

            case "Steuer":
                return c3;

            default:
                return c1;
        }
    }
}

The converter object:

public class CategoryChooserConverter<T> extends StringConverter<CategoryObject> {

    @Override
    public CategoryObject fromString(final String catName) {
        //This is the important code!
        return Dev_CatProvider.getCatForName(catName);
    }

    @Override
    public String toString(final CategoryObject categoryObject) {
        if (categoryObject == null) {
            return null;
        }
        return categoryObject.getName();
    }
}
Neuron
  • 5,141
  • 5
  • 38
  • 59
Korashen
  • 2,144
  • 2
  • 18
  • 28
  • The converter property Javadoc doesn't seem to be appropriate. I've just opened an issue: https://javafx-jira.kenai.com/browse/RT-40799 – Paolo Fulgoni May 18 '15 at 10:11
  • I hate that they've done it like this, having to f with string-comparisons. Why didn't they just add a `comboBox.setText(User::getName)` like on other stuff? – Impulse The Fox Jan 15 '18 at 08:38
  • This worked perfectly well. Just remember to call `comboBox.setConverter(new CategoryChooserConverter())` – Zorobay Dec 10 '21 at 12:15