即使因为在另一个标签中不可见,我如何使JavaFX的webview渲染?

时间:2017-12-20 21:20:34

标签: java javafx webview

我在JavaFX WebView上加载网站,并在一段时间后截取屏幕截图如下:

WritableImage image = webView.snapshot(null, null);

如果我正在查看工作正常的WebView,但是如果它被隐藏在不在前台的选项卡中(我不确定其他隐藏它的情况) ),然后,截图是适当的网站,但完全空白。

即使不可见,如何强制WebView渲染?

在此期间,webView.isVisible()为真。

我发现WebView中的方法名为isTreeReallyVisible(),目前包含:

private boolean isTreeReallyVisible() {
    if (getScene() == null) {
        return false;
    }

    final Window window = getScene().getWindow();

    if (window == null) {
        return false;
    }

    boolean iconified = (window instanceof Stage) ? ((Stage)window).isIconified() : false;

    return impl_isTreeVisible()
           && window.isShowing()
           && window.getWidth() > 0
           && window.getHeight() > 0
           && !iconified;
}

当WebView被隐藏在非前景标签中时,impl_isTreeVisible()为false(return语句中的所有其他因素都为true)。该方法在Node上,如下所示:

/**
 * @treatAsPrivate implementation detail
 * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 */
@Deprecated
public final boolean impl_isTreeVisible() {
    return impl_treeVisibleProperty().get();
}

/**
 * @treatAsPrivate implementation detail
 * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 */
@Deprecated
protected final BooleanExpression impl_treeVisibleProperty() {
    if (treeVisibleRO == null) {
        treeVisibleRO = new TreeVisiblePropertyReadOnly();
    }
    return treeVisibleRO;
}

我可以覆盖impl_treeVisibleProperty()来提供我自己的实现,但是WebView是最终的,所以,我不能继承它。

最小化(图标化)或隐藏选项卡上的另一个完全不同的情况是让舞台完全隐藏(如在托盘栏中运行)。在该模式下,即使我可以进行渲染,WebView也不会调整大小。我调用webView.resize()然后截取屏幕截图,屏幕截图的大小合适,但实际呈现的页面的大小与WebView之前的大小无关。

在显示和隐藏的阶段调试此大小调整行为,我发现最终我们到达Node.addToSceneDirtyList()包含:

private void addToSceneDirtyList() {
    Scene s = getScene();
    if (s != null) {
        s.addToDirtyList(this);
        if (getSubScene() != null) {
            getSubScene().setDirty(this);
        }
    }
}

在隐藏模式下,getScene()会返回null,与正在显示的内容不同。这意味着永远不会调用s.addToDirtyList(this)。我不确定这是不是因为它没有得到适当调整大小的原因。

有一个关于这个的错误,一个非常古老的错误,在这里:https://bugs.openjdk.java.net/browse/JDK-8087569但我不认为这是整个问题。

我是用Java 1.8.0_151做的。我尝试了9.0.1以查看它是否会表现不同,因为我的理解是WebKit已升级,但不是,它是相同的。

3 个答案:

答案 0 :(得分:4)

在此处重现Pablo的问题:https://github.com/johanwitters/stackoverflow-javafx-webview

Pablo建议覆盖WebView并调整一些方法。鉴于它是最终类和私人成员,这不起作用。作为替代方案,我使用javassist重命名方法,并用我希望它执行的代码替换代码。我已经“替换”了方法handleStagePulse的内容,如下所示。

package com.johanw.stackoverflow;

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

import javax.imageio.ImageIO;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;


public class WebViewSample extends Application {
    private Scene scene;
    private TheBrowser theBrowser;

    private void setLabel(Label label) {
        label.setText("" + theBrowser.browser.isVisible());
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Tabs");
        Group root = new Group();
        Scene scene = new Scene(root, 400, 250, Color.WHITE);

        TabPane tabPane = new TabPane();

        BorderPane borderPane = new BorderPane();

        theBrowser = new TheBrowser();
        {
            Tab tab = new Tab();
            tab.setText("Other tab");

            HBox hbox0 = new HBox();
            {
                Button button = new Button("Screenshot");
                button.addEventHandler(MouseEvent.MOUSE_PRESSED,
                        new EventHandler<MouseEvent>() {
                            @Override
                            public void handle(MouseEvent e) {
                                WritableImage image = theBrowser.getBrowser().snapshot(null, null);
                                File file = new File("test.png");
                                RenderedImage renderedImage = SwingFXUtils.fromFXImage(image, null);
                                try {
                                    ImageIO.write(
                                            renderedImage,
                                            "png",
                                            file);
                                } catch (IOException e1) {
                                    e1.printStackTrace();
                                }

                            }
                        });
                hbox0.getChildren().add(button);
                hbox0.setAlignment(Pos.CENTER);
            }

            HBox hbox1 = new HBox();
            Label visibleLabel = new Label("");
            {
                hbox1.getChildren().add(new Label("webView.isVisible() = "));
                hbox1.getChildren().add(visibleLabel);
                hbox1.setAlignment(Pos.CENTER);
                setLabel(visibleLabel);
            }
            HBox hbox2 = new HBox();
            {
                Button button = new Button("Refresh");
                button.addEventHandler(MouseEvent.MOUSE_PRESSED,
                        new EventHandler<MouseEvent>() {
                            @Override
                            public void handle(MouseEvent e) {
                                setLabel(visibleLabel);
                            }
                        });
                hbox2.getChildren().add(button);
                hbox2.setAlignment(Pos.CENTER);
            }
            VBox vbox = new VBox();
            vbox.getChildren().addAll(hbox0);
            vbox.getChildren().addAll(hbox1);
            vbox.getChildren().addAll(hbox2);
            tab.setContent(vbox);
            tabPane.getTabs().add(tab);
        }
        {
            Tab tab = new Tab();
            tab.setText("Browser tab");
            HBox hbox = new HBox();
            hbox.getChildren().add(theBrowser);
            hbox.setAlignment(Pos.CENTER);
            tab.setContent(hbox);
            tabPane.getTabs().add(tab);
        }

        // bind to take available space
        borderPane.prefHeightProperty().bind(scene.heightProperty());
        borderPane.prefWidthProperty().bind(scene.widthProperty());

        borderPane.setCenter(tabPane);
        root.getChildren().add(borderPane);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

class TheBrowser extends Region {

    final WebView browser;
    final WebEngine webEngine;

    public TheBrowser() {
        browser = new WebViewChanges().newWebView();
        webEngine = browser.getEngine();
        getStyleClass().add("browser");
        webEngine.load("http://www.google.com");
        getChildren().add(browser);

    }
    private Node createSpacer() {
        Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        return spacer;
    }

    @Override protected void layoutChildren() {
        double w = getWidth();
        double h = getHeight();
        layoutInArea(browser,0,0,w,h,0, HPos.CENTER, VPos.CENTER);
    }

    @Override protected double computePrefWidth(double height) {
        return 750;
    }

    @Override protected double computePrefHeight(double width) {
        return 500;
    }

    public WebView getBrowser() {
        return browser;
    }

    public WebEngine getWebEngine() {
        return webEngine;
    }
}

从WebViewSample调用此代码段,打开2个选项卡。一个带有“快照”按钮,另一个带有WebView。正如Pablo指出的那样,带有WebView的选项卡需要成为第二个能够重现的选项卡。

;; https://blog.aaronbieber.com/2015/05/24/from-vim-to-emacs-in-
fourteen-days.html 

(require 'package)

(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/"))
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" .  "http://stable.melpa.org/packages/"))

(setq package-enable-at-startup nil)
(package-initialize)

我没有成功解决Pablo的问题,但希望使用javassist的建议可能有所帮助。

我确定:继续......

答案 1 :(得分:-1)

我会考虑使用此命令在适当的时刻重新加载页面:

webView.getEngine().reload();

还尝试更改方法SnapshotParameters

中的参数snapShot

如果它不起作用,那么我会考虑在屏幕上呈现WebView时将Image存储在内存中。

答案 2 :(得分:-1)

如果您使用快照的逻辑实现,则只有屏幕上可见的内容才会被视为快照。 要获取Web视图的快照,您可以在拍摄快照之前单击显示视图的选项卡,使其自动显示。或者您可以手动单击选项卡并截取屏幕截图。 我认为没有API允许隐藏部分的快照,因为逻辑上它会消除隐藏事物的概念。 您已经可以使用快照代码了。您可以单击或加载选项卡,如:

您可以添加selectionChangedListener,也可以在快照之前进行加载。

addItemTab.setOnSelectionChanged(event -> loadTabBasedFXML(addItemTab, "/view/AddItem.fxml"));

private void loadTabBasedFXML(Tab tab, String fxmlPath) {
        try {
            AnchorPane anchorPane = FXMLLoader.load(this.getClass().getResource(fxmlPath));
            tab.setContent(anchorPane);
        } catch (IOException e) {
        }
    }