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!
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!