Search code examples
javajavafxscrollbarjavafx-8

JavaFX TreeView restore Scroll State


I want to save current scroll state of a TreeView component and restore that state any time.

I tried many things to get scroll position and set it back but couldn't do it in any way.

The key point here is that we need to save min,max,value,unitInc,blockInc values and restore these values when needed. Only value or only min and max is not enough to restore correctly.

Update

Listen vertical value changes and store scroll state

Set<Node> nodes = this.treeView.lookupAll(".scroll-bar");
for (Node node : nodes) {
    ScrollBar scrollBar = getScrollBar(node);
    if (scrollBar.getOrientation() == Orientation.VERTICAL) {
        scrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
            double value = newValue.doubleValue();
            if (value > 0) {
                scrollState.setMin(scrollBar.getMin());
                scrollState.setMax(scrollBar.getMax());
                scrollState.setUnitIncrement(scrollBar.getUnitIncrement());
                scrollState.setBlockIncrement(scrollBar.getBlockIncrement());
                scrollState.setVerticalValue(value);
            }
        });
    }
}

Store scroll state here also

private ScrollBar getScrollBar(Node node) {
    ScrollBar scrollBar = (ScrollBar) node;
    if (Objects.nonNull(scrollBar)) {
        scrollState.setMin(scrollBar.getMin());
        scrollState.setMax(scrollBar.getMax());
        scrollState.setUnitIncrement(scrollBar.getUnitIncrement());
        scrollState.setBlockIncrement(scrollBar.getBlockIncrement());
    }

    return scrollBar;
}

Restore stored scroll state

threadService.schedule(() -> { // run after some ms, it is important
    threadService.runActionLater(() -> { // run in ui thread

        Set<Node> nodes = this.treeView.lookupAll(".scroll-bar");
        for (Node node : nodes) {
            ScrollBar scrollBar = getScrollBar(node);
            if (scrollBar.getOrientation() == Orientation.VERTICAL) {
                double verticalValue = scrollState.getVerticalValue();
                if (verticalValue > 0) {
                    scrollBar.setMin(scrollState.getMin());
                    scrollBar.setMax(scrollState.getMax());
                    scrollBar.setUnitIncrement(scrollState.getUnitIncrement());
                    scrollBar.setBlockIncrement(scrollState.getBlockIncrement());
                    scrollBar.setValue(verticalValue);
                }
            }
        }
    }, true);
}, 50, TimeUnit.MILLISECONDS);

Solution

  • If you want to just scroll to an index in the TreeView, then you can use the scrollTo method (e.g. you save the selected index, then you sroll to this index later):

    tree.scrollTo(5);
    

    In the case if you actually want to store and then restore the positions of the ScrollBars, you can use the lookupAll method of Node class to look for .scroll-bar style then to case it to ScrollBar object which has a minProperty, maxProperty and valueProperty to store.

    Update:

    Based on OP's finding, the unitIncrementProperty and the blockIncrementProperty of the ScrollBar also should be stored and restored to properly support the operation.

    Example

    In the example I have filled a TreeView with some dummy data. It contains a Button to save the position of the horizontal and the vertical ScrollBar into a member object, that able to store the min, max, value, blockIncrement and unitIncrement values of the position. It also has a Button to restore the last saved scrollbar-positions.

    public class TreeViewSample extends Application {
    
        // Members to store the horizontal and vertical positions
        private ScrollBarState hScrollBarState = null;
        private ScrollBarState vScrollBarState = null;
    
        TreeView<String> tree;
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
            primaryStage.setTitle("Tree View Sample with scrollbars");
    
            TreeItem<String> rootItem = new TreeItem<String>("Items");
            rootItem.setExpanded(true);
            for (int i = 1; i < 30; i++) {
                TreeItem<String> item = new TreeItem<String>("Long long long long long long long Item" + i);
                rootItem.getChildren().add(item);
            }
    
            tree = new TreeView<String>(rootItem);
            VBox root = new VBox();
    
    
            Button buttonSave = new Button("Save ScrollBars!");
            buttonSave.setOnAction((event) -> {
                // On save get the scrollbars and update the members (if the scrollbar is present)
                ScrollBar bar = getScrollBar(tree, Orientation.HORIZONTAL);
                if (bar != null)
                    hScrollBarState = new ScrollBarState(bar.getMin(), bar.getMax(), bar.getValue(), bar.getBlockIncrement(), bar.getUnitIncrement());
    
                bar = getScrollBar(tree, Orientation.VERTICAL);
    
                if (bar != null)
                    vScrollBarState = new ScrollBarState(bar.getMin(), bar.getMax(), bar.getValue(), bar.getBlockIncrement(), bar.getUnitIncrement());
    
            });
    
            Button buttonRestore = new Button("Restore ScrollBars!");
            buttonRestore.setOnAction((event) -> {
    
                restoreScrollBarPositions(getScrollBar(tree, Orientation.HORIZONTAL), hScrollBarState);
                restoreScrollBarPositions(getScrollBar(tree, Orientation.VERTICAL), vScrollBarState);
            });
    
            root.getChildren().addAll(tree, buttonSave, buttonRestore);
    
            primaryStage.setScene(new Scene(root, 300, 250));
            primaryStage.show();
        }
    
        private ScrollBar getScrollBar(TreeView<?> tree, Orientation orientation) {
            // Get the ScrollBar with the given Orientation using lookupAll
            for (Node n : tree.lookupAll(".scroll-bar")) {
                if (n instanceof ScrollBar) {
                    ScrollBar bar = (ScrollBar) n;
    
                    if (bar.getOrientation().equals(orientation))
                        return bar;
                }
            }
            return null;
        }
    
        private void restoreScrollBarPositions(ScrollBar bar, ScrollBarState state) {
            // Set back the position values if they present
            if (bar != null && state != null) {
                bar.setMin(state.min);
                bar.setMax(state.max);
                bar.setValue(state.value);
                bar.setUnitIncrement(state.unitIncrement);
                bar.setBlockIncrement(state.blockIncrement);
            }
        }
    
        // Simple class to store the position values
        class ScrollBarState {
            double min;
            double max;
            double value;
            double blockIncrement;
            double unitIncrement;
    
            ScrollBarState(double min, double max, double value, double blockInc, double unitInc) {
                this.min = min;
                this.max = max;
                this.value = value;
                this.blockIncrement = blockInc;
                this.unitIncrement = unitInc;
            }
        }
    }