Thursday, January 16, 2020

Opening an About dialog box with JavaFX

Copyright © 2020, Steven E. Houchin. All rights reserved.

I was recently putting the finishing touches on a demo desktop application written in Java 8 using JavaFX. The UI consists of a single FXML file attached to a controller class. My next task was to add a nice looking About box - something better than an Alert.  So, I created an About.fxml using JavaFX Scene Builder 2.0.  I wanted to attach the About.fxml to the same application controller class I already had, so the About's OK button would dispatch its event there.  That, apparently, was a mistake that caused much grief, because loading the About.fxml kept giving me a non-specific LoadException error.  Postings at online help sites didn't seem to match my situation. Most of them dealt with file path problems to the user's FXML file, resulting in a NullPointerException, but my FXML is embedded within my application JAR file.

I eventually came to the conclusion that the FXMLLoader did not like loading a second FXML specifying the same controller class as my main FXML. So, how to load my About.fxml to get the result I wanted - its use of the same controller class?

Okay, here's what I did.  First, I removed any reference to a controller in About.fxml.  That meant I had to specify the controller some other way.  Here's my code in the controller class handler for the About menu event:


// load the About box FXML document
javafx.fxml.FXMLLoader loader = new javafx.fxml.FXMLLoader(
                    MyMainApp.class.getResource("About.fxml"));
loader.setController(this); // set 'this' as its controller
Parent root = loader.load(); // load UI objects from the FXML document

It turns out the magic I needed was the "setController" method above.  Specifying the controller class in the FXML file tries to create a new instance of the controller class, I assume, which must be the origin of my earlier LoadException error.  So, without that, the above loads fine.

Once the UI was loaded into a root Parent object (above), then it was time to configure the About dialog just the way I wanted: modal, no extra window decorations (i.e. Utility), owned by the main window, always on top, and not resizable ...

// create the dialog stage and set up its attributes
javafx.stage.Window owner = MyMainApp.getStage().getScene().getWindow();
Stage dialog = new Stage();
dialog.initStyle(StageStyle.UTILITY);
dialog.initModality(Modality.WINDOW_MODAL);
dialog.initOwner(owner);
dialog.setAlwaysOnTop(true);
dialog.setTitle("About MyMainApp");
dialog.setResizable(false);

Lastly, it was time to show the dialog:

// create the scene and set onto the stage
Scene scene = new Scene(root);
dialog.setScene(scene);
dialog.sizeToScene();
dialog.showAndWait();

At that point, my nice About box with a title and the system close button pops up!  The underlying main window cannot gain focus.  When I press the About's 'OK' button, I then get its event callback in my same controller class, which then closes the About box:

@FXML
public void onButtonHelpAboutDismissAction(ActionEvent event)
{
    Stage stage = (Stage)buttonHelpAboutDismiss.getScene().getWindow();
    stage.close();
}

Now, I'm sure there's a stupidly simpler way to do this in JavaFX, but I didn't see any other online examples for an About box like this. So, this is my contribution, as a relative Java newbie.

Any comments?