Creating Dialogs

I have two separate dialog windows for saving and loading events:

 Load Calendar

 Figure 7 – Select Event Dialog

 

 Save Calendar

Figure 8 – Save Event Dialog

 Both windows were created using SceneBuilder; here are the corresponding FXML files:

<?xml version="1.0" encoding="UTF-8"?>
 
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?scenebuilder-background-color 0xbfbfbfff?>
 
<AnchorPane fx:id="contentAnchor" prefHeight="480.0" prefWidth="520.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="datecalculator.DCEventSelect">
  <children>
    <TableView fx:id="selectTable" minHeight="-Infinity" minWidth="-Infinity" prefHeight="432.0" prefWidth="520.0">
      <columns>
        <TableColumn id="eventColumn" fx:id="eventColumn" editable="false" minWidth="200.0" prefWidth="240.0" style="" text="Event" />
        <TableColumn id="eventDateColumn" fx:id="eventDateColumn" editable="false" minWidth="200.0" prefWidth="272.0" style="" text="Event Date" />
      </columns>
      <stylesheets>
        <URL value="@WBCalc.css" />
      </stylesheets>
    </TableView>
    <HBox fx:id="buttonBox" alignment="CENTER" layoutY="432.0" prefHeight="48.0" prefWidth="520.0" spacing="10.0">
      <children>
        <Button fx:id="selectButton" contentDisplay="CENTER" mnemonicParsing="false" prefWidth="50.0" text="Select" />
        <Button fx:id="closeButton" contentDisplay="CENTER" mnemonicParsing="false" prefWidth="50.0" text="Close" />
      </children>
    </HBox>
  </children>
</AnchorPane>

Listing 8 – Select Event Dialog FXML File

<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
 
<AnchorPane fx:id="mainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="160.0" prefWidth="336.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="datecalculator.DCEventCreate">
   <children>
      <Label fx:id="titleLabel" alignment="CENTER" contentDisplay="CENTER" layoutX="96.0" layoutY="3.0" text="Create New Event" textAlignment="CENTER">
         <font>
            <Font name="Arial Black" size="14.0" />
         </font>
      </Label>
      <Label fx:id="eventDateLabel" layoutX="11.0" layoutY="38.0" text="Event Date" textAlignment="RIGHT">
         <font>
            <Font name="Arial" size="12.0" />
         </font>
      </Label>
      <TextField fx:id="eventDateField" disable="true" editable="false" layoutX="76.0" layoutY="35.0" prefHeight="23.0" prefWidth="250.0">
         <font>
            <Font name="Arial" size="12.0" />
         </font>
      </TextField>
      <Label fx:id="eventNameLabel" layoutX="4.0" layoutY="74.0" text="Event Name" textAlignment="RIGHT">
         <font>
            <Font name="Arial" size="12.0" />
         </font>
      </Label>
      <TextField fx:id="eventNameField" layoutX="76.0" layoutY="71.0" prefHeight="23.0" prefWidth="250.0" promptText="Enter the Event Name">
         <font>
            <Font name="Arial" size="12.0" />
         </font>
      </TextField>
      <Button fx:id="okButton" alignment="CENTER" contentDisplay="CENTER" layoutX="100.0" layoutY="119.0" mnemonicParsing="false" prefWidth="60.0" text="OK" textAlignment="CENTER">
         <font>
            <Font size="12.0" />
         </font>
      </Button>
      <Button fx:id="cancelButton" alignment="CENTER" contentDisplay="CENTER" layoutX="176.0" layoutY="120.0" mnemonicParsing="false" prefWidth="60.0" text="Cancel" textAlignment="CENTER">
         <font>
            <Font name="Arial" size="12.0" />
         </font>
      </Button>
   </children>
</AnchorPane>

Listing 9 – Save Event Dialog FXML File

Here is the corresponding code.  The technique I use for each dialog is essentially the same as the technique I use in creating the application main Stage:

  • I create the dialog layout and properties using SceneBuilder. The result is an FXML file that establishes the dialog root (AnchorPane instance, in both cases), and the dialog UI hierarchy).
  • I create the dialog class:
    • Make it a child of the Stage class and implement the Initializable interface
    • Override the initialize() method with initialization actions
    • Establish event handlers for the dialog buttons
    • Establish an instance reference to the caller Stage (DateCalculatorUI) by providing a public setDialogStage() method
    • Create code to process the user selection.
  • I create the methods to display and handle the dialog in the parent class (DateCalculatorUI):
    • Create an FXMLLoader instance
    • Make a reference to the FXML file location (URL)
    • Load and parse the XML file (loader.load()).
    • Create an instance of the dialog (eventCreateStage and eventSelectStage)
    • Establish a reference to the dialog controller (top-level element in the FXML hierarchy) – note that I cast the controller to the dialog instance.
    • Set key dialog properties (title, modality, owner, etc)
    • Call showAndWait() to display the dialog and wait for the user to return from the dialog
    • Process the corresponding result.

I’ll discuss some of the details of this code when I get into the Controller and Model:

    /* EVENT CREATION AND SELECTION ------------------------------------------*/
   
    /**
     * Displays the DCEventSelect Stage for the Start Event field or
     * Finish Event field, based on the input parameter.
     * @param cType - indicates Start event or finish event.
     */
    private void displayEventSelectStage(CalendarOps.CalendarType cType) {
        if (eventListManager == null) {
            eventListManager = new EventListManager();            
        }
        int recCount = eventListManager.getEventList().size();
        String recordsReadInfo = "Read " + recCount + " Calendar record" + (recCount==1?"":"s") + ".";       
        statusField.setText(recordsReadInfo);
       
        try {
            FXMLLoader floader = new FXMLLoader();
            // Identify the URL of the FXML file used to lay out the UI.
            floader.setLocation(getClass().getResource("DCEventSelect.fxml"));
            // Load the FXML Object hierarchy.
            floader.load();
            // Our top-level hierarchy element is an AnchorPane.
            AnchorPane eventSelectRoot = floader.getRoot();
 
            DCEventSelect eventSelectStage = new DCEventSelect();
            DCEventSelect dialogController = (DCEventSelect)floader.getController();
            dialogController.setDialogStage(eventSelectStage);
           
            EventCalendar ec = new EventCalendar();          
            dialogController.setEventCalendar(ec);
           
            eventSelectStage.setTitle("Select " + cType + " Event");
            eventSelectStage.initModality(Modality.APPLICATION_MODAL);
            eventSelectStage.initOwner(this);
            eventSelectStage.setResizable(false);           
            eventSelectStage.setScene(new Scene(eventSelectRoot));           
 
            eventSelectStage.showAndWait();
            if (ec.getEventID() >= 0) {
                if (cType == CalendarOps.CalendarType.START) {
                    putSelectedRecordInStartCalendar(ec.getEventID());
                }
                else if (cType == CalendarOps.CalendarType.FINISH) {
                    putSelectedRecordInFinishCalendar(ec.getEventID());
                }
            }
        }
        catch (IOException e) {
            statusField.setText(eventFileNotFound);
        }
    }

Listing 10 – DateCalculatorUI Select Event Dialog Handling

package datecalculator;
 
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import dcops.EventListManager;
import dcops.EventCalendar;
 
/**
 * The DCEventSelect class implements the DCEventSelect Stage,
 * which displays a dialog containing a simple table of Event/Event Date rows,
 * and allows the user to select one Event for either the Start or Finish
 * Calendar. The UI content is generated from the corresponding FXML file,
 * DCEventSelect.fxml.
 *
 * @author Philip Nau
 *      TODO: Expand event list to include more events.
 *      TODO: Provide customized event lists (by category, country, region, time, etc).
 *      FIXME: Fix bug that returns incorrect event (row) after sorting a column.
 */
public class DCEventSelect extends Stage implements Initializable {
    /**
     * EventCalendar user-selected instance of EventCalendar from
     * the event table.
     */
    private EventCalendar selectedEvent;
    /**
     * Name of this Stage, for reference by the parent Stage.
     */
    private Stage dialogStage;
   
    // All items in the DCEventSelect FXML file are referenced here, used or not.
    @FXML private  AnchorPane contentAnchor;
    @FXML private  TableView selectTable;
    @FXML private  TableColumn eventColumn;
    @FXML private  TableColumn eventDateColumn;
    @FXML private  HBox buttonBox;
    @FXML private  Button selectButton;
    @FXML private  Button closeButton;
   
    /**
     * Initializes all event selection fields, and calls local methods to
     * handle other user actions.
     *
     * @param url Uniform Resource Locator of Resource Bundle.
     * @param rb  TODO: DCEventSelect text needs to go in a resource bundle.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        setButtonActions();
        buildEventSelectionList();
    }
 
    /**
     * Maintains the selected EventCalendar instance.
     * @param ec the selected EventCalendar instance.
     */
    public void setEventCalendar(EventCalendar ec) {
        selectedEvent = ec;
        selectedEvent.setEventID(-1);
    }
 
    /**
     * Sets the name of this dialog Stage.
     * @param s the name of this dialog Stage.
     */
    public void setDialogStage(Stage s) {
        dialogStage = s;
    }
   
    /**
     * Sets event handlers for all "actionable" controls.
     */   
    private void setButtonActions() {
        selectButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent e) {
                try {
                    selectedEvent.setEventID(selectTable.getSelectionModel().getSelectedIndex());
                }
                catch (NullPointerException npe) {  // Ignore an NPE
                }
            }
        });
       
        selectTable.setOnMouseClicked(new EventHandler() {
           @Override
            public void handle(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    try {
                        selectedEvent.setEventID(selectTable.getSelectionModel().getSelectedIndex());
                        dialogStage.close();
                    }
                    catch (NullPointerException npe) {  // Ignore an NPE
                    }                        
                }
            }           
        });
 
        closeButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent e) {
                dialogStage.close();
            }
        });
    }
 
    /**
     * Creates the two-column table of events/event dates in the DCEventSelect
     * window.
     */
    private void buildEventSelectionList() {
        EventListManager eventListManager = new EventListManager();
        ArrayList eventList = eventListManager.getEventList();
 
        selectTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        selectTable.setId("selectTable");
 
        eventColumn.setCellValueFactory(
            new PropertyValueFactory("eventName"));
        eventColumn.setResizable(true);
        eventColumn.setSortable(true);
        eventDateColumn.setCellValueFactory(
            new PropertyValueFactory("eventDate"));
        eventDateColumn.setResizable(true);
        eventDateColumn.setSortable(true);
        final ObservableList e = FXCollections.observableArrayList(eventList);
 
        selectTable.setItems(e);
    }
}

Listing 11 – DCEventSelect Class

package datecalculator;
 
import dcops.CalendarOps;
import dcops.EventCalendar;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
 
/**
 * The DCEventCreate class implements the DCEventCreate Stage,
 * which is a modal dialog that prompts the user to name a new Event being
 * generated from either the Start or Finish Calendar. The UI content is g
 * enerated from the corresponding FXML file, DCEventCreate.fxml.
 *
 * @author Philip Nau
 */
public class DCEventCreate extends Stage implements Initializable {
    /**
     * EventCalendar instance of StartCalendar or FinishCalendar.
     */
    private EventCalendar selectedEvent;
    /**
     * Name of this Stage, for reference by the parent Stage.
     */
    private Stage dialogStage;
       
    // All items in the DCEventCreate FXML file are referenced here, used or not.
    @FXML private  AnchorPane mainPane;
    @FXML private  Label titleLabel;
    @FXML private  Label eventDateLabel;
    @FXML private  TextField eventDateField;
    @FXML private  Label eventNameLabel;
    @FXML private  TextField eventNameField;
    @FXML private  Button okButton;
    @FXML private  Button cancelButton;
/**
     * Initializes all event selection fields, and calls local methods to
     * handle other user actions.
     *
     * @param url Uniform Resource Locator of Resource Bundle.
     * @param rb  TODO: DCEventSelect data needs to go in a resource bundle.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        setButtonActions();
    }
 
    /**
     * Maintains the selected EventCalendar instance.
     * @param ec the selected EventCalendar instance.
     */
    public void setEventCalendar(EventCalendar ec) {
        selectedEvent = ec;
        selectedEvent.setEventID(-1);
    }
   
    /**
     * Sets the name of this dialog Stage.
     * @param s the name of this dialog Stage.
     */
    public void setDialogStage(Stage s) {
        dialogStage = s;
    }
 
    /**
     * Places the String value of the Event date in the eventDateField
     * instance.
     */
    public void setDateField() {
        if (selectedEvent != null) {
            eventDateField.setText(CalendarOps.convertCalendarDateToString(selectedEvent));
        }
    }
   
    /**
     * Sets event handlers for all "actionable" controls.
     */   
    private void setButtonActions() {
        okButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent e) {
                if (setEventName()) {
                    dialogStage.close();
                }   
            }
        });
        cancelButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent e) {
                dialogStage.close();
            }
        });
    }
   
    /**
     * Retrieves the selected event name from the eventNameField
     * instance and updates the selectedEvent EventCalendar instance.
     * @return true if user specified an event name, false if not (field is blank).
     */
    private boolean setEventName() {
        String name = eventNameField.getText();
        String invalidEntry = "Event Name cannot be blank!";
       
        if (name.length() > 0) {
            selectedEvent.setEventName(name);
            return true;
        }
        else {
            eventNameField.setPromptText(invalidEntry);
            return false;
        }
    }
}

Listing 12 – DCEventCreate Class

    /**
     * Displays the DCEventCreate Stage to prompt for the name of the
     * Event to be saved. Upon return the new Event will have a user-specified
     * name.
     * @param ec - the new Event.
     */
    private void displayEventCreateStage(EventCalendar ec) {
        try {
            FXMLLoader floader = new FXMLLoader();
            // Identify the URL of the FXML file used to lay out the UI.
            floader.setLocation(getClass().getResource("DCEventCreate.fxml"));
            // Load the FXML Object hierarchy.
            floader.load();
            // Our top-level hierarchy element is an AnchorPane.
            AnchorPane eventCreateRoot = floader.getRoot();
 
            DCEventCreate eventCreateStage = new DCEventCreate();
            DCEventCreate dialogController = (DCEventCreate)floader.getController();
            dialogController.setEventCalendar(ec);
            dialogController.setDateField();
            dialogController.setDialogStage(eventCreateStage);
       
            eventCreateStage.setTitle("Specify New Event Name");
            eventCreateStage.initModality(Modality.APPLICATION_MODAL);
            eventCreateStage.initOwner(this);
            eventCreateStage.setResizable(false);           
            eventCreateStage.setScene(new Scene(eventCreateRoot));           
 
            eventCreateStage.showAndWait();
        }
        catch (IOException e) {
            statusField.setText(eventFileNotFound);
        }            
    }

Listing 13 – DateCalculatorUI Save Event Dialog Handling