0

I am trying to close the current fxml to move to the next one. I followed the reply from this question: close fxml window by code, javafx:

@FXML private javafx.scene.control.Button closeButton;

@FXML
private void closeButtonAction(){
    // get a handle to the stage
    Stage stage = (Stage) closeButton.getScene().getWindow();
    // do what you have to do
    stage.close();
}

And I encountered the same problem as the unanswered comment below it:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: com.sun.javafx.stage.EmbeddedWindow cannot be cast to javafx.stage.Stage

All of the other answers also doesn't help. There are little discussion on EmbeddedWindow so I have no clue on what to do next. The previous screen was made with javax.swing, not JavaFx, and the transition is as follow:

import javafx.embed.swing.JFXPanel;

// more code

        JFXPanel fxPanel = new JFXPanel();
        this.add(fxPanel);
        
        this.setTitle("Title");
        this.setSize(1024, 768);
        this.setVisible(true);
        Platform.runLater(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    FXMLLoader loader = new FXMLLoader(getClass().getResource("<String url to my fxml file>"));
                    ScreenController controller = new ScreenController();
                    loader.setController(controller);
                    Parent root = loader.load();
                    fxPanel.setScene(new Scene(root));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

// more code

By the time I'm done writing the context, I think the problem may lie in the usage of JFXPanel, but I can't find a solution either. So helps are appreciated. Thanks!

  • 3
    Do you need to mix JavaFX and Swing? This isn't really recommended. If you really need to do that, you'll need to pass a reference to the `JFrame` to the controller, and have the controller call `setVisible(false)` on the `JFrame` (which will need to be done on the AWT event dispatching thread, not on the FX Application Thread). – James_D Jan 10 '23 at 18:24
  • In my previous comment, I'm assuming you want to close the actual window (which is what the code would do if you were displaying in a stage). Obviously, if you want to display a different FXML in the same panel, you don't need any of this. Your question says "I am trying to close the current *fxml* to move to the next one" (my emphasis), but the question you link is about closing the current *window*. What exactly do you want to do? – James_D Jan 10 '23 at 18:35
  • @James_D it follows the instruction in my assignment and I'm not allowed to change it. I'll try your suggestion and give an update later. – Minh Long Vu Jan 10 '23 at 18:37
  • So what does it actually say to do? Close the window? Or just display another FXML? – James_D Jan 10 '23 at 18:38
  • The way I am working right now is it opens a new window for each fxml. The idea to display another FXML in the same panel didn't occur in my head. It should actually be better to do that. I still need to also return to the interface created by swing however. For this specific action, I should do as your first comment? – Minh Long Vu Jan 10 '23 at 18:44
  • It should also be noted that the cast in the original question you linked is unnecessary. You can just do `closeButton.getScene().getWindow().hide()`, and for a `Stage` (the context for the original question), `close()` is equivalent to the superclass method `hide()`. The `EmbeddedWindow` that is returned by `getWindow()` when the scene is displayed in a `JFXPanel` is of course a subclass of `Window`, so you can make the same call here, though I have no idea what it will actually do (i.e. I don't know what the implementation of `EmbeddedWindow.hide()` is). – James_D Jan 10 '23 at 21:07
  • 1
    *“it follows the instruction in my assignment and I'm not allowed to change it”* -> it boggles the mind that somebody would actually create an assignment requiring mixing JavaFX and Swing/AWT. – jewelsea Jan 11 '23 at 01:04

1 Answers1

4

To close the containing JFrame in a mixed Swing and JavaFX application, from a JavaFX controller, you need to provide the controller with sufficient information to close the window. Probably the best way to decouple this properly is to just have a Runnable in the controller that knows how to close the window:

public class ScreenController {

    private Runnable windowCloser ;

    public void setWindowCloser(Runnable windowCloser) {
        this.windowCloser = windowCloser;
    }

    // ...

    @FXML
    private void closeButtonAction(){
        
        // do what you have to do

        // close the current window:
        windowCloser.run();
    }

    // ...
}

And then:

    JFXPanel fxPanel = new JFXPanel();
    this.add(fxPanel);
    
    this.setTitle("Title");
    this.setSize(1024, 768);
    this.setVisible(true);

    // Assuming this is a JFrame subclass
    // (otherwise replace "this" with a reference to the JFrame):

    Runnable windowCloser = () -> SwingUtilities.invokeLater(
        () -> this.setVisible(false)
    );

    Platform.runLater(() -> {

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("<String url to my fxml file>"));
            ScreenController controller = new ScreenController();

            controller.setWindowCloser(windowCloser);

            loader.setController(controller);
            Parent root = loader.load();
            fxPanel.setScene(new Scene(root));
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

You should also consider just loading the FXML into the current JFXPanel, which is much easier:

public class ScreenController {


    // ...

    @FXML
    private Button closeButton;

    @FXML
    private void closeButtonAction(){

        Parent root = /* load next FXML */ ;
        closeButton.getScene().setRoot(root);
    }

    // ...
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I don't understand this part: `Runnable windowCloser = () -> SwingUtilities.runLater(() -> this.setVisible(false));`. Also it is giving me the error `The method runLater(() -> {}) is undefined for the type SwingUtilities`. – Minh Long Vu Jan 10 '23 at 19:33
  • @MinhLongVu Sorry, `SwingUtilities.invokeLater(...)` (my Swing knowledge is almost never used these days). What don't you understand about the code? – James_D Jan 10 '23 at 19:41
  • @MinhLongVu `SwingUtilties.invokeLater(...)` schedules the `Runnable` to run on the _event-dispatch thread_ (EDT). That's the Swing UI thread. The equivalent in JavaFX is `Platform.runLater(...)`, which schedules the `Runnable` to run on the _JavaFX Application Thread_ (the JavaFX UI thread). The `() -> ...` bits are lambda expressions. If you don't understand lambdas, check out https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html or https://www.geeksforgeeks.org/lambda-expressions-java-8/ – Slaw Jan 10 '23 at 19:41
  • Yes, invokeLater worked. I also tried changing SwingUtilities.runLater(...) to Platform.runLater(...) and it also worked at closing the window. Are they any different? – Minh Long Vu Jan 10 '23 at 19:45
  • If it's the "nested lambda" that is confusing, just consider it as `Runnable r = () -> this.setVisible(false);`. Then `r` is a `Runnable` that closes the window, but it must be run on the AWT Event Dispatch Thread, so to pass it to the FXML controller, you need `Runnable windowCloser = () -> SwingUtilities.invokeLater(r);`. – James_D Jan 10 '23 at 19:45
  • 2
    @MinhLongVu `Platform.runLater(...)` is incorrect there. That would close the Swing window on the FX Application Thread, which is [not allowed](https://docs.oracle.com/en/java/javase/19/docs/api/java.desktop/javax/swing/package-summary.html). – James_D Jan 10 '23 at 19:48