I am trying to instantiate many JFrames with WebView, it will work as long as after opening the first WebView, at least one WebView is still alive and the JFrame (along with everything else) arent disposed of.
After some checking, it seems that after closing the all WebView JFrame, the Platform.runLater() no longer runs on the next instantiation of the JFrame with the WebViewPanel.
The following is the simplified code:
public class WebViewPanel{
private JFXPanel jfxPanel;
private WebView view;
private WebEngine webEngine;
private JPanel panel;
public WebViewPanel(JPanel panel){
this.panel = panel;
panel.setVisible(true);
createScene();
}
private void createScene(){
Platform.runLater(new Runnable(){
#Override
public void run(){ //Runnable no longer runs after all JFrames with WebViewPanels are closed
jfxPanel = new JFXPanel();
view = new WebView();
webEngine = view.getEngine();
jfxPanel.setScene(new Scene(view));
panel.add(jfxPanel);
jfxPanel.setVisible(true);
}
});
}
}
So, what must I do to resolve this problem?
This happens because Platform exits itself if not explicitly set.
Here's what you need to do just after
Platform.runLater(new Runnable(){ // ..code
add Platform.setImplicitExit(false); at the first row
so your new code will be -
private void createScene(){
Platform.runLater(new Runnable(){
Platform.setImplicitExit(false);
#Override
public void run(){
jfxPanel = new JFXPanel();
view = new WebView();
webEngine = view.getEngine();
jfxPanel.setScene(new Scene(view));
panel.add(jfxPanel);
jfxPanel.setVisible(true);
}
});
}
Just FYI if you are trying to display the panel more than once there is no need to call createScene() add the JFXPanel to a JFrame(preferable panel works fine too) with a static reference. dispose the frame to close it and when again you need to show this simply set the same frame setVisibale(true);. everytime calling this frame you can also check if the static reference of this frame points to null(representing first call) then call createscene() else just set the static reference to visible.
Related
I'm building a Swing application and I'm using JFXPanel to bring up a WebView for user authentication. I need to return the url that the WebEngine is sent to after a successful user sign-on- but I don't know how to pull the URL from the view at the time the window is closed. I'm aware of Stage.setOnHiding, but this can't apply here since JFXPanels don't act as stages, though they contain a scene.
I'm very new to JavaFX so if this code snippet demonstrates bad conventions, I'd be happy to hear it!
private static void buildAuthBrowser() {
JFrame frame = new JFrame("frame");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
private static void initFX(JFXPanel fxPanel) {
// fx thread
Scene scene = createScene();
fxPanel.setScene(scene);
}
private static Scene createScene() {
Group root = new Group();
Scene scene = new Scene(root);
WebView view = new WebView();
WebEngine browser = view.getEngine();
browser.load(auth.getUrl());
root.getChildren().add(view);
return (scene);
}
Found a solution- I was able to set a listener on the engine in the scene builder method, this way, every time the browser visits a new URL it automatically updates a private variable.
in createScene():
authBrowser.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
if (Worker.State.SUCCEEDED.equals(newValue)) {
setCode(authBrowser.getLocation());
}
});
I'm working on a task which needs to invoke a java method from a html content. This is a swing application and I used JavaFX WebView to load the HTML content into the application. But when I tried to invoke the Java methods it didn't work and sometime its giving fatal error and crashes the application.
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>
Please let me know what needs to be changed here
From the documentation, both the class and method used for the callback must be 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.
(My emphasis.)
Your Solution class is not public, so this won't work.
In addition, when a new document is loaded, the window will lose its attributes. Since loading happens asynchronously, you need to ensure the member is set on the window after the document loads. You can do this via a listener on the 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());
There are a number of other problems with your code:
JFrames must be constructed on the AWT event dispatch thread (the same rule also applies to modifying components displayed in a JFrame). You can do this by wrapping the call to createUI() in SwingUtilities.invokeLater(...).
It is unclear why you made Solution a subclass of JFrame, as well as creating a new JFrame in createUI(). Since you never use the fact that Solution subclasses JFrame, you should remove that.
PlatformImpl is not part of the public API: consequently it would be perfectly OK for the JavaFX team to remove that class in a later release. You should use methods in the Platform class.
You almost certainly want the Javascript callback to interact with the current Solution instance, not some arbitrary instance you create. (If you're in an inner class, use Solution.this to access the current instance of the surrounding object.)
A working version of your code is
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");
}
}
I'm working on an application which is based on swing and javafx 8. In this create a frame in swing and jbutton use and jbutton action code is done in javafx 8 means use scene. in this a alert dialog create.but problem is that if i click anywhere except alert dialog then alert dialog hidden behind swing frame.i want javafx alert dialog keep on swing frame which i create.if i click ok on alert then action is goes to swing frame.
please suggest......
here is my code:
final JFXPanel fxPanel = new JFXPanel();
Platform.runLater(new Runnable() {
public void run() {
initFX(fxPanel);
}
private void initFX(JFXPanel fxPanel) {
// TODO Auto-generated method stub
Scene scene = createScene();
fxPanel.setScene(scene);
}
private Scene createScene() {
Group root = new Group();
Scene scene = new Scene(root);
Alert cn=new Alert(AlertType.CONFIRMATION);
cn.initStyle(StageStyle.UNDECORATED);
cn.setTitle(null);
cn.setHeaderText(null);
cn.setContentText(ProjectProps.rb.getString("msg.play.confirm"));
DialogPane dp=cn.getDialogPane();
dp.getStylesheets().add(getClass().getResource("login.css").toExternalForm());
dp.setStyle("-fx-border-color: black; -fx-border-width: 5px; ");
Optional<ButtonType> result=cn.showAndWait();
if(result.get()==ButtonType.OK){
System.out.println("ok button clicked:"+result.get());
k=0;
} else{
System.out.println("cancel clicked");
k=1;
}
return (scene);
}
});
I'm not sure to understand what you really want to do. Do you absolutely need a JavaFX alert in a swing application? JDialog works very well, and Alert and Dialogs works very well too in a JavaFX context.
Have you tried to set your Alert application modal with cn.initModality(Modality.APPLICATION_MODAL) ?
Maybe you can find you answer in this post : How to make a JFrame Modal in Swing java
This is the suggested code in the post:
final JDialog frame = new JDialog(parentFrame, frameTitle, true);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
I would create a new JDialog and set as parameter your main frame (parentFrame) and add your JFXPanel to its content.
frame.getContentPane.add(fxPanel)
You can use fxPanel.setScene(yourScene) to add FX content to your JDialog.
Example for the suggestion:
public class Main {
public static void main(String[] args) {
// create parent
JFrame parent = new JFrame("MainFrame");
parent.setSize(500, 500);
// center of screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
parent.setLocation(dim.width/2-parent.getSize().width/2, dim.height/2-parent.getSize().height/2);
parent.setVisible(true);
createDialog(parent);
}
private static void createDialog(JFrame parent) {
// create dialogs JFX content
BorderPane layout = new BorderPane();
layout.setTop(new Button("HELLO"));
layout.setBottom(new Button("IT'S ME"));
Scene scene = new Scene(layout);
JFXPanel dlgContent = new JFXPanel();
dlgContent.setScene(scene);
dlgContent.setPreferredSize(new Dimension(200, 200));
dlgContent.setVisible(true);
// create dialog and set content
JDialog dlg = new JDialog(parent, "Dialog", true);
dlg.setLocationRelativeTo(parent);
dlg.getContentPane().add(dlgContent);
dlg.pack();
dlg.setVisible(true);
}
}
Hope I could help you.
Inside of my JPanel, I have a button calling SimpleBrowser
JButton swingButton = new JButton();
swingButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
SimpleBrowser openBrowser = new SimpleBrowser();
openBrowser.main((new String[0]));
}
});
swingButton.setText("Browser");
add(swingButton, BorderLayout.SOUTH);
this is my SimpleBrowser class
public class SimpleBrowser extends Application {
VBox vb = new VBox();
public void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
vb.setId("root");
WebView browser = new WebView();
WebEngine engine = browser.getEngine();
String url = "https://www.google.com";
engine.load(url);
vb.getChildren().addAll(browser);
Scene scene = new Scene(vb);
primaryStage.setScene(scene);
primaryStage.show();
}
When I run this code, JButton opens up properly SimpleBrowser and display google. However, when I close this application and repress JButton, nothing happens. It suppose to relaunch SimpleBrowser and display google.
Could you guys help me?
I actually find this code from oracle which helped me a lot.
I will share it here just in case if anyone needs
https://docs.oracle.com/javafx/2/swing/SimpleSwingBrowser.java.htm
explanation can be found here
https://docs.oracle.com/javafx/2/swing/swing-fx-interoperability.htm#CHDIEEJE
Thank you for trying to help me
I have a JPanel component containing a JFXPanel with a browser that embedds a YouTube video. I'm using the videos embed URL from YouTube (i.e. https://www.youtube.com/embed/W-J2OYN9fF8?autoplay=true&controls=0).
I can add the JPanel (VideoPlayer) to a surrounding component without any problem. However - when I remove the VideoPlayer I would also like to stop the YouTube-player. As of now - it keeps playing in the background (with annoying sound). I'm guessing I have to get inside the JFX thread somehow... So, if someone could please help me with code to put in the stopTrailer() method - I'd be very grateful!
Here's my current code. For those who are looking for a simple way to embed a JFX YouTube player in a normal JPanel - this works great - appart from the limitations above...
public class VideoPlayer extends JPanel {
private Stage stage;
private WebView browser;
private JFXPanel jfxPanel;
private WebEngine webEngine;
private String videoUrl;
public VideoPlayer(String url){
this.videoUrl = url;
jfxPanel = new JFXPanel();
createScene();
setLayout(new BorderLayout());
setPreferredSize(new Dimension(800, 560));
add(jfxPanel, BorderLayout.CENTER);
}
private void createScene() {
PlatformImpl.startup(new Runnable() {
#Override
public void run() {
stage = new Stage();
stage.setTitle("Video");
stage.setResizable(true);
Group root = new Group();
Scene scene = new Scene(root,80,20);
stage.setScene(scene);
//Set up the embedded browser:
browser = new WebView();
webEngine = browser.getEngine();
webEngine.load(videoUrl);
ObservableList<Node> children = root.getChildren();
children.add(browser);
jfxPanel.setScene(scene);
}
});
}
public void stopTrailer() {
}
}
The answer to this was actually really simple. I put the following in the stopTrailer method.
public void stopTrailer() {
PlatformImpl.startup(new Runnable() {
#Override
public void run() {
remove(jfxPanel);
webEngine.load(null);
}
});
}
What it actually does is to reload the browser webEngine to the "address" null. And removes the panel. It seems a bit weird to me cause I would interpret this code as the start of a second thread - without access to the first one. Perhaps someone could explain just how this works?