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

我正在JavaFX WebView上加载一个网站,过了一段时间用以下内容截取屏幕截图:

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

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

即使不可见,如何强制WebView呈现?

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

我发现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是最终的,所以,我无法inheritance它。

最小化(图标化)或隐藏选项卡上的另一个完全不同的情况是让舞台完全隐藏(如在托盘栏中运行)。 在该模式下,即使我可以进行渲染, WebView也不会resize。 我调用webView.resize()然后截取屏幕截图,屏幕截图的大小合适,但实际呈现的页面大小与WebView之前的大小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) 。 我不确定这是不是因为它没有得到适当resize的原因。

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

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

在这里重现Pablo的问题: https : //github.com/johanwitters/stackoverflow-javafx-webview 。

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

 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(); } } } 

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

 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() { @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() { @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; } } 

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

我敢肯定:继续……

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

 webView.getEngine().reload(); 

还尝试在方法snapShot更改参数SnapshotParameters

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

如果您通过逻辑实现快照,则只有屏幕上可见的内容才会被视为快照。 要拍摄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) { } }