FXMisc / Flowless

Efficient VirtualFlow for JavaFX
BSD 2-Clause "Simplified" License
187 stars 39 forks source link

GetFirstVisibleIndex returns same values before and after scrolling #123

Open PavelTurk opened 1 month ago

PavelTurk commented 1 month ago

This is my test code:

public class FlawTest extends Application {

    private static class FlowRow implements Cell<Integer, Node> {

        private Integer rowIndex;

        private final Label label = new Label();

        private final HBox hBox = new HBox(label);

        public FlowRow(Integer rowIndex) {
            this.rowIndex = rowIndex;
            label.setText(String.valueOf(rowIndex));
        }

        @Override
        public void updateItem(Integer item) {
            rowIndex = item;
            if (item == null) {
                label.setText("");
            } else {
                label.setText(String.valueOf(rowIndex));
            }
        }

        @Override
        public Node getNode() {
            return hBox;
        }

        @Override
        public boolean isReusable() {
            return true;
        }
    }

    private VirtualFlow<Integer, FlowRow> flow;

    private ObservableList<Integer> rows;

    @Override
    public void start(Stage primaryStage) throws Exception {
        List<Integer> list = new ArrayList<>();
        for (var i = 0; i < 100000; i++) {
            list.add(i);
        }
        rows = FXCollections.observableList(list);

        flow = VirtualFlow.createVertical(rows, row -> new FlowRow(row));
        VirtualizedScrollPane<VirtualFlow<Integer, FlowRow>> scrollPane = new VirtualizedScrollPane<>(flow);
        VBox.setVgrow(scrollPane, Priority.ALWAYS);

        var scrollButton = new Button("Scroll");
        scrollButton.setOnAction(e -> {
            System.out.println("Before scrolling first cell index: " + flow.getFirstVisibleIndex());
            flow.scrollYBy(flow.getHeight());
            System.out.println("After scrolling first cell index: " + flow.getFirstVisibleIndex());
        });

        VBox root = new VBox(scrollPane, new HBox(scrollButton));
        Scene scene = new Scene(root, 400, 300);
        primaryStage.setTitle("Flaw Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

If you push button you will get the following output:

Before scrolling first cell index: 0
After scrolling first cell index: 0
Before scrolling first cell index: 17
After scrolling first cell index: 17

I tried:

scrollButton.setOnAction(e -> {
    System.out.println("Before scrolling first cell index: " + flow.getFirstVisibleIndex());
    flow.scrollYBy(flow.getHeight());
    Platform.runLater(() -> System.out.println("After scrolling first cell index: " + flow.getFirstVisibleIndex()));
});

but the result was the same.

Jugen commented 1 month ago

Yeah, at least one layout pulse must occur for the value to update. What happens is that "Platform.runLater" is bundled into the same layout pulse as "scrollYBy" so the value hasn't been updated yet. So you need something like the following:

scrollButton.setOnAction(e -> {
    System.out.println("Before scrolling first cell index: " + flow.getFirstVisibleIndex());
    flow.scrollYBy(flow.getHeight());
    scene.addPostLayoutPulseListener( new WaitForScrollYPulse( scene ) );
});
private class WaitForScrollYPulse implements Runnable
{
    private Scene scene;
    private int pulseCount = 0;

    public WaitForScrollYPulse( Scene scene )
    {
        this.scene = scene;
    }

    @Override
    public void run()
    {
        if ( ++pulseCount > 1 )
        {
            scene.removePostLayoutPulseListener( this );
            System.out.println("After scrolling first cell index: " + flow.getFirstVisibleIndex());
        }
    }
}