2017-12-20 1 views
12

는 내가 자바 FX의 웹보기와 같은과 스크린 샷을 복용 잠시 후 웹 사이트를로드 해요 :다른 탭에 있기 때문에 표시되지 않더라도 JavaFX의 웹보기 렌더링을 어떻게 만듭니 까?

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

내가 잘 작동하는 웹보기에서 찾고 있다면, 그러나 그것은 존재에 의해 숨겨진 경우 포어 그라운드에 있지 않은 탭에서 (나는 그것을 숨기는 다른 경우에 대해 잘 모르겠다), 스크린 샷은 적절한 사이트이지만 전체적으로 비어 있습니다.

표시되지 않더라도 WebView를 렌더링하려면 어떻게해야합니까?

이 시간 동안 webView.isVisible()은 true입니다.

나는 isTreeReallyVisible()라고 WebView의 방법은 거기에 발견하고 현재이 포함

웹보기가 아닌 전경 탭에있는 숨겨져
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; 
} 

, impl_isTreeVisible()는 return 문에서 거짓 (다른 모든 요인입니다 사실입니다.) 그 방법은 Node에 있으며 다음과 같습니다 : 나는 내 자신의 구현을 제공 impl_treeVisibleProperty()를 오버라이드 (override) 한 수

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

하지만 WebView 최종, 그래서, 나는 그것에서 상속 할 수 없습니다.

완전히 다른 상황 (아이콘 화됨) 또는 숨겨진 탭에서 완전히 (트레이 바에서 실행중인 것처럼) 스테이지가 완전히 숨겨진 상태입니다. 해당 모드에서 렌더링을 수행 할 수 있더라도 WebView의 크기가 조정되지 않습니다. webView.resize()을 호출 한 다음 스크린 샷을 찍고 스크린 샷은 적절한 크기이지만 실제로 렌더링 된 페이지의 크기는 WebView입니다.

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

숨겨진 모드에서, getScene() 반환이 표시되는 때 일어나는 달리 null :

가 표시 숨겨진 단계에서이 크기 조정 동작을 디버깅, 나는 결국 우리가 포함 Node.addToSceneDirtyList()에 도착 것을 발견했다. 즉, s.addToDirtyList(this)이 호출되지 않습니다. 이것이 제대로 크기가 조정되지 않는 이유인지 여부는 확실하지 않습니다.

여기에 버그가 있습니다. 매우 오래된 것입니다 : https://bugs.openjdk.java.net/browse/JDK-8087569하지만 전체적인 문제라고 생각하지 않습니다.

Java 1.8.0_151에서이 작업을 수행하고 있습니다. WebKit이 업그레이드되었다는 것을 이해 했으므로 9.0.1을 다르게 사용하려고 시도했지만 그렇지 않습니다.

+0

탭 패널의 스킨이 어떻게 구현되었는지는 기억이 나지 않지만 설명하는 시나리오에서는 웹보기가 장면에있을 수 없습니다. 이 경우 스냅 샷이 작동하지 않습니다 (단, 스냅 샷을 찍는 동안 일시적으로 웹보기를 장면에 추가 할 수 있습니다). 그것이 여전히 장면의 일부라면, 나는 전혀 모른다 ... –

+3

@James_D는 전혀 모른다 ... 최후의 심판의 날. – Mordechai

+1

헬프가 있으면 확인하십시오. https://stackoverflow.com/questions/40642573/trying-to-render-javafx-webview-to-offscreen-buffer-or-fbo – AsifAli72090

답변

-1

나는이 명령을 사용하여 적절한 순간에 페이지를 다시로드 고려할 것 :

또한
webView.getEngine().reload(); 

그 다음 작동하지 않을 I 메모리에 이미지를 저장 고려할 경우 방법 snapShot

에서 매개 변수 SnapshotParameters을 변경하려고 WebView가 화면에 렌더링 될 때.

+0

적합한 순간이 뭔가요? WebView가 화면에 나타나지 않을 수 있습니다. – Pablo

3

여기 파블로의 문제를 재현 : https://github.com/johanwitters/stackoverflow-javafx-webview.

파블로는 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에서 호출됩니다. 하나는 "스냅 샷"버튼이 있고 다른 하나는 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<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; 
    } 
} 

나는 파블로의 문제를 해결에 성공하지했지만, 희망 제안이와 Javassist 도움이 될 사용.

나는 확신합니다 : 계속하려면 ...

+0

위와는 달리 마지막 메서드를 재정의하려는 경우에 대비하여 다음과 같이 추가하고 싶습니다. https://stackoverflow.com/questions/748362/override-java-final-methods -via-reflection-or-other-means –

+0

물론 ......... 웹 뷰는 보이지 않을 때 렌더링되지 않습니다. 브라우저를 두 번째 탭에 놓으려고했는데 결코 선택하지 않았습니다. 여전히 이미지가 출력됩니까? – Mordechai

+0

아직 코드를 테스트하지는 않았지만 예제와 내가 한 질문에서 지적해야 할 중요한 차이점 중 하나는 스크린 샷을 찍기 전에 탭을 표시하지 않는다는 것입니다. 두 번째 탭에서 웹보기에 페이지를로드하고 첫 번째 탭에 버튼이 있고 두 번째 탭이 표시되지 않으면 내 상황에 더 가깝습니다. – Pablo

-1

당신이 스냅 샷의 논리적 구현으로 이동하는 경우는, 화면에 표시 만 가지 스냅 샷으로 촬영. 웹보기의 스냅 샷을 찍는 경우 스냅 샷을 찍기 직전에보기가 렌더링 된 탭을 클릭하여 자동으로 웹보기를 표시 할 수 있습니다. 또는 탭을 수동으로 클릭하고 스크린 샷을 찍을 수 있습니다. API는 숨겨진 부분의 스냅 샷을 논리적으로 취할 수있는 API가 아니므로 숨겨진 사물의 개념을 손상시킬 수 있습니다. 스냅 샷 촬영을위한 코드는 이미 제공되고 있습니다. 클릭하거나 탭로드 :

selectionChangedListener를 추가하거나 스냅 샷 바로 전에로드를 수행 할 수 있습니다.

+0

자세한 내용을 보려면 https://stackoverflow.com/help/how-to-answer를 확인하십시오. –

+0

명확한 대답으로 기대되는 모든 세부 사항이 있다고 생각합니다.코멘트를 가리 키기 전에 구체적으로하십시오. –

+0

숨겨진 요소를 스냅 샷으로 저장할 수 없다는 주장이 잘못되었습니다. 장면에없는 경우 캡처 할 수도 있습니다 (visible 속성이 명시 적으로 false로 설정되지 않은 경우). 어떤 이유로 웹보기가 여기에 예외이며 그것은 문제입니다. – Mordechai

관련 문제