Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8154854

JavaFX TableView does not update correctly

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Cannot Reproduce
    • Icon: P4 P4
    • 9
    • 8u77
    • javafx
    • x86_64
    • linux_ubuntu

      FULL PRODUCT VERSION :
      java version "1.8.0_77"
      Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
      Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)


      ADDITIONAL OS VERSION INFORMATION :
      4.2.0-35-generic #40-Ubuntu SMP Tue Mar 15 22:15:45 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

      ANY OS is affected as far as I can tell.

      A DESCRIPTION OF THE PROBLEM :
      TableCell doesn't update correctly (when reused)

      Included source provides a minimal, complete and verifiable example. Files:
      FXMLDocumentController.java
      InvCodeBug.java
      Person.java
      FXMLDocument.fxml

      Console dump of the ObservableList shows that the values are *calculated* correctly by the example source code, but not *shown* correctly.

      Clicking on any other control in the window, e.g. the scrollbar, makes the visible value in the table change to the corresponding value in the ObservableList.

      So the table is not showing the correct value until the focus changes, and the bug is not in the example source code.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Tableview contains 2 columns, 75 rows.

      Left column consists of checkboxes. Right column is a code that changes depending on the state of the checkboxes in the left column. The code is simply the amount of rows with enabled checkboxes as String. At initialization, all checkboxes are selected and the code column is filled with Strings from "1" to "75".

      Scroll down to the bottom of the table. Deselect the checkbox in row 74, then select it again. The bug does not always occur. If it doesn't, Scrolling up and down a bit with the scrollbar and repeating the deselecting/selecting step can make it occur.

      If the bug occurs it stays bugged, deselect the checkbox again...


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      When reselecting the checkbox on row 74, the codes in the last 2 rows, should read "74" and "75" again.

      When deselecting the checkbox on row 74 again, the codes in the last 3 rows should read "73", "", "74"
      ACTUAL -
      Quite often, the values of the last two rows show "74", "74" instead of the expected "74", "75".

      If the bug occurs, and you deselect the checkbox on row 74 again, the last three rows show "73", "", 75" instead of the expected "73", "", "74".


      REPRODUCIBILITY :
      This bug can be reproduced often.

      ---------- BEGIN SOURCE ----------
      FXMLDocumentController.java

      package invcodebug;

      import java.net.URL;
      import java.util.ResourceBundle;
      import javafx.beans.value.ObservableValue;
      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.fxml.FXML;
      import javafx.fxml.Initializable;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableView;
      import javafx.scene.control.cell.CheckBoxTableCell;
      import javafx.scene.control.cell.PropertyValueFactory;
      import javafx.util.Callback;

      public class FXMLDocumentController implements Initializable {
      @FXML private TableView<Person> personTable;
      @FXML private TableColumn<Person, Boolean> invitedCol;
      @FXML private TableColumn<Person, String> inviteCodeCol;
      private final ObservableList<Person> persons
      = FXCollections.observableArrayList();

      @Override
      public void initialize(URL location, ResourceBundle resources) {
      initPersonTable();
      populatePersons();
      }

      private void initPersonTable() {
      invitedCol.setCellValueFactory(new PropertyValueFactory<>("invited"));
      inviteCodeCol.setCellValueFactory(new PropertyValueFactory<>("inviteCode"));

      invitedCol.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
      @Override
      public ObservableValue<Boolean> call(Integer param) {
      doInvCode();

      // SHOWS: underlying ObservableList has correct values
      System.out.println("--------------------------");
      for (Person p : persons) {
      System.out.println(p.isInvited() + " " + p.getInviteCode()
      );
      }

      // WORKAROUND: personTable.requestFocus();

      return persons.get(param).invitedProperty();
      }
      }));

      personTable.setItems(persons);
      }

      private void doInvCode() {
      int invCounter = 1;
      for (Person p : persons) {
      if (p.isInvited()) {
      p.setInviteCode(((Integer) invCounter).toString());
      invCounter++;
      } else p.setInviteCode("");
      }
      }

      private void populatePersons() {
      for (int i = 0; i < 75; i++) {
      persons.add(new Person(true, ""));
      }
      }
      }



      InvCodeBug.java

      package invcodebug;

      import javafx.application.Application;
      import javafx.fxml.FXMLLoader;
      import javafx.scene.Parent;
      import javafx.scene.Scene;
      import javafx.stage.Stage;

      public class InvCodeBug extends Application {

      @Override
      public void start(Stage stage) throws Exception {
      Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
      Scene scene = new Scene(root);
      stage.setScene(scene);
      stage.show();
      }

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



      Person.java

      package invcodebug;

      import javafx.beans.property.SimpleBooleanProperty;
      import javafx.beans.property.SimpleStringProperty;

      public class Person {
      private final SimpleBooleanProperty invited;
      private final SimpleStringProperty inviteCode;

      public Person(boolean invited, String inviteCode) {
      this.invited = new SimpleBooleanProperty(invited);
      this.inviteCode = new SimpleStringProperty(inviteCode);
      }

      public boolean isInvited() {
      return invited.get();
      }
      public SimpleBooleanProperty invitedProperty() {
      return invited;
      }

      public String getInviteCode(){
      return inviteCode.get();
      }
      public void setInviteCode(String invCode) {
      this.inviteCode.set(invCode);
      }
      public SimpleStringProperty inviteCodeProperty() {
      return inviteCode;
      }
      }



      FXMLDocument.fxml

      <?xml version="1.0" encoding="UTF-8"?>

      <?import javafx.scene.control.Label?>
      <?import javafx.scene.control.TableColumn?>
      <?import javafx.scene.control.TableView?>
      <?import javafx.scene.control.TextField?>
      <?import javafx.scene.layout.AnchorPane?>

      <AnchorPane id="AnchorPane" prefHeight="464.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="invcodebug.FXMLDocumentController">
      <children>
      <TableView fx:id="personTable" editable="true" layoutX="26.0" layoutY="28.0" prefHeight="347.0" prefWidth="572.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="20.0">
      <columns>
      <TableColumn fx:id="invitedCol" prefWidth="27.0" sortable="false" />
      <TableColumn fx:id="inviteCodeCol" editable="false" prefWidth="110.0" resizable="false" sortable="false" text="Invite Code" />
      </columns>
      </TableView>
      </children>
      </AnchorPane>
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      In this case, a

      Table.requestFocus();

      in the Callback does the trick, as shown in the comment in the source code.

      Thank you JavaFX team!

            jgiles Jonathan Giles
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: