Arjan Arjan - 1 year ago 374
Java Question

How can I solve this visual glitch in my JavaFX TableView?

I made an app, it has a weird bug that's bugging me. :P

The app produces files. The file names are made of the value of a table column, and a numerical prefix. The prefix is also shown in the table. User can enable/disable entries in the table with a checkbox in the table row. Furthermore, user can disable the prefix, pad the prefix or make it start at a different value with a checkbox and a couple of spinners. The prefixes in the table adjust accordingly.

It's a bit large to post on here so I cut it and turned it into an app that produces invite codes for invited people.

The bug: Scroll down to the bottom of the table. Deselect the person with invite code 74, then 73. Then select them again, in the same order. The last three invite codes should read 073, 074 and 075 again. But quite often, they show 073, 073, 074 or 073, 074, 074.

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, it produces the right file names. Clicking any other control in the window (e.g. the scrollbar of the table or the text field) 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!?

Model -

package invcodebug;


public class Person {
private final SimpleBooleanProperty invited;
private final SimpleStringProperty invCode;
private final SimpleStringProperty name;

public Person() {

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

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

public String getInvCode(){
return invCode.get();
public void setInvCode(String invCode) {
public SimpleStringProperty invCodeProperty() {
return invCode;

public String getName(){
return name.get();
public void setName(String name) {;
public SimpleStringProperty nameProperty() {
return name;

package invcodebug;

import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.Callback;

public class FXMLDocumentController implements Initializable {
private ResourceBundle resources = null;
private CheckBox selectAllCheckBox;
@FXML private TableView<Person> personTable;
@FXML private TableColumn<Person, Boolean> invitedCol;
@FXML private TableColumn<Person, String> invCodeCol;
@FXML private TableColumn<Person, String> nameCol;
private final ObservableList<Person> persons
= FXCollections.observableArrayList();

@FXML private CheckBox invCodeCheckBox;
@FXML private Label paddingLabel;
@FXML private Spinner<Integer> paddingSpinner;
private static final int MIN_PADDING = 1;
private static final int MAX_PADDING = 5;
private static final int INIT_PADDING = 3;
@FXML private Label startAtLabel;
@FXML private Spinner<Integer> startAtSpinner;
private static final int MIN_STARTAT = 1;
private static final int MAX_STARTAT = 10000;
private static final int INIT_STARTAT = 1;
private static final int INC_STARTAT = 50;

public void initialize(URL location, ResourceBundle resources) {
this.resources = resources;

private void initInvCodeCheckBox(){
invCodeCheckBox.setOnAction((ActionEvent e) -> {

private void initPersonTable() {
invitedCol.setCellValueFactory(new PropertyValueFactory<>("invited"));
invitedCol.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
public ObservableValue<Boolean> call(Integer param) {
// DEBUG my friggin prefix bug
for (Person p : persons) {
System.out.println(p.isInvited() + " " + p.getInvCode() + " : " + p.getName()
return persons.get(param).invitedProperty();
invCodeCol.setCellValueFactory(new PropertyValueFactory<>("invCode"));

nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
// WORKS but has the onfocuslost cancels edits problem. Oh well.
nameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
public void handle(TableColumn.CellEditEvent<Person, String> event) {
((Person) event.getTableView().getItems().get(event.getTablePosition().getRow())).setName(event.getNewValue());


private void initPaddingSpinner() {
paddingSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(MIN_PADDING, MAX_PADDING, INIT_PADDING));
// set prefix to new padded string
paddingSpinner.valueProperty().addListener(new ChangeListener<Integer>() {
public void changed(ObservableValue<? extends Integer> obs, Integer oldVal, Integer newVal) {

private void initStartAtSpinner() {
startAtSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(MIN_STARTAT, MAX_STARTAT, INIT_STARTAT, INC_STARTAT));
// set prefix to new padded string
startAtSpinner.valueProperty().addListener(new ChangeListener<Integer>() {
public void changed(ObservableValue<? extends Integer> obs, Integer oldVal, Integer newVal) {

private String makeInvCode(Integer invNumber) {
String str = invNumber.toString();
int padding = paddingSpinner.getValue();
StringBuilder sb = new StringBuilder();
for (int toPrepend = padding - str.length(); toPrepend > 0; toPrepend--) {
return sb.toString();

private void doInvCode() {
int invCounter = startAtSpinner.getValue();
for (Person p : persons) {
if (invCodeCheckBox.isSelected() && p.isInvited()) {
} else {

public CheckBox getSelectAllCheckBox() {
if (selectAllCheckBox == null) {
final CheckBox selectAllCheckBox = CheckBoxBuilder.create().build();
selectAllCheckBox.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
for (Person p : persons) {
this.selectAllCheckBox = selectAllCheckBox;
return selectAllCheckBox;

private void populatePersons() {
for (int i = 0; i < 15; i++) {
new Person(true, "", "Smith"),
new Person(true, "", "Johnson"),
new Person(true, "", "Williams"),
new Person(true, "", "Jones"),
new Person(true, "", "Brown"));


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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Spinner?>
<?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="" xmlns:fx="" fx:controller="invcodebug.FXMLDocumentController">
<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">
<TableColumn fx:id="invitedCol" prefWidth="27.0" sortable="false" />
<TableColumn fx:id="invCodeCol" editable="false" prefWidth="87.0" resizable="false" sortable="false" text="InvCode" />
<TableColumn fx:id="nameCol" prefWidth="387.0" sortable="false" text="Name" />
<TextField layoutX="66.0" layoutY="376.0" prefHeight="26.0" prefWidth="181.0" promptText="Name" />
<CheckBox fx:id="invCodeCheckBox" layoutX="36.0" layoutY="414.0" mnemonicParsing="false" text="Invite Code" AnchorPane.leftAnchor="40.0" />
<Label fx:id="paddingLabel" layoutX="191.0" layoutY="415.0" text="Padding:">
<Insets right="7.0" />
<Spinner fx:id="paddingSpinner" layoutX="255.0" layoutY="410.0" prefHeight="26.0" prefWidth="72.0">
<Label fx:id="startAtLabel" layoutX="360.0" layoutY="415.0" text="Start at:">
<Insets right="7.0" />
<Spinner fx:id="startAtSpinner" layoutX="421.0" layoutY="410.0" prefHeight="26.0" prefWidth="72.0" AnchorPane.rightAnchor="107.0" />

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 {

public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

Scene scene = new Scene(root);


public static void main(String[] args) {


Answer Source

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.