为什么我无法通过JavaFX WebView中的JavaScript调用从html内容中调用Java方法?

时间:2020-05-26 11:09:51

标签: java javafx javafx-webengine javafx-webview

我正在从事一项需要从html内容调用java方法的任务。这是一个摇摆应用程序,我使用JavaFX WebView将HTML内容加载到该应用程序中。但是,当我尝试调用Java方法时,它无法正常工作,有时会出现致命错误并导致应用程序崩溃。

Java类

class Solution extends JFrame { 
    
private JFXPanel jfxPanel;
static JFrame f; 

public static void main(String[] args) {
    new Solution().createUI();
}

    private void createUI() {
f = new JFrame("panel"); 

JPanel p = new JPanel(); 

jfxPanel = new JFXPanel();
createScene();
p.add(jfxPanel);

f.add(p);
f.setSize(300, 300); 
f.show(); 
    } 
    
    private void createScene() {
        
PlatformImpl.setImplicitExit(false);
PlatformImpl.runAndWait(new Runnable() {
@Override
public void run() {
BorderPane borderPane = new BorderPane();
WebView webComponent = new WebView();
WebEngine webEngine = webComponent.getEngine();

webEngine.load(TestOnClick.class.getResource("/mypage.html").toString());

borderPane.setCenter(webComponent);
Scene scene = new Scene(borderPane,300,300);
jfxPanel.setScene(scene);

JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", new Solution());
}
});
}
    
    public void onClick() {
        System.out.println("Invoked from JS");
    }
}

HTML

<button onclick="app.onClick()">Click ME</button>

请让我知道这里需要更改的内容

1 个答案:

答案 0 :(得分:1)

documentation中,用于回调的类和方法都必须为public

从JavaScript回调到Java

JSObject.setMember方法可用于启用来自 将JavaScript转换为Java代码,如以下示例所示。 Java代码建立了一个名为app的新JavaScript对象。这个 对象具有一个公共成员,方法退出。

public class JavaApplication {
    public void exit() {
        Platform.exit();
    }
}
...
JavaApplication javaApp = new JavaApplication();
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", javaApp);

...

必须同时将Java类和方法声明为公共。

(我的重点。)

您的Solution类不是公开的,因此它将无法正常工作。

此外,在加载新文档时,window将失去其属性。由于加载是异步发生的,因此您需要确保在文档加载后在窗口上设置成员。您可以通过documentProperty()上的侦听器来完成此操作:

    webEngine.documentProperty().addListener((obs, oldDoc, newDoc) -> {
        JSObject window = (JSObject) webEngine.executeScript("window");
        window.setMember("app", this);          
    });

    webEngine.load(Solution.class.getResource("/mypage.html").toString());

您的代码还有很多其他问题:

  1. JFrame必须在AWT事件分配线程上构造(相同的规则也适用于修改JFrame中显示的组件)。您可以通过将调用包装到createUI()中的SwingUtilities.invokeLater(...)来实现。
  2. 目前尚不清楚为什么将Solution设为JFrame的子类,以及为什么 JFrame中创建新的createUI()。由于您从不使用Solution继承JFrame子类的事实,因此应将其删除。
  3. PlatformImpl不是公共API的一部分:因此,JavaFX团队在以后的版本中删除该类是完全可以的。您应该使用Platform类中的方法。
  4. 几乎可以肯定,您希望Javascript回调与当前的Solution实例进行交互,而不是与您创建的任意实例进行交互。 (如果您在内部类中,请使用Solution.this访问周围对象的当前实例。)

您的代码的有效版本是

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;

public class Solution  {

    private JFXPanel jfxPanel;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Solution()::createUI);
    }



    private void createUI() {
        JFrame f = new JFrame("panel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel p = new JPanel();

        jfxPanel = new JFXPanel();
        createScene();
        p.add(jfxPanel);

        f.add(p);
        f.setSize(300, 300);
        f.setVisible(true);
    }

    private void createScene() {

        Platform.setImplicitExit(false);
        Platform.runLater(() -> {
            BorderPane borderPane = new BorderPane();
            WebView webComponent = new WebView();
            WebEngine webEngine = webComponent.getEngine();

            webEngine.documentProperty().addListener((obs, oldDoc, newDoc) -> {
                JSObject window = (JSObject) webEngine.executeScript("window");
                window.setMember("app", this);          
            });

            webEngine.load(Solution.class.getResource("/mypage.html").toString());

            borderPane.setCenter(webComponent);
            Scene scene = new Scene(borderPane, 300, 300);
            jfxPanel.setScene(scene);

        });
    }

    public void onClick() {
        System.out.println("Invoked from JS");
    }

}