J.Ober
J.Ober

Reputation: 199

JavaFX Display Enum with String field in Combobox as String (in TableView)

My goal is to display every field of an instance of a Class in a tableView. The Class has a field of type enum which has a field of type String. The enum shall be displayed in a ComboBox as it's String field name. Of course it also has to be editable.

Now what isn't working: The String field of enum class is only displayed if the ComboBox is clicked on, otherwise it is the name of the enum constant. Also, if another enum in the combobox is selected, it can't be commited for edit. Clicking return doesn't deselect the Combobox neither is the method commitEdit invoked. If an other column is selected for edit, the attempted edit is cancelled.

I put some effort into trying to figure this out, so I thought maybe one could help me here. As the original task is about much bigger classes in enterprise software, I abstracted it to ask this question.

I know I could make the column holding the enum of type String and make it work with MyEnum.values() and MyEnum.valueOf() but that could not go into production due to bad performance as the original classes are too big.

Here is my code as an example, if you don't understand the problems just try to use the combobox once and you'll see.

Class which is type of TableView:

public class MyClass {

private MyEnum myEnum;

private String string;

public MyClass(MyEnum myEnum, String string) {
    this.myEnum = myEnum;
    this.string = string;
}

public MyEnum getMyEnum() {
    return myEnum;
}

public void setMyEnum(MyEnum myEnum) {
    this.myEnum = myEnum;
}

public String getString() {
    return string;
}

}

It's enum field:

public enum MyEnum {

EnumOne("First Enum"),
EnumTwo("Second Enum");

private String name;

public String getName() {
    return name;
}

private MyEnum(String name) {
    this.name = name;
}

}

FX App:

public class NewFXMain extends Application {

@Override
public void start(Stage primaryStage) {
    ObservableList<MyClass> items = FXCollections.observableArrayList();
    items.add(new MyClass(MyEnum.EnumOne, "String"));
    TableView<MyClass> table = new TableView(items);
    table.setEditable(true);
    TableColumn<MyClass, MyEnum> myEnumColumn = new TableColumn();
    TableColumn<MyClass, String> stringColumn = new TableColumn();

    stringColumn.setCellFactory(TextFieldTableCell.forTableColumn());
    stringColumn.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getString()));



    myEnumColumn.setCellFactory((param) -> new MyEnumComboBoxCell());
    myEnumColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper(data.getValue().getMyEnum()));
    myEnumColumn.setOnEditCommit(
            event -> {
                event.getRowValue().setMyEnum(event.getNewValue());
                System.out.println(event.getRowValue().getMyEnum());

            });

    table.getColumns().addAll(myEnumColumn, stringColumn);

    StackPane root = new StackPane();
    root.getChildren().add(table);
    Scene scene = new Scene(root, 300, 250);
    primaryStage.setScene(scene);
    primaryStage.show();
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}

}

class MyEnumComboBoxCell extends ComboBoxTableCell<MyClass, MyEnum> {

private ComboBox<MyEnum> box;

public MyEnumComboBoxCell() {
    box = new ComboBox<>(FXCollections.observableArrayList(MyEnum.values()));
    box.setCellFactory(new Callback<ListView<MyEnum>, ListCell<MyEnum>>() {
        @Override
        public ListCell<MyEnum> call(ListView<MyEnum> param) {
            return new ListCell<MyEnum>() {
                @Override
                protected void updateItem(MyEnum item, boolean empty) {
                    super.updateItem(item, empty);
                    if ( item != null ) setText(item.getName());
                }

            };
        }
    });
}

@Override
public void startEdit() {
    super.startEdit();
    setGraphic(box);
}

@Override
public void commitEdit(MyEnum newValue) {
    super.commitEdit(newValue);
    if ( newValue != null ) {
        setText(newValue.getName());
        getTableView().getSelectionModel().getSelectedItem().setMyEnum(newValue);
        box.setValue(newValue);
    }
}

@Override
public void updateItem(MyEnum item, boolean empty) {
    super.updateItem(item, empty);
    if ( empty ) {
        setGraphic(null);
    } else {
        setGraphic(null);
        setText(item.getName());
    }
}

}

Upvotes: 1

Views: 2556

Answers (2)

J.Ober
J.Ober

Reputation: 199

Thanks a lot! Actually I've spent a day on this, partially with another person, so this is really appreciated! :)

But: I have to implement setOnEditCommit or otherwise the myEnum field backing the tableColumn does not change! With this everything works. Without, only what is displayed is changed.

    myEnumColumn.setOnEditCommit(
            event ->
    {
        event.getRowValue().setMyEnum(event.getNewValue());

    });

Upvotes: 1

Sunflame
Sunflame

Reputation: 3186

Instead of setting the name in updateItem use a StringConverter like:

public class MyEnumConverter extends StringConverter<MyEnum>{

    @Override public String toString(MyEnum enumConstant) {
        return enumConstant.getName();
    }

    @Override public MyEnum fromString(String string) {
        return MyEnum.valueOf(string);
    }
}

Then in the cell's constructor:

this.setConverter(new MyEnumConverter());

Edit: You may not @Override all of the ComboBoxTableCell's methods, since all of them are working like you want. On the other hand you should not specify an own ComboBox for the table cell, since it has one. You just have to add a StringConverter and set the items.

You may use like this:

myEnumColumn.setCellFactory((param) -> new ComboBoxTableCell<>(new StringConverter<MyEnum>() {
            @Override public String toString(MyEnum object) {
                return object.getName();
            }

            @Override public MyEnum fromString(String string) {
                return MyEnum.valueOf(string);
            }
        }, MyEnum.values()));

If you prefer you can create a separate class for the StringConverter like I mentioned earlier, then just simply:

myEnumColumn.setCellFactory(factory -> new ComboBoxTableCell<>(new MyEnumConverter(), MyEnum.values()));

You can also get rid of the myEnumColumn.setOnEditCommit.

Upvotes: 2

Related Questions