Flowless icon indicating copy to clipboard operation
Flowless copied to clipboard

Creating a two dimensional VirtualFlow

Open Symeon94 opened this issue 2 months ago • 1 comments

Hello,

I'm currently using TableView in my project, but I've run into two major limitations that are pushing me to consider reimplementing a custom table using Flowless:

  1. Performance inefficiencies — similar to those highlighted in Flowless benchmarks.
  2. Limited control over scrolling — specifically, no direct access to TableView's scrolling behavior.

To work around this, I started experimenting with VirtualFlow. However, it seems you can only create either a vertical or a horizontal flow — not both simultaneously. My approach was to create a horizontal VirtualFlow where each column is itself a vertical VirtualFlow. This setup works to some extent, but introduces a new challenge:

  • I can add a global horizontal scrollbar.
  • I cannot add vertical scrollbars for all columns together.

I tried to bind vertical scrolling bidirectionally, but it seems to causes misalignments in certain cases.

My question is: is there a known way to implement a truly two-dimensional VirtualFlow with independent horizontal and vertical scrollbars? Or any best practices for synchronizing scrolling across nested flows without losing alignment?

Any insights or suggestions would be greatly appreciated!

Thanks, Syméon

Symeon94 avatar Nov 06 '25 14:11 Symeon94

Here is some of the code if that helps understanding:

The table

public class CustomTable extends AnchorPane {
    public CustomTable(List<List<String>> columns) {
        ObservableList<List<String>> observableList = FXCollections.observableList(columns);

        // Following is a property to bind all the columns
        Var<Double> scrollY = Var.doubleVar(new SimpleDoubleProperty(0.0));
        VirtualFlow<List<String>, CustomTableColumn> columnContent = VirtualFlow.createHorizontal(observableList, strings -> {
            CustomTableColumn column = new CustomTableColumn(strings);
            column.bindScroll(scrollY);
            return column;
        });
        VirtualizedScrollPane<VirtualFlow<List<String>, CustomTableColumn>> scrollPane = new VirtualizedScrollPane<>(columnContent);
        AnchorPane.setTopAnchor(scrollPane, 0);
        AnchorPane.setRightAnchor(scrollPane, 0);
        AnchorPane.setBottomAnchor(scrollPane, 0);
        AnchorPane.setLeftAnchor(scrollPane, 0);
        this.getChildren().add(scrollPane);
    }
}

And the custom column:


public class CustomTableColumn implements Cell<List<String>, AnchorPane> {
    private final VirtualFlow<String, CustomCell> columnContent;
    private final AnchorPane pane;

    public CustomTableColumn(List<String> content) {
        pane = new AnchorPane();
        ObservableList<String> list = FXCollections.observableList(content);
        columnContent = VirtualFlow.createVertical(list, CustomCell::new);
        AnchorPane.setTopAnchor(columnContent, 0);
        AnchorPane.setRightAnchor(columnContent, 0);
        AnchorPane.setBottomAnchor(columnContent, 0);
        AnchorPane.setLeftAnchor(columnContent, 0);
        pane.getChildren().add(columnContent);
    }

    public void bindScroll(Var<Double> prop) {
        columnContent.estimatedScrollYProperty().bindBidirectional(prop);
    }

    @Override
    public AnchorPane getNode() {
        return pane;
    }
}

Symeon94 avatar Nov 06 '25 14:11 Symeon94