'JavaFX tableview auto scroll to selected item when pressing a button to selectNext() or selectPrevious()

I'm writing a JavaFX program with a TableView called 'table' and 2 buttons called 'previous' & 'next'.

Here is part of the code:

previous.setOnAction(event -> {
     table.getSelectionModel().selectPrevious();
});

next.setOnAction(event -> {
     table.getSelectionModel().selectNext();
});

However, if I keep pressing the buttons, the table will not scroll automatically to keep the selected item visible. So I modified the code like this :

previous.setOnAction(event -> {
     table.getSelectionModel().selectPrevious();
     table.scrollTo(table.getSelectionModel().getSelectedItem());
});

next.setOnAction(event -> {
     table.getSelectionModel().selectNext();
     table.scrollTo(table.getSelectionModel().getSelectedItem());
});

But it will always try to keep the selected item at the top of the visible region. If I keep pressing 'next'. The selected item will stay at the top instead of staying at the bottom.

I want to mimic the natural behavior of a tableview in the way that if I press up or down on the keyboard with something selected, the tableview will scroll automatically to keep the selected item visible.

How should I modify the code to make the auto scrolling more natural when I press the buttons?

Thanks



Solution 1:[1]

The problem is

  • missing fine-grained control of scrollTo target location on application level
  • the (somewhat unfortunate) implementation of virtualizedControl.scrollTo(index) which (ultimately) leads to calling flow.scrollToTop(index)

There's a long-standing RFE (reported 2014!) requesting better control from application code. Actually, VirtualFlow has public methods (scrollToTop, scrollTo, scrollPixels) providing such, only they are not passed on to the control layer (getVirtualFlow in VirtualContainerBase is final protected), so can't be overridden in a custom skin. Since fx12, we can hack a bit, and expose the onSelectXX of Tree/TableViewSkin and use those, either directly in application code (example below) or in a custom TableView.

Example code:

public class TableSelectNextKeepVisible extends Application {

    /**
     * Custom table skin to expose onSelectXX methods for application use.
     */
    public static class MyTableSkin<T> extends TableViewSkin<T> {

        public MyTableSkin(TableView<T> control) {
            super(control);
        }

        /**
         * Overridden to widen scope to public.
         */
        @Override
        public void onSelectBelowCell() {
            super.onSelectBelowCell();
        }

        /**
         * Overridden to widen scope to public.
         */
        @Override
        public void onSelectAboveCell() {
            super.onSelectAboveCell();
        }

    }
    
    private Parent createContent() {
        TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales())) {

            @Override
            protected Skin<?> createDefaultSkin() {
                return new MyTableSkin<>(this);
            }

        };

        TableColumn<Locale, String> country = new TableColumn<>("Column");
        country.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
        table.getColumns().addAll(country);

        Button next = new Button("next");
        next.setOnAction(e -> {
            table.getSelectionModel().selectNext();
            // scrolls to top
//            table.scrollTo(table.getSelectionModel().getSelectedIndex());
            ((MyTableSkin<?>) table.getSkin()).onSelectBelowCell();

        });

        Button previous = new Button("previous");
        previous.setOnAction(e -> {
            table.getSelectionModel().selectPrevious();
            // scrolls to top
//            table.scrollTo(table.getSelectionModel().getSelectedIndex());
            ((MyTableSkin<?>) table.getSkin()).onSelectAboveCell();

        });

        BorderPane content = new BorderPane(table);
        content.setBottom(new HBox(10, next, previous));
        return content;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.show();
    }

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

}

Solution 2:[2]

try using getSelectedIndex as follows instead of using getSelectedItem

previous.setOnAction(event -> {
     table.getSelectionModel().selectPrevious();
     table.scrollTo(table.getSelectionModel().getSelectedIndex());
});

Solution 3:[3]

Platform.runLater( () -> TABLE_NAME.scrollTo(TABLE_INFORMATION_LIST.getList().size()-index) ); should work if you call it whenever you add information to the table.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 kleopatra
Solution 2 Cesar Salas
Solution 3 ottlite