JavaFX:计算滚动到特定控件的位置

JavaFX: Calculate position to scroll to for specific control

我需要计算控件相对于滚动窗格内容高度的确切位置,以便能够设置滚动窗格的 vValue,但我的公式似乎还不完全正确。

我创建了一个带有滚动窗格的场景,它由 table 内容和许多部分组成,而标签作为部分 headers:

Table 的内容

  1. 第 1 部分
  2. 第 2 部分

第 1 部分:

Lorem Ipsum...

第 2 部分:

Lorem Ipsum...

我使用 onMouseReleased 事件来处理鼠标点击内容 table 中的部分标签。现在我想通过设置滚动窗格的 vValue 自动向下滚动到相关部分标签(变量 'control')。

我通过以下逻辑计算控件的滚动位置:

scrollPane.setVvalue(0.0); // Reset previous vValue to start from the top
Bounds uberPaneBounds = uberPane.localToScene(uberPane.getBoundsInLocal());
Bounds controlBounds = control.localToScene(control.getBoundsInLocal());
double vValue = controlBounds.getMaxY() / uberPaneBounds.getMaxY();

uberPane 是一个 VBox,直接位于 scrollpane 之下,包括不同的窗格。因此,部分标签嵌套在不同层级的不同窗格中。

不幸的是,vValue 并不完全正确,并且相关部分在滚动窗格中越往下越不正确。似乎有一个附加参数随着高度的增加而增加。我做了 10 个部分的测试(包括 ToC):

感谢任何帮助!

滚动窗格无法滚动到内容底部高于滚动窗格视口底部的位置。因此,您可以滚动的最大值是滚动窗格视口中节点的高度减去视口本身的高度。

另请注意,您假设滚动值的范围从零到最大滚动偏移量(以像素为单位)。这可能是正确的,除非您明确更改它,但实际上您应该将您想要的值线性插入到滚动窗格的 vminvmax 属性定义的范围内。

假设 control 是滚动窗格中显示的节点的子节点,您需要如下内容:

double y = control.getBoundsInParent().getMinY();
double scrollRange = scrollPane.getVmax() - scrollPane.getVmin() ;
double scrollPixelRange = scrollPane.getContent().getBoundsInLocal().getHeight()
    - scrollPane.getViewportBounds().getHeight();
double scrollOffset = y*scrollRange / scrollPixelRange ;
scrollPane.setVvalue(scrollPane.getVmin() + scrollOffset);

这是一个完整的例子:

import java.util.Random;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;


/**
 * JavaFX App
 */
public class App extends Application {
    
    private final Random rng = new Random();

    @Override
    public void start(Stage stage) {
        VBox content = new VBox(5);
        VBox tableOfContents = new VBox(5);
        content.getChildren().add(tableOfContents);
        
        ScrollPane scrollPane = new ScrollPane(content);

        for (int i = 1 ; i <=5 ; i++) {
            createSection("Section "+i, scrollPane, content, tableOfContents);
        }
        
        Scene scene = new Scene(scrollPane);
        stage.setScene(scene);
        stage.show();
    }
    
    private void createSection(String title, ScrollPane scrollPane, Pane content, Pane tableOfContents) {
        Label sectionLabel = new Label(title);
        Hyperlink link = new Hyperlink(title);
        link.setOnAction(e -> {
            double y = sectionLabel.getBoundsInParent().getMinY();
            double scrollRange = scrollPane.getVmax() - scrollPane.getVmin() ;
            double scrollPixelRange = scrollPane.getContent().getBoundsInLocal().getHeight()
                - scrollPane.getViewportBounds().getHeight();
            double scrollOffset = y*scrollRange / scrollPixelRange ;
            scrollPane.setVvalue(scrollPane.getVmin() + scrollOffset);
        });
        Hyperlink returnToTop = new Hyperlink("Return to top");
        returnToTop.setOnAction(e -> scrollPane.setVvalue(scrollPane.getVmin()));
        Pane section = new Pane();
        section.setPrefWidth(400);
        section.setPrefHeight(200+rng.nextInt(400));
        section.setBackground(new Background(new BackgroundFill(Color.LIGHTBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
        tableOfContents.getChildren().add(link);
        content.getChildren().addAll(sectionLabel, section, returnToTop);
    }
    

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

}

如果您需要滚动到不是 scollpane 内容的直接子控件,您可以使用

修改它
double y = scrollPane.getContent().sceneToLocal(
    control.localToScene(control.getBoundsInLocal())).getMinY();

(这会将控件的局部坐标转换为场景坐标,然后将这些坐标转换为滚动窗格内容的坐标。)