javaswingjavafxjfxpanel

Cannot get JavaFx Stage from Node because class com.sun.javafx.stage.EmbeddedWindow cannot be cast to class javafx.stage.Stage


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!


Solution

  • 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);
        }
    
        // ...
    }