I am encountering an issue with rendering a Label in TableCell. The Label flickers when the TableCell gets an update. This happens if I wrap the Label in a Pane and set it as graphic to the cell.
On the other hand, if I set the Label directly as the graphic(without wrapper), then it doesn't show the flickering effect.
Do any of you have any idea why this happens.
Below is a quick demo that showcases the flickering effect of the Label. You can uncomment the other line and can see the flickering does not happen. The issue happens on both JavaFX8 and JavaFX20
When wrapped with StackPane (flickering):

(some frames might be skipped in gif, but the flickering happens on every update)
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class LabelRenderingInCell_Demo extends Application {
class Person {
private final StringProperty name = new SimpleStringProperty();
public Person(final String n) {
setName(n);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(final String name) {
this.name.set(name);
}
@Override
public String toString() {
return name.get();
}
}
private int count = 1;
@Override
public void start(final Stage primaryStage) throws Exception {
Person person = new Person("Name");
final ObservableList<Person> items = FXCollections.observableArrayList();
items.add(person);
final TableView<Person> tableView = new TableView<>();
final TableColumn<Person, String> col = new TableColumn<>("Name");
col.setPrefWidth(150);
col.setCellValueFactory(param -> param.getValue().nameProperty());
col.setCellFactory(new Callback<>() {
@Override
public TableCell<Person, String> call(final TableColumn<Person, String> param) {
return new TableCell<>() {
@Override
protected void updateItem(final String item, final boolean empty) {
super.updateItem(item, empty);
setText(null);
if (item != null) {
setGraphic(new StackPane(new Label(item)));
// Uncomment the below code for no flickering
// setGraphic(new Label(item));
} else {
setGraphic(null);
}
}
};
}
});
tableView.getColumns().add(col);
tableView.setItems(items);
VBox.setVgrow(tableView, Priority.ALWAYS);
Button btn = new Button("Update");
btn.setOnAction(e -> {
person.setName("Name " + count++);
});
Scene scene = new Scene(new VBox(btn, tableView));
primaryStage.setScene(scene);
primaryStage.setTitle("Label In TableCell");
primaryStage.show();
}
}
Update:
The content of is very dynamic based on the value associated with the property. It can be combination of different colored labels with some images as well

Update #2: Based on the suggestions in the comments, I changed my code to reuse the labels or atleast maintain a pool of Labels. But I still see the flickering everytime a new Label is added to cell.
Below is a quick modified demo with the logic. I am using a pool of 5 Labels that needs to be displayed based on the grade. Each Label will have its own styling, interactions, icons etc. So using only 1 Label for all grades will make things too messy here.
The Label flickers everytime a new grade Label is generated and added. For testing purpose included a refresh button that generates a new cell for checking the cycle again. (You may not see the flicker in the below gif if the frames gets skipped, but you can notice when running the application)
It may look like it is just a blink. When the behavior is not systematic, it looks a bit odd/worse when seen on larger scale.

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
public class LabelRenderingInCell_Demo extends Application {
class Person {
private final ObjectProperty<Integer> score = new SimpleObjectProperty<>(0);
public ObjectProperty<Integer> scoreProperty() {
return score;
}
public void setScore(final int score) {
this.score.set(score);
}
}
@Override
public void start(final Stage primaryStage) throws Exception {
Person person = new Person();
final ObservableList<Person> items = FXCollections.observableArrayList();
items.add(person);
final TableView<Person> tableView = new TableView<>();
final TableColumn<Person, Integer> col = new TableColumn<>("Grade");
col.setPrefWidth(150);
col.setCellValueFactory(param -> param.getValue().scoreProperty());
col.setCellFactory(new Callback<>() {
@Override
public TableCell<Person, Integer> call(final TableColumn<Person, Integer> param) {
return new TableCell<>() {
private Map<String, Label> labelPool = new HashMap<>();
private HBox pane = new HBox();
@Override
protected void updateItem(final Integer item, final boolean empty) {
super.updateItem(item, empty);
setText(null);
if (item != null) {
pane.getChildren().clear();
Label label = getLabel(item);
label.setText(buildGrade(item) + "(" + item + ")");
pane.getChildren().addAll(label);
setGraphic(pane);
} else {
setGraphic(null);
}
}
private String buildGrade(int score) {
if (score < 3) {
return "Poor";
} else if (score < 5) {
return "Average";
} else if (score < 7) {
return "Good";
} else if (score < 9) {
return "Better";
} else {
return "Excellent";
}
}
private Label getLabel(int score) {
String grade = buildGrade(score);
Label lbl = labelPool.get(grade);
if (lbl == null) {
System.out.println("Cell-"+this.hashCode() + " :: New label created.... " + grade);
lbl = new Label(grade + "(" + score + ")");
// Add other styling and interactions based on the grade
labelPool.put(grade, lbl);
}
return lbl;
}
};
}
});
tableView.getColumns().add(col);
tableView.setItems(items);
VBox.setVgrow(tableView, Priority.ALWAYS);
SecureRandom rnd = new SecureRandom();
Button btn = new Button("Update Score");
btn.setOnAction(e -> {
person.setScore(rnd.nextInt(11));
});
Button refresh = new Button("Refresh Table to start from scratch");
refresh.setOnAction(e -> {
System.out.println("--------------------------------");
tableView.refresh();
});
VBox root = new VBox(btn, refresh, tableView);
root.setSpacing(5);
root.setPadding(new Insets(5));
Scene scene = new Scene(root, 350, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Label In TableCell");
primaryStage.show();
}
}
Don't create node instances in the updateItem() method. This method may be called frequently and is not designed for "heavy" operations such as creating UI nodes.
Instead, create any nodes you might need at the creation time of the cell. Then use the updateItem() method solely to configure those nodes and choose which to display, as necessary.
Here is an example cell factory which switches nodes on each button click, without any flicker:
col.setCellFactory(new Callback<>() {
@Override
public TableCell<Person, String> call(final TableColumn<Person, String> param) {
return new TableCell<>() {
private final Label label = new Label();
private final StackPane even = new StackPane();
private final StackPane odd = new StackPane();
@Override
protected void updateItem(final String item, final boolean empty) {
super.updateItem(item, empty);
setText(null);
if (item != null) {
StackPane graphic ;
String[] tokens = item.split(" ");
int value = tokens.length == 2 ? Integer.parseInt(tokens[1]) : 0;
graphic = value % 2 == 0 ? even : odd ;
even.getChildren().clear();
odd.getChildren().clear();
graphic.getChildren().add(label);
label.setText(item);
setGraphic(graphic);
// Uncomment the below code for no flickering
// setGraphic(new Label(item));
} else {
setGraphic(null);
}
}
};
}
});
This seems to be really a nasty problem:
The very first time such a switched-in node is showing, it flickers - doesn't happen if switched again. Creating all nodes up front doesn't seem to help.
What does help (not formally tested, beware!) is to add the cell
in code (note that the contentDisplay isn't really important, just something I prefer to do when it's graphic only :)
col.setCellFactory(new Callback<>() {
@Override
public TableCell<Person, Integer> call(final TableColumn<Person, Integer> param) {
return new TableCell<>() {
private Map<String, Label> labelPool = new HashMap<>();
private HBox pane = new HBox();
private ChangeListener<TableView<?>> tableListener;
{
buildPool();
setGraphic(pane);
setText("");
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
tableListener = (src, ov, nv) -> {
if (nv != null) {
System.out.println(nv.getSkin());
if (nv.getSkin() != null) {
pane.getChildren().addAll(labelPool.values());
((TableViewSkin<?>) nv.getSkin()).getChildren().add(this);
pane.getChildren().clear();
}
}
};
tableViewProperty().addListener(tableListener);
}
@Override
protected void updateItem(final Integer item, final boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Label label = getLabel(item);
label.setText(buildGrade(item) + "(" + item + ")");
pane.getChildren().setAll(label);
} else {
pane.getChildren().clear();
}
}
private void buildPool() {
for (int i = 0; i < 11; i++) {
getLabel(i);
}
}
private String buildGrade(int score) {
if (score < 3) {
return "Poor";
} else if (score < 5) {
return "Average";
} else if (score < 7) {
return "Good";
} else if (score < 9) {
return "Better";
} else {
return "Excellent";
}
}
private Label getLabel(int score) {
String grade = buildGrade(score);
Label lbl = labelPool.get(grade);
if (lbl == null) {
System.out.println("Cell-"+this.hashCode() + " :: New label created.... " + grade);
lbl = new Label(grade + "(" + score + ")");
// Add other styling and interactions based on the grade
labelPool.put(grade, lbl);
}
return lbl;
}
};
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With