我正在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已升级,但不是,它是相同的。
在这里再现巴勃罗的问题: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的建议可能有所帮助。
我敢肯定:继续......
我会考虑使用此命令在适当的时候重新加载页面:
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存储在内存中。
如果您通过逻辑实现快照,则只有屏幕上可见的内容才会被视为快照。要获取Web视图的快照,您可以在拍摄快照之前单击显示视图的选项卡,使其自动显示。或者您可以手动单击选项卡并截取屏幕截图。我认为没有API允许隐藏部分的快照,因为逻辑上它会消除隐藏事物的概念。您已经可以使用快照代码了。您可以单击,或加载选项卡,如:
您可以添加selectionChangedListener,也可以在快照之前执行加载。
SnapshotParameters