Tuesday 2 April 2013

JavaFX Modal Dialog with a controller class


JavaFX Modal Dialog with a controller class

There are quite a few tutorials out there detailing how to produce a modal dialog with JavaFX. Jewelsea gives an excellent example on https://gist.github.com/jewelsea/1887631 and I recommend that you read it.
However none that I've found seem to show you how to do it using controller classes and separate JavaFX FXML dialogs. Admittedly I've not looked that hard but hopefully this will help someone.

Prerequisites

1.       Java SDK – which at the time of writing is located here - http://www.oracle.com/technetwork/java/javase/downloads/index.html
2.       JavaFX – this is included in the SDK.
3.       JavaFX SceneBuilder – found here http://www.oracle.com/technetwork/java/javafx/tools/index.html

I assume that if you’re reading this then you already have the above.

Method

I’ve used NetBeans to create this example. It doesn’t matter particularly but it does show. Anyway, firstly, create a new FXML project. I’ve called mine JavaFXTest with the controller named Test.
Modify the code so that it looks like the following:

-----------------------------------------------------------------
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *
 * Demonstrates a modal dialog with controller class
 */
public class JavaFXTest extends Application {
   
    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader fl = new FXMLLoader();
        fl.setLocation(getClass().getResource("Test.fxml"));
        fl.load();
        Parent root = fl.getRoot();
        TestController tc = (TestController)fl.getController();
        tc.setStage(stage);
       
        Scene scene = new Scene(root);
       
        stage.setScene(scene);
        stage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}
-----------------------------------------------------------------

NetBeans will also create a FXML file with one button and a controller class. The code for the controller class will need to be modified to look like this:

-----------------------------------------------------------------
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

/**
 *
 * This is the main controller class
 */
public class TestController implements Initializable {
   
    private Stage primaryStage;
   
    @FXML
    private void handleButtonAction(ActionEvent event) throws IOException {
        System.out.println("You clicked me!");
        FXMLLoader fl = new FXMLLoader();
        fl.setLocation(getClass().getResource("Test1.fxml"));
        fl.load();
        Parent root = fl.getRoot();
       
        Stage modal_dialog = new Stage(StageStyle.DECORATED);
        modal_dialog.initModality(Modality.WINDOW_MODAL);
        modal_dialog.initOwner(primaryStage);
        Scene scene = new Scene(root);
       
        Test1Controller t1 = (Test1Controller)fl.getController();
        t1.setStage(modal_dialog);
        modal_dialog.setScene(scene);
        modal_dialog.show();
    }
   
    public void setStage(Stage temp){
        primaryStage = temp;
    }
   
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }   
}
-----------------------------------------------------------------

Essentially all we’ve done here is pass a reference to the primary Stage object to the main controller class. The only tricky bit (because it’s not documented well) is to use an FXMLLoader class to load the initial FXML dialog and get the controller.

Once this is done you can use the same method to create a new FXML dialog. This time a modal one. Make the owner of the modal dialog the initial stage. Then pass a reference to the newly created modal dialog to the modal dialog controller class, which we now create.

So the next step is to create another FXML file within the project. Right click on the project and select an empty FXML file. Call the controller class Test1. Once created, the code should be changed to this:

-----------------------------------------------------------------
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.stage.Stage;

/**
 * FXML Controller class
 *
 * This is the modal dialog
 */
public class Test1Controller implements Initializable {

    private Stage parentStage;
   
    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me 2!");
        parentStage.close();
    }
   
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }
   
    public void setStage(Stage temp){
        parentStage = temp;
    }
}
---------------------------------------------------------------------------------

In the FXML file create a button and associate it with the handleButtonAction handler.

As you can see the Stage object used to create the dialog is passed to the controller. Within the controller you then use the handler to take any actions, update objects and close the dialog.

It isn't pretty (much like this blog), but it does work.






2 comments:

  1. Useful, thanks

    ReplyDelete
  2. thank you! I've been researching this for days with no luck

    ReplyDelete