Reputation: 823
Left column: Checkboxes to select or deselect rows, selected by default. Right column: a String representing the amount of selected rows up to and including the row. So deselecting a checkbox in a row changes the values in the rows underneath.
The bug: Scroll down to the bottom of the table. Deselect the checkbox in the row with invite code 74. Select it again. The last three invite codes should read 73, 74 and 75 again. But quite often, they show 73, 73, 74 or 73, 74, 74.
The bug does not always occur! If it does not occur, scrolling up and down a bit with the scrollbar of the table and repeating the procedure above can make it occur.
It seems that the bug is only visual - I made it dump the contents of the ObservableList to the console and it shows the correct values. Other than this visual glitch, my app works correctly. Clicking any other control in the window (e.g. the scrollbar of the table) flips the invite codes in the table to the correct value. Switching to another workspace on my desktop and going back makes it show the right values as well.
Small image, showing the console dump of the ObservableList on the left, the bugged table on the right.
The Question, quite logically: How can I squash this bug!?
EDIT: threw out more code as advised by Kleopatra. Thanks!
MCV:
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()
);
}
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, ""));
}
}
}
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>
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);
}
}
Upvotes: 4
Views: 994
Reputation: 383
Probably not the most technical answer, but by using personTable.requestFocus();
at the end of the doInvCode()
method, the table is refreshed visually and seems to fix the problem.
Upvotes: 1