如何 set/remember JavaFX 8 WebView 中的滚动条缩略图位置?

How to set/remember scrollbar thumb position in JavaFX 8 WebView?

我对 JavaFX 8 WebView 及其滚动条有疑问。

我想重新加载 HTML 文档并记住当前的垂直滚动条位置。不幸的是,每当我重新加载文档时,滚动条都会移动到其初始位置。如何强制滚动条记住它的位置?

我解决这个问题的尝试:

通过 WebView#lookup(String id)(参见 1)可以访问 WebView 实例的垂直和水平 ScrollBar 实例。这使我能够掌握当前的滚动条值,即通过 ScrollBar#getValue() 的拇指位置。不幸的是,通过 ScrollBar#setValue(double value) 设置值并没有达到预期的效果。滚动条滑块位置保持不变。

此外,我怀疑未呈现 JavaFX 滚动条。相反,似乎显示了具有类似样式的 WebKit 滚动条。至少通过 Scenic View 的简短场景图分析给我这样的印象。

这是 JavaFX 错误吗?有没有办法实现所需的行为?

这里有一个演示 setValue() 问题的示例程序:

import java.util.Set;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

/**
 * ScrollBar bug in JavaFX 8 WebView?
 * 
 * @author Michael Hoffer <info@michaelhoffer.de>
 */
public class WebViewAndItsScrollBars extends Application {

    @Override
    public void start(Stage primaryStage) {

        // create webview and load content
        WebView view = new WebView();
        view.getEngine().load(
                "https://docs.oracle.com/javase/8/javafx/"
                + "api/javafx/scene/control/ScrollBar.html");

        // vertical scrollbar of the webview
        ScrollBar vScrollBar = getVScrollBar(view);

        // change scrollbar value, i.e., thumb position via button
        Button btn = new Button();
        btn.setText("Move ScrollBar");
        btn.setOnAction((ActionEvent event) -> {
            if (vScrollBar != null) {
                double value = 2000;
                System.out.println(">> current value: " + vScrollBar.getValue());
                System.out.println(">> setting scrollbar value to " + value);
                vScrollBar.setValue(value);
            }
        });

        // create root layout
        VBox root = new VBox();
        root.setAlignment(Pos.CENTER);
        root.getChildren().add(view);
        root.getChildren().add(btn);

        // setup and show stage
        Scene scene = new Scene(root, 1024, 600);
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    /**
     * Returns the vertical scrollbar of the webview.
     *
     * @param webView webview
     * @return vertical scrollbar of the webview or {@code null} if no vertical
     * scrollbar exists
     */
    private ScrollBar getVScrollBar(WebView webView) {

        Set<Node> scrolls = webView.lookupAll(".scroll-bar");
        for (Node scrollNode : scrolls) {
            if (ScrollBar.class.isInstance(scrollNode)) {
                ScrollBar scroll = (ScrollBar) scrollNode;
                if (scroll.getOrientation() == Orientation.VERTICAL) {
                    return scroll;
                }
            }
        }
        return null;
    }
}

相关问题:How to hide scrollbars in the JavaFX WebView

我找到了解决方案:

可以通过使用 JavaScript 实现所需的行为。大多数代码示例通过 WebView#getEngine()#loadContent(String content) 将 JavaScript 代码直接注入到内容中。但是,对于 URL,这不起作用。但是网络引擎提供了直接JavaScript执行的方法:

webView.getEngine().executeScript("// javascript code");

以下是我用来控制滚动条的方法:

    /**
     * Scrolls to the specified position.
     * @param view web view that shall be scrolled
     * @param x horizontal scroll value
     * @param y vertical scroll value
     */
    public void scrollTo(WebView view, int x, int y) {
        view.getEngine().executeScript("window.scrollTo(" + x + ", " + y + ")");
    }

    /**
     * Returns the vertical scroll value, i.e. thumb position.
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getValue().
     * @param view
     * @return vertical scroll value
     */
    public int getVScrollValue(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollTop");
    }

    /**
     * Returns the horizontal scroll value, i.e. thumb position.
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getValue()}.
     * @param view
     * @return horizontal scroll value
     */
    public int getHScrollValue(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollLeft");
    }

    /**
     * Returns the maximum vertical scroll value. 
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getMax()}.
     * @param view 
     * @return vertical scroll max
     */
    public int getVScrollMax(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollWidth");
    }

    /**
     * Returns the maximum horizontal scroll value. 
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getMax()}.
     * @param view 
     * @return horizontal scroll max
     */
    public int getHScrollMax(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollHeight");
    }

这是问题代码的工作版本:

import java.util.Set;
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

/**
 * ScrollBar bug in JavaFX 8 WebView?
 *
 * @author Michael Hoffer &lt;info@michaelhoffer.de&gt;
 */
public class WebViewAndItsScrollBars extends Application {

    @Override
    public void start(Stage primaryStage) {

        // create webview and load content
        WebView view = new WebView();

        view.getEngine().load(
                "https://docs.oracle.com/javase/8/javafx/"
                + "api/javafx/scene/control/ScrollBar.html");

        // change scrollbar value, i.e., thumb position via button
        Button btn = new Button();
        btn.setText("Move ScrollBar");
        btn.setOnAction((ActionEvent event) -> {

            int value = 2000;
            System.out.println(">> current value: " + getVScrollValue(view));
            System.out.println(">> setting scrollbar value to " + value);
            scrollTo(view, 0, value);

        });

        // create root layout
        VBox root = new VBox();
        root.setAlignment(Pos.CENTER);
        root.getChildren().add(view);
        root.getChildren().add(btn);

        // setup and show stage
        Scene scene = new Scene(root, 1024, 600);
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    /**
     * Scrolls to the specified position.
     * @param view web view that shall be scrolled
     * @param x horizontal scroll value
     * @param y vertical scroll value
     */
    public void scrollTo(WebView view, int x, int y) {
        view.getEngine().executeScript("window.scrollTo(" + x + ", " + y + ")");
    }

    /**
     * Returns the vertical scroll value, i.e. thumb position.
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getValue().
     * @param view
     * @return vertical scroll value
     */
    public int getVScrollValue(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollTop");
    }

    /**
     * Returns the horizontal scroll value, i.e. thumb position.
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getValue()}.
     * @param view
     * @return horizontal scroll value
     */
    public int getHScrollValue(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollLeft");
    }

    /**
     * Returns the maximum vertical scroll value. 
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getMax()}.
     * @param view 
     * @return vertical scroll max
     */
    public int getVScrollMax(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollWidth");
    }

    /**
     * Returns the maximum horizontal scroll value. 
     * This is equivalent to {@link javafx.scene.control.ScrollBar#getMax()}.
     * @param view 
     * @return horizontal scroll max
     */
    public int getHScrollMax(WebView view) {
        return (Integer) view.getEngine().executeScript("document.body.scrollHeight");
    }
}