How to prevent Javafx Tableview from reloading all displayed Rows, when only certain Items change?

287 Views Asked by At

I'm trying to improve the performance of an Application with multiple Tableviews that are constantly being updated. On each update only 1 item inside the ObservableList changes. The reloading of the rows of those Tableviews and applying css-styles to these rows are slowing down the application in a significant way.

I looked at the way, that Tableviews are reloading their rows and realized that all rows are constantly being reconstructed from scratch independent of the type of change to the underlying ObservableList. The changing of 1 item inside the viewport / 1 item outside of the viewport / a complete replacement of the List causes the exact number of row constructions.

Is there a way prevent the Javafx Tableviews from reloading all displayed Rows, when only certain Items change?

Other possible tweaks to improve the performance of constantly reloading Tableviews would be greatly appreciated.

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class FXApplication extends Application {

    private BorderPane border;
    private TableView<Item> tableView;
    private Button addItemToTv = new Button("Add");
    private Button replaceFirst = new Button("Replace first");
    private Button replaceLast = new Button("Replace last");
    private Button fullReplace = new Button("Full replace");

    private int counter = 0;

    protected Map<Integer, TableRow<Item>> rowsByIndex = new HashMap<>();

    @Override
    public void start(Stage primaryStage) {

        constructTv();
        setButtonActions();
        Scene s = constructScene();

        primaryStage.setScene(s);
        primaryStage.show();
    }

    private Scene constructScene() {
        border = new BorderPane();
        border.setCenter(tableView);
        border.setTop(new HBox(addItemToTv, replaceFirst, replaceLast, fullReplace));

        ScrollPane scp = new ScrollPane();
        scp.setFitToHeight(true);
        scp.setFitToWidth(true);
        scp.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
        scp.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
        scp.setContent(border);

        return new Scene(scp);
    }

    private void setButtonActions() {
        addItemToTv.setOnAction(e -> {
            tableView.getItems().add(new Item("bla"));
            Platform.runLater(this::resetCounter);
        });
        replaceFirst.setOnAction(e -> {
            tableView.getItems().set(0, new Item("blub"));
            Platform.runLater(this::resetCounter);
        });
        fullReplace.setOnAction(e -> {
            ArrayList<Item> items = new ArrayList<>(tableView.getItems());
            tableView.getItems().setAll(items);
            Platform.runLater(this::resetCounter);
        });
        replaceLast.setOnAction(e -> {
            tableView.getItems().set(tableView.getItems().size()-1, new Item("blub"));
            Platform.runLater(this::resetCounter);
        });
    }


    private void resetCounter() {
        Timeline tl = new Timeline(new KeyFrame(Duration.millis(500), (ee) -> {
            System.out.println(counter + " row calls");
            counter = 0;
        }));
        tl.play();

    }

    private Node constructTv() {
        tableView = new TableView<>();
        for(int i = 0; i<10; i++){
            TableColumn<Item, String> col = new TableColumn<>("col " + i);
            col.setCellValueFactory(param -> {
                return new SimpleStringProperty(param.getValue().getString());
            });
            tableView.getColumns().add(col);
        }

        for (int i = 0; i < 30 ; i++) {
            tableView.getItems().add(new Item("bla"));
        }

        tableView.setRowFactory(param -> {
            TableRow<Item> row = new TableRow<>();
            row.itemProperty().addListener((observable, oldValue, newValue) -> {
                Platform.runLater(() -> {
                    rowsByIndex.put(row.getIndex(), row);
                    System.err.println("row change " + row.getIndex());
                    counter++;
                });
            });
            return row;
        });

        return tableView;
    }


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

}
class Item {
    private String string;

    public Item(String s) {
        string = s;
    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }
}
0

There are 0 best solutions below