Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Thursday, October 16, 2014

Java FX Notification Overlays

This post presents a Java FX program that uses overlays to notify the user of a validation error.  The solution is FXML-driven and uses an off-the-shelf Scene Builder Alert Template with no modifications.


Popups and Overlays

Ever since the early days of the web, I've hated popups.  Even when they're needed -- say to let you know a validation has failed -- they can inconveniently position themselves far from your mouse's location.  Worse still, you might have to shuffle your windows looking for a popup with a bad z-order.

There is a notification style in Javascript frameworks that started around the time tabbed-browsing was introduced.  Instead of displaying a heavyweight window on top of another window, the current display is overlaid with a centered message on a slight transparency.  And because the native windows aren't involved, the display remains in position and the notification immediately has the focus for one-click dismissal.

Application Screenshots

The function of the demo application, called Maven POM Updater, is to recursively update the versions of Maven POMs in a directory structure.  This application consists of a main screen with some controls (TextField, TableView, Button).  
Main View of Demo Application
If the user attempts an operation without sufficient parameters -- say scanning a folder structure that isn't found or doesn't have any POMs, the user is notified.  The notification mechanism is to overlay a modal component on top of the main view, allowing for the main view to be seen via transparency.
Notification Overlay

FXML

If you're coding Java FX, it's important to incorporate FXML into your design to support a flexible UI.  Although GUI builders have been around for years, the technology has advanced to the point where it can be incorporated easily into a project's mainline Java development.  See Apple's Storyboard for a GUI builder success story.  Also, you can support different UI requirements.  For example, I have a Hi-DPI set of FXML for this particular app.

The Main View was created based on a mockup while the Alert View was created with the New > Template command in Scene Builder.

CSS

Additionally, it's important to use CSS to benefit from the productivity boost in focusing on styles and also to support skinning.  Customers may want a less flashy, more readable, or co-branded version of your app.  I'm using CSS to set several backgrounds.  .alert-background-pane has the dark gray with a slight alpha to show a dimmed mainView when the notification is displayed.

.main-view-pane {
-fx-background-color: #FFFFFF;
}

.alert-pane {
-fx-background-color: #FFFFFF;
}

.alert-background-pane {
-fx-background-color: rgba(64, 64, 64, 0.85);
}

Java Code

The Java Code starts with the Application subclass.  In the start() method, first load each FMXL file using the FXMLLoader class.  Unpack each Controller so that you can connect the MainViewController to the AlertViewController so that the business logic of MainViewController can configure the alert based on the validation or other condition.  See "mainViewController.alertController=alertController".

     String mpuFXML = "mavenpomupdater.fxml";
    String alertFXML = "alert.fxml";
    
     final StackPane sp = StackPaneBuilder.create().build();
    
    FXMLLoader mainViewLoader= new FXMLLoader(getClass().getResource(mpuFXML));
    Parent mainView = (Parent)mainViewLoader.load();
    MainViewController mainViewController = mainViewLoader.getController();
    
    FXMLLoader alertViewLoader = new FXMLLoader(getClass().getResource(alertFXML));
    Parent alertView = (Parent)alertViewLoader.load();
    
    final AlertController alertController = alertViewLoader.getController();
    mainViewController.alertController = alertController;

Note the StackPane which is the display organizer for the app.  mainView will be loaded directly into the StackPane as the second of two items so that it appears on top initially.  alertView will be wrapped into a FlowPane to allow for the centering.  The FlowPane containing alertView will be added to the StackPane as the first items, initially in the background.

        mainView.getStyleClass().add("main-view-pane");
        
        final FlowPane fp = new FlowPane();
        fp.setAlignment(Pos.CENTER);
        fp.getChildren().add( alertView );
        fp.getStyleClass().add("alert-background-pane");
        
        alertView.getStyleClass().add("alert-pane");
        
        sp.getChildren().add( fp );  // initially hide the alert

        sp.getChildren().add( mainView );

These lines are additional CSS wiring and Scene creation.

        Scene scene = new Scene(sp);
        scene.getStylesheets().add("com/bekwam/mavenpomupdater/mpu.css");
        primaryStage.setScene(scene);
        primaryStage.show();

Operation

The Browse button will produce a search path for Maven POMs.  If you press "Scan" on a path without any POMs, you'll get a notification.  In terms of the UI, mainView will be put to the background of the StackPane.  This presents alertView and dismissing this will put alertView in the background.  There is a handy "toBack()" call that makes this happen without manipulating the underlying collections.

mavenpomupdater.fxml is a VBox which is injected into the mainView Controller.

@FXML
VBox vbox;

In a method called "scan()" which is wired to the Scan Button, there is a block of code which checks for pom.xml files.  If an empty list is returned, a method on the AlertViewController is called (from the MainViewController).  setNotificationDialog() sets the specific message that the user will see.  This is followed up with a toBack() call which puts the mainView in the background.

        if( CollectionUtils.isEmpty(pomPaths) ) {
         alertController.setNotificationDialog("No POMs Found", "No pom.xml files were found in the specified Project Root.");
        vbox.toBack();  // bring up the alert view
        return;

        }

The alertView exists for the life of the app, but the message and details change.  Note that the fx:ids that I'm using are the ones from the template without modification (cancelButton, messageLabel, etc).

public void setNotificationDialog(String message, String details) {
cancelButton.setVisible(false);
okButton.setVisible(false);
actionButton.setText( "Dismiss" );
messageLabel.setText( message );
detailsLabel.setText( details );
}
To dismiss the app, I use the injected GridPane (from the FXML) to get the parent (a FlowPane created in the code).  I then call toBack() and mainView retakes control.

@FXML
public void dismiss() {
Parent flowPane = gp.getParent();
flowPane.toBack();
}

Bonus: Escape Key

The Escape key can also dismiss the notification.  Back in the start() method of the Application, there is a listener registered that makes use of alertView.

        scene.setOnKeyPressed( new EventHandler<KeyEvent>() {
         @Override
             public void handle(KeyEvent t) {
                 KeyCode key = t.getCode();
                 if (key == KeyCode.ESCAPE  && (sp.getChildren().get(1) == fp)) {
                 if( log.isDebugEnabled() ) {
                 alertController.dismiss();
                 }
                 }
             }
        });
What I like most about this solution is that there is a real minimum of Java code in the app devoted to the UI.  I don't have to keep track of what the user's seeing.  This is a small app, and if there were more views, I'd consider replacing the toBack() algorithm with a toFront().  However, that means that something has to keep track of what is being toFront()-ed.

I'll be putting the full source up on a public repository on GitHub.  If you'd like to see the app in action, visit http://www.bekwam.com/mpu and pull up the mpu.jnlp (this is a Beta version that works as-is).  Note that only Java 8 is supported, so if you're working from a Java 7 machine, wait for the source and run it locally.

If you have any suggestions for the app, email them to mavenpomupdater@bekwam.com.

5 comments:

  1. great tutorial. it would be great if you could provide the java project... otherwise it's hard to understand all the parts as javafx beginner.

    ReplyDelete
  2. great tutorial. it would be great if you could provide the java project... otherwise it's hard to understand all the parts as javafx beginner.

    ReplyDelete
    Replies
    1. Hi,

      Several of these posts are paired with a Maven module stored in GitHub. The links to the projects are in the posts themselves. I'll be adding a link on the learn.html page on my main website.

      This is the GitHub parent project: https://github.com/bekwam/examples-javafx-repos1.

      Good luck.

      Delete
    2. Hi Carl

      Many thanks for the github link :) Great job :)

      Delete