Rahul Gupta
Rahul Gupta

Reputation: 520

JavaFx- Why ChoiceBox value reset inside a tableview after scrolling

I have a tableview which have two columns.First column simply populated by observableList and Second column have choiceboxes in each cell.

My problem is that when I select value from choicebox and scroll down to select value from another choicebox and again scroll up then previous selected values of choiceboxe resets.

Below are my code:

@FXML
private TableColumn<FileHeaders, ChoiceBox<String>> fileHeaders;
public void setFileHeaders(ObservableList<String> fileHeadersObservableList) {
    fileHeaders.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<FileHeaders, ChoiceBox<String>>, ObservableValue<ChoiceBox<String>>>() {
        @Override
        public ObservableValue<ChoiceBox<String>> call(TableColumn.CellDataFeatures<FileHeaders, ChoiceBox<String>> rawUdrsList) {
            ChoiceBox<String> choiceBox = new ChoiceBox<>();
            System.out.println(choiceBox);//this value print again and again when I scroll the tableview
            choiceBox.setItems(fileHeadersObservableList);
            choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> ov, String t, String valueFromChoiceBox) {
                    //write some code stuff     
                }
            });
            return new SimpleObjectProperty<ChoiceBox<String>>(choiceBox);
        }
    });
}

As far as I think that call method invoke everytime when i scroll the tableview,so new choicebox created and set into the tableview.

How can i solve this problem, I really appreciate ifhelps from you guys.

Thanks

Upvotes: 1

Views: 901

Answers (1)

jewelsea
jewelsea

Reputation: 159556

Your Error

A cell value factory should not return a node, it should just return the relevant data property backing the cell. Instead, the nodes should be returned in as part of the table cell implementation supplied by a cell factory.

Defining Cell Factories

The makery tutorials provide a nice example which shows the difference in usage between a cell value factory and a cell factory. Usually, the two are used in combination when you require a custom rendering of cell data.

However, JavaFX has predefined helper classes: look at How do you create a table cell factory in JavaFX to display a ChoiceBox?, which uses a ChoiceBoxTableCell. Perhaps your question just works out as a duplicate of that.

Background

To understand how ChoiceBoxTableCell works from first principles, you can look at its source code. A table cell is a Labeled. A Labeled can have both text and a graphic. The text label is used to display the chosen value when the cell is not being edited. Whenever the user double clicks on the cell to edit it, then the text is set to null and a graphic node for the label is displayed instead (in this case a choice box allowing the user to choose a new value for the cell being edited). Once the editing is complete, the new value for the choice is saved to the underlying backing data value property, the display switches back to plain text and the graphic for the cell is set back to null so that it no longer displays. For your use case, it will be easier just to use the pre-defined class rather than to write a custom implementation of the same functionality.

Sample

Output of the sample app below, after the user has twice clicked on a user state which is backed by a choice box, the first click highlighting the row and the second click bringing up the edit choice box for the item:

sample choice box

Sample code demoing use of a ChoiceBoxTableCell:

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.stage.Stage;

public class TableChoices extends Application {

    @Override
    public void start(final Stage stage) throws Exception {
        ObservableList<User> data = createTestData();

        TableColumn<User, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));

        TableColumn<User, UserState> stateCol = new TableColumn<>("State");
        stateCol.setCellValueFactory(new PropertyValueFactory<>("state"));
        stateCol.setCellFactory(
                ChoiceBoxTableCell.forTableColumn(UserState.values())
        );
        stateCol.setEditable(true);
        stateCol.setPrefWidth(100);
        stateCol.setOnEditCommit(
                (TableColumn.CellEditEvent<User, UserState> t) ->
                        t.getTableView()
                                .getItems()
                                .get(t.getTablePosition().getRow())
                                .setState(t.getNewValue())
        );

        TableView<User> tableView = new TableView<>(data);
        //noinspection unchecked
        tableView.getColumns().addAll(
                nameCol,
                stateCol
        );
        tableView.setPrefSize(170, 150);
        tableView.setEditable(true);

        stage.setScene(new Scene(tableView));
        stage.show();
    }

    public static void main(String[] args) throws Exception {
        launch(args);
    }

    public enum UserState {
        ACTIVE,
        LOCKED,
        DELETED
    }

    public static class User {
        private StringProperty name;
        private ObjectProperty<UserState> state;

        public User(String name, UserState state) {
            this.name = new SimpleStringProperty(name);
            this.state = new SimpleObjectProperty<>(state);
        }

        public String getName() {
            return name.get();
        }

        public StringProperty nameProperty() {
            return name;
        }

        public void setName(String name) {
            this.name.set(name);
        }

        public UserState getState() {
            return state.get();
        }

        public ObjectProperty<UserState> stateProperty() {
            return state;
        }

        public void setState(UserState state) {
            this.state.set(state);
        }
    }

    private ObservableList<User> createTestData() {
        return FXCollections.observableArrayList(
                new User("Jack", UserState.ACTIVE),
                new User("Jill", UserState.LOCKED),
                new User("Tom", UserState.DELETED),
                new User("Harry", UserState.ACTIVE)
        );
    }
}

I advise you to look over the sample code closely and note the use of a setOnEditCommit handler for the table column, which updates the backing data to reflect the edited value. Without code such as this, then the edit will not be committed back to the backing data (at least in Java 8, the JavaFX Table Tutorial notes that future JavaFX versions may make the implementation a bit less clunky).

Upvotes: 2

Related Questions