JavaFX TreeView 恢复滚动状态

JavaFX TreeView restore Scroll State

我想保存 TreeView 组件的当前滚动状态并随时恢复该状态。

我尝试了很多方法来获取滚动位置并将其设置回去,但无论如何都做不到。

这里的重点是我们需要保存min,max,value,unitInc,blockInc的值,并在需要的时候恢复这些值。仅值或仅最小值和最大值不足以正确恢复。

更新

监听垂直值变化并存储滚动状态

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);
            }
        });
    }
}

这里也存储滚动状态

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;
}

恢复存储的滚动状态

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);

如果您只想滚动到 TreeView 中的索引 ,则可以使用 scrollTo 方法(例如,您保存选定的索引, 然后你稍后滚动到这个索引):

tree.scrollTo(5);

如果你真的想存储然后恢复ScrollBar的位置,你可以使用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来存储。

更新:

根据 OP 的发现,ScrollBarunitIncrementProperty and the blockIncrementProperty 也应该存储和恢复以正确支持操作。

例子

在示例中,我用一些虚拟数据填充了 TreeView。它包含一个 Button 来保存水平和垂直的位置 ScrollBar 到一个成员对象中,可以存储位置的最小值、最大值、值、blockIncrement 和 unitIncrement 值。它还有一个 Button 来恢复上次保存的滚动条位置。

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;
        }
    }
}

这可能对某些人有用。我基于 DVarga 的解决方案创建了一个更友好的状态对象。这个正在使用 TextArea。

public class ScrollBarState
{
  private final TextArea textArea;
  private final Orientation orientation;
  private ScrollBar scrollBar;

  private double min;
  private double max;
  private double value;
  private double blockIncrement;
  private double unitIncrement;

  // ---------------------------------------------------------------------------------//
  // constructor
  // ---------------------------------------------------------------------------------//

  public ScrollBarState (TextArea textArea, Orientation orientation)
  {
    this.textArea = textArea;
    this.orientation = orientation;
  }

  // ---------------------------------------------------------------------------------//
  // reset
  // ---------------------------------------------------------------------------------//

  public void reset ()
  {
    scrollBar = null;
  }

  // ---------------------------------------------------------------------------------//
  // save
  // ---------------------------------------------------------------------------------//

  public void save ()
  {
    if (scrollBar == null && !setScrollBar ())
      return;

    this.min = scrollBar.getMin ();
    this.max = scrollBar.getMax ();
    this.value = scrollBar.getValue ();
    this.blockIncrement = scrollBar.getBlockIncrement ();
    this.unitIncrement = scrollBar.getUnitIncrement ();
  }

  // ---------------------------------------------------------------------------------//
  // restore
  // ---------------------------------------------------------------------------------//

  public void restore ()
  {
    if (scrollBar == null)
      return;

    scrollBar.setMin (min);
    scrollBar.setMax (max);
    scrollBar.setValue (value);
    scrollBar.setUnitIncrement (unitIncrement);
    scrollBar.setBlockIncrement (blockIncrement);
  }

  // ---------------------------------------------------------------------------------//
  // setScrollBar
  // ---------------------------------------------------------------------------------//

  private boolean setScrollBar ()
  {
    for (Node node : textArea.lookupAll (".scroll-bar"))
      if (node instanceof ScrollBar
          && ((ScrollBar) node).getOrientation ().equals (orientation))
      {
        scrollBar = (ScrollBar) node;
        return true;
      }
    return false;
  }
}