即使由于位于另一个选项卡中而不可见,我如何使JavaFX的webview渲染?

问题描述 投票:12回答:3

我正在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;
}

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

最小化(图标化)或隐藏选项卡上的另一个完全不同的情况是让舞台完全隐藏(如在托盘栏中运行)。在该模式下,即使我可以渲染,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,但我不认为这是整个问题。

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

java javafx webview
3个回答
4
投票

在这里再现巴勃罗的问题:s.addToDirtyList(this)

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

https://bugs.openjdk.java.net/browse/JDK-8087569

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

https://github.com/johanwitters/stackoverflow-javafx-webview

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

我敢肯定:继续......


-1
投票

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

public class WebViewChanges {
//    public static String MY_WEBVIEW_CLASSNAME = WebView.class.getName();

    public WebView newWebView() {
        createSubclass();
        return new WebView();
    }

    // https://www.ibm.com/developerworks/library/j-dyn0916/index.html
    boolean created = false;
    private void createSubclass() {
        if (created) return;
        created = true;
        try
        {
            String methodName = "handleStagePulse";

            // get the super class
            CtClass webViewClass = ClassPool.getDefault().get("javafx.scene.web.WebView");

            // get the method you want to override
            CtMethod handleStagePulseMethod = webViewClass.getDeclaredMethod(methodName);

            // Rename the previous handleStagePulse method
            String newName = methodName+"Old";
            handleStagePulseMethod.setName(newName);

            //  mnew.setBody(body.toString());
            CtMethod newMethod = CtNewMethod.copy(handleStagePulseMethod, methodName, webViewClass, null);
            String body = "{" +
                    "  " + Scene.class.getName() + ".impl_setAllowPGAccess(true);\n" +
                    "  " + "final " + NGWebView.class.getName() + " peer = impl_getPeer();\n" +
                    "  " + "peer.update(); // creates new render queues\n" +
//                    "  " + "if (page.isRepaintPending()) {\n" +
                    "  " + "   impl_markDirty(" + DirtyBits.class.getName() + ".WEBVIEW_VIEW);\n" +
//                    "  " + "}\n" +
                    "  " + Scene.class.getName() + ".impl_setAllowPGAccess(false);\n" +
                    "}\n";
            System.out.println(body);
            newMethod.setBody(body);
            webViewClass.addMethod(newMethod);


            CtMethod isTreeReallyVisibleMethod = webViewClass.getDeclaredMethod("isTreeReallyVisible");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

还尝试在方法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; } } 中更改参数webView.getEngine().reload();

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


-1
投票

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

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

SnapshotParameters
© www.soinside.com 2019 - 2024. All rights reserved.