Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a question regarding the JavaFX 8 WebView and its scrollbars.

I want to reload an HTML document and remember the current vertical scrollbar position. Unfortunately, the scrollbar moves to its initial position whenever I reload the document. How can I force the scrollbar to remember its position?

My attempts to solve this problem:

Via WebView#lookup(String id) (see 1) it is possible to access the vertical and horizontal ScrollBar instances of a WebView instance. This allows me to get hold of the current scrollbar value, i.e. thumb position via ScrollBar#getValue(). Unfortunately, setting the value via ScrollBar#setValue(double value) does not have the desired effect. The scrollbar thumb position remains unchanged.

Furthermore, I suspect that the JavaFX scrollbars are not rendered. Instead it seems that a WebKit scrollbar with similar style is shown instead. At least a brief scene graph analysis via Scenic View gives me this impression.

Is this a JavaFX bug? Is there a way to achieve the desired behavior?

Here is a sample program that demonstrates the setValue()-problem:

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 <[email protected]>
 */
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;
    }
}

Related question: How to hide scrollbars in the JavaFX WebView

like image 539
miho Avatar asked Jul 07 '15 09:07

miho


1 Answers

I found the solution:

The desired behavior can be achieved by using JavaScript. Most code examples inject the JavaScript code directly into the content, via WebView#getEngine()#loadContent(String content). However, For URLs this doesn't work. But the web-engine provides a method for direct JavaScript execution:

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

Here are the methods that I use for controlling the scrollbars:

    /**
     * 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");
    }

Here is a working version of the code from the question:

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;[email protected]&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");
    }
}
like image 70
miho Avatar answered Sep 28 '22 09:09

miho