2017-12-20 2 views
12

ich eine Website auf einem JavaFX WebView Laden und nach eine Weile einen Screenshot mit so etwas wie unter:Wie mache ich JavaFX's Webview rendern, auch wenn es nicht sichtbar ist, weil es in einem anderen Tab ist?

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

Wenn ich an diesem WebView suchen, funktioniert gut, aber wenn es versteckt ist, indem sie in einer Registerkarte, die nicht im Vordergrund ist (ich bin nicht sicher über andere Fälle von es zu verbergen), dann ist der Screenshot der entsprechenden Website, aber völlig leer.

Wie kann ich die WebView zwingen zu rendern, auch wenn nicht sichtbar?

Während dieser Zeit ist webView.isVisible() wahr.

Ich fand es ein Verfahren in WebViewisTreeReallyVisible() genannt und enthält zur Zeit:

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

Wenn die WebView, indem sie in einem nicht-Vordergrund Registerkarte ausgeblendet ist, ist impl_isTreeVisible() false (alle anderen Faktoren in der return-Anweisung sind wahr). Das Verfahren ist auf Node und sieht wie folgt aus:

/** 
* @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; 
} 

ich impl_treeVisibleProperty() außer Kraft gesetzt hätte meine eigene Implementierung zur Verfügung zu stellen, aber WebView ist endgültig, so kann ich nicht von ihm erben.

Eine andere völlig andere Situation als minimiert (ikonifiziert) oder auf einer versteckten Registerkarte ist die Bühne vollständig ausgeblendet (wie in der Taskleiste). In diesem Modus ändert sich die Größe der WebView nicht, selbst wenn das Rendern möglich ist. Ich rufe webView.resize() an und mache dann einen Screenshot und der Screenshot hat die passende Größe, aber die tatsächlich gerenderte Seite ist von welcher Größe auch immer die WebView vorher war.

Debuggen dieses Schlichte Verhalten in gezeigt und versteckten Stufen, fand ich, dass wir schließlich zu Node.addToSceneDirtyList() erhalten, die enthält:

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

Wenn im verborgenen Modus, getScene() kehren null, im Gegensatz zu, was passiert, wenn es Show zu sein. Das heißt, dass s.addToDirtyList(this) nie aufgerufen wird. Ich bin mir nicht sicher, ob dies der Grund ist, warum es nicht richtig skaliert wird.

Hier ist ein Fehler, ein sehr alter, hier: https://bugs.openjdk.java.net/browse/JDK-8087569 aber ich denke nicht, dass das das ganze Problem ist.

Ich mache das mit Java 1.8.0_151. Ich habe versucht 9.0.1 zu sehen, ob es sich anders verhalten würde, da ich weiß, dass WebKit aktualisiert wurde, aber nein, es ist das gleiche.

+0

Ich erinnere mich nicht, wie die Skin für ein Registerkartenfenster implementiert ist, aber es ist möglich, dass sich die Webansicht in dem Szenario, das Sie beschreiben, nicht in einer Szene befindet. In diesem Fall funktioniert der Schnappschuss nicht (Sie können die Web-Ansicht jedoch vorübergehend zu einer Szene hinzufügen, während Sie den Schnappschuss machen). Wenn es immer noch Teil einer Szene ist, habe ich keine Ahnung ... –

+3

@James_D hat keine Ahnung ... Doomsday. – Mordechai

+1

sehen Sie es, wenn seine Hilfe https://stackoverflow.com/questions/40642573/trying-to-render-javafx-webview-to-offs-buffer-or-fbo – AsifAli72090

Antwort

-1

halte ich würde mit diesem Befehl Seite im geeigneten Moment Nachladen:

webView.getEngine().reload(); 

Versuchen Sie auch Parameter SnapshotParameters in Methode snapShot

zu ändern, wenn es nicht dann würde ich arbeiten würde betrachten Bild in Speicher wenn WebView auf dem Bildschirm gerendert wird.

+0

Was ist der geeignete Moment? Die WebView wird möglicherweise nie auf dem Bildschirm angezeigt. – Pablo

3

Reproduzieren Pablos Problem hier: https://github.com/johanwitters/stackoverflow-javafx-webview.

Pablo schlug vor, WebView zu überschreiben und einige Methoden anzupassen. Das funktioniert nicht, da es sich um eine letzte Klasse und ein privates Mitglied handelt. Als Alternative habe ich javassist verwendet, um eine Methode umzubenennen und den Code durch den Code zu ersetzen, den ich ausführen möchte. Ich habe den Inhalt der Methode handleStagePulse "ersetzt", wie unten gezeigt.

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

Dieses Snippet wird vom WebViewSample aufgerufen, das 2 Registerkarten öffnet. Eine mit einer "Schnappschuss" -Taste, eine andere mit der WebView. Wie Pablo darauf hingewiesen hat, muss die Registerkarte mit dem WebView die zweite Registerkarte sein, um reproduzieren zu können.

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

Ich habe nicht bei der Festsetzung Pablos Problems gelungen, aber hoffentlich der Vorschlag Javassist zu verwenden könnte helfen.

Ich bin sicher: Fortsetzung folgt ...

+0

Anders als oben, möchte ich hinzufügen, dass, wenn Sie diese endgültige Methode überschreiben möchten, hier ist eine Möglichkeit, dies zu tun: https://StackOverflow.com/Questions/748362/Override-JavaFinal-Methods -Via-Reflection-oder-andere-Mittel –

+0

Natürlich ....... Die Web-Ansicht wird nicht ausgelagert, wenn sie nicht sichtbar ist. Haben Sie versucht, den Browser in den zweiten Tab zu setzen, und wählen Sie ihn nie aus? Wird er trotzdem das Bild ausgeben? – Mordechai

+0

Ich habe Ihren Code noch nicht getestet, aber ein großer Unterschied zwischen Ihrem Beispiel und dem was ich tue, was ich in meiner Frage hätte erwähnen sollen, ist, dass ich den Tab nicht zeige, bevor ich den Screenshot mache. Wenn Sie eine Seite in einer Webansicht auf der zweiten Registerkarte laden und die erste Registerkarte die Schaltfläche haben und nie die zweite Registerkarte anzeigen, dann sind Sie näher an meiner Situation. – Pablo

-1

Wenn Sie durch logische Implementierung von Snapshot gehen, nur Dinge, die auf dem Bildschirm sichtbar sind, werden als Momentaufnahme gemacht. Zum Erstellen eines Schnappschusses der Webansicht können Sie ihn entweder automatisch anzeigen lassen, indem Sie auf die Registerkarte klicken, in der die Ansicht gerendert wird, bevor der Schnappschuss erstellt wird. Oder Sie können manuell auf die Registerkarte klicken und den Screenshot erstellen. Ich denke, dass keine API erlaubt, Schnappschuss von versteckten Teil zu machen, wie es logisch das Konzept von versteckten Dingen voilate wird. Der Code zum Aufnehmen eines Schnappschusses ist bereits verfügbar. Sie können klicken, oder laden Sie die Registerkarte wie:

Sie können eine AuswahlChangedListener hinzufügen oder Sie können die Last direkt vor dem Snapshot ausführen.

+0

können Sie https://stackoverflow.com/help/how-to-answer für mehr Verständnis überprüfen. –

+0

Ich denke, alle Details, die als eine klare Antwort erwartet werden, sind da.Seien Sie spezifisch, bevor Sie auf einen Kommentar zeigen. –

+0

Ihre Behauptung, dass Sie verborgene Elemente nicht erfassen können, ist falsch. Sie können sogar erfassen, wenn es sich nicht in einer Szene befindet (es sei denn, die sichtbare Eigenschaft ist explizit auf "false" gesetzt). Web-Ansicht aus irgendeinem Grund ist hier eine Ausnahme und das ist das Problem. – Mordechai

Verwandte Themen