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

Why I'm unable to invoke Java methods from html content via the JavaScript call in JavaFX WebView?

我正在处理一项需要从 html 内容调用 java 方法的任务。这是一个 swing 应用程序,我使用 JavaFX WebView 将 HTML 内容加载到应用程序中。但是当我尝试调用 Java 方法时它不起作用,有时它会给出致命错误并使应用程序崩溃。

Java class

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>

请让我知道这里需要更改什么

documentation开始,用于回调的class和方法都必须是public:

Calling back to Java from JavaScript

The JSObject.setMember method is useful to enable upcalls from JavaScript into Java code, as illustrated by the following example. The Java code establishes a new JavaScript object named app. This object has one public member, the method exit.

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

...

The Java class and method must both be declared public.

(我的重点。)

你的 Solution class 不是 public,所以这行不通。

此外,当加载新文档时,window 将失去其属性。由于加载是异步发生的,因此您需要确保在文档加载后在 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. JFrames 必须在 AWT 事件调度线程上构造(同样的规则也适用于修改 JFrame 中显示的组件)。您可以通过将对 createUI() 的调用包装在 SwingUtilities.invokeLater(...).
  2. 中来实现
  3. 不清楚您为何将 Solution 设为 JFrame 的子 class, 以及 创建新的 JFramecreateUI()。由于您从未使用 Solution subclasses JFrame 这一事实,您应该删除它。
  4. PlatformImpl 不是 public API 的一部分:因此,JavaFX 团队完全可以在以后的版本中删除 class。您应该使用 Platform class.
  5. 中的方法
  6. 您几乎肯定希望 Javascript 回调与当前 Solution 实例交互,而不是您创建的任意实例。 (如果您在内部 class 中,请使用 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");
    }

}