Search code examples
javafxscrolltableviewtextfieldmousewheel

Javafx TableView scrolling bug when editing text cell


When I'm editing a Textfield cell in a Javafx TableView I have noticed that if I scroll using mousewheel then it changes focus to another cell in the same column.

This seems like a bug to me.

In my specific situation I have setup a table where I only want some rows to be editable. This generally works fine except you are able to subvert this constraint by clicking to edit an editable row and then scrolling the mousewheel. This allows you then then edit rows which should not be editable.

This can even be demonstrated by running this simple example from the Oracle TableView docs

Run the example and add extra rows until it requires a vertical scrollbar to appear. Then start editing a cell and scroll with mouse wheel

Does anyone know of a fix/work around for this?

I am running on 1.8.0_92

Updated Example With instructions:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableViewExample extends Application {

  private TableView<Person> table = new TableView<Person>();
  private ObservableList<Person> people = FXCollections.observableArrayList();

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

  public TableViewExample() {
    for (int id = 0; id < 100; id++) {
      people.add(new Person(String.valueOf(id)));
    }
  }

  @Override
  public void start(Stage stage) {
    Scene scene = new Scene(new Group());
    stage.setTitle("Table View Sample");

    table.setEditable(true);

    TableColumn idColumn = new TableColumn("Id");
    idColumn.setPrefWidth(400);
    idColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("id"));
    idColumn.setCellFactory(
          new Callback<TableColumn, TableCell>() {
            public TableCell call(TableColumn p) {
              return new EditingCell();
            }
          }
    );
    idColumn.setOnEditCommit(
          new EventHandler<CellEditEvent<Person, String>>() {
            @Override
            public void handle(CellEditEvent<Person, String> t) {
              Person person =
                    ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow()));
              person.setId(t.getNewValue());
            }
          }
    );

    table.setItems(people);
    table.getColumns().addAll(idColumn);
    ((Group) scene.getRoot()).getChildren().addAll(table);
    stage.setScene(scene);
    stage.show();
  }

  public static class Person {

    private final SimpleStringProperty id;

    private Person(String id) {
      this.id = new SimpleStringProperty(id);
    }

    public String getId() {
      return id.get();
    }

    public void setId(String id) {
      this.id.set(id);
    }
  }

  class EditingCell extends TableCell<Person, String> {

    private TextField textField;

    public EditingCell() {
    }

    @Override
    public void startEdit() {
      if (!isEmpty()) {
        super.startEdit();
        createTextField();
        setText(null);
        setGraphic(textField);
        textField.selectAll();
      }
    }

    @Override
    public void cancelEdit() {
      super.cancelEdit();

      setText((String) getItem());
      setGraphic(null);
    }

    @Override
    public void updateItem(String item, boolean empty) {
      super.updateItem(item, empty);

      if (empty) {
        setText(null);
        setGraphic(null);
      } else {
        if (isEditing()) {
          if (textField != null) {
            textField.setText(getString());
          }
          setText(null);
          setGraphic(textField);
        } else {
          setText(getString());
          setGraphic(null);
        }
      }
    }

    private void createTextField() {
      textField = new TextField(getString());
      textField.setMinWidth(getWidth() - getGraphicTextGap() * 2);
      textField.focusedProperty().addListener(new ChangeListener<Boolean>(){
        @Override
        public void changed(ObservableValue<? extends Boolean> arg0,
              Boolean arg1, Boolean arg2) {
          if (!arg2) {
            commitEdit(textField.getText());
          }
        }
      });
    }

    private String getString() {
      return getItem() == null ? "" : getItem();
    }
  }
}

Running example:

Click to edit a value in a cell

enter image description here

Scroll wheel mouse down and notice that another cell has been focused in edit mode

enter image description here


Solution

  • In my case the following code has resolved the problem:

     table.addEventFilter(ScrollEvent.ANY, scrollEvent -> {
                table.refresh();
    
                // close text box
                table.edit(-1, null);
            });