我在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已升级,但不是,它是相同的。
答案 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) {
}
}