Open and Save Menus in JavaFX

I have already written a tutorial on JavaFX FileChoosers. However, the previous example did not integrate a JavaFX MenuBar or an FXML file into the mix. Typically, you don’t create or design FileChoosers in FXML, though some dialogs can be designed in FXML. However, FileChoosers tend to be created and shown programmatically, rather than in FXML for display later.

This JavaFX FileChooser example shows a basic MenuBar with Open, Save, SaveAs and Quit MenuItems as shown in the image below.

JavaFX FileChooser Menus
A simple JavaFX MenuBar with Open, Save, Save As and Quit MenuItems.

Note: You can see a complete list of FileChooser related tutorials on the JavaFX FileChooser page.

First, let’s look at the Main class. If you’ve done a few of the examples here on “What is JavaFX?”, then you’ve already seen this a million times. You have a JavaFX Application that calls launch in the main class. This results in a call to start with the primary JavaFX Stage object. The stage is going to be your main window. You load the stage with your scene, which in turn has a JavaFX container as it’s sole child node.

In this case the child node of the JavaFX Scene is a BorderPane that was designed in SceneBuilder. We get the FXML resource by using the current class’s getResource() method, and then call the provided URL with the FXMLLoader. The JavaFX FXMLLoader also handles loading the Controller specified in the FXML and injecting method references and node references as needed.

Oh, … and the Main class also sets the JavaFX Stage title to “JavaFX Open-Save Example.”

package com.whatisjavafx;
	
import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class Main extends Application {
  public static Stage primaryStage;
  
	@Override
	public void start(Stage stage) {
	  
	  primaryStage = stage;
	  
    try {
      URL url = 
          getClass()
          .getResource("OpenSave.fxml");
      BorderPane root = FXMLLoader.load(url);

      Scene scene = new Scene(root);
      stage.setScene(scene);
      stage
      .setTitle("JavaFX Open-Save Example");
      stage.show();
    } catch(Exception e) {
      e.printStackTrace();
    }
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}

 

Below here is the FXML. This can be opened in SceneBuilder for a easier time inspecting it. If you just want the rundown without the hassle of opening it in SceneBuilder, then here it is.

The FXML describes a BorderPane with a JavaFX MenuBar loaded at the top. It turns out BoarderPanes work very nicely with JavaFX MenuBars. You load the main app container in the center with the MenuBar in the top. It’s a great use of the BoarderPane container.

In our case, we have a TextArea in the center of the JavaFX BoarderPane. Our MenuBar has only one Menu. That Menu contains four JavaFX MenuItems and a couple of JavaFX SeparatorMenuItems to make the Menu more appealing, visually.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane
 maxHeight="-Infinity"
 maxWidth="-Infinity"
 minHeight="-Infinity"
 minWidth="-Infinity"
 prefHeight="400.0"
 prefWidth="600.0"
 xmlns="http://javafx.com/javafx/8.0.65"
 xmlns:fx="http://javafx.com/fxml/1"
 fx:controller="com.whatisjavafx.OpenSaveController">
 <top>
  <MenuBar BorderPane.alignment="CENTER">
   <menus>
    <Menu
     mnemonicParsing="false"
     text="File">
     <items>
      <MenuItem
       fx:id="openMI"
       mnemonicParsing="false"
       onAction="#openAction"
       text="Open" />
      <SeparatorMenuItem mnemonicParsing="false" />
      <MenuItem
       fx:id="saveMI"
       disable="true"
       mnemonicParsing="false"
       onAction="#saveAction"
       text="Save" />
      <MenuItem
       fx:id="saveAsMI"
       mnemonicParsing="false"
       onAction="#saveAsAction"
       text="Save As..." />
      <SeparatorMenuItem mnemonicParsing="false" />
      <MenuItem
       fx:id="quitMI"
       mnemonicParsing="false"
       onAction="#quitAction"
       text="Quit" />
     </items>
    </Menu>
   </menus>
  </MenuBar>
 </top>
 <center>
  <TextArea
   fx:id="myTextArea"
   prefHeight="200.0"
   prefWidth="200.0"
   text="Some sample text..."
   BorderPane.alignment="CENTER" />
 </center>
</BorderPane>

 

Finally, here’s the controller that does all the work. I left all the FXML defined objects at the top, and put the methods that do all the work down below.

The System.exit(0) is nice for a quick exit. A more advanced application might want to handle shutdown procedures such as saving data before exiting.

package com.whatisjavafx;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Scanner;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;

public class OpenSaveController {

  // First we deal with the FXML
  // dependency injection

  @FXML
  private MenuItem openMI;

  @FXML
  private MenuItem saveMI;

  @FXML
  private MenuItem saveAsMI;

  @FXML
  private MenuItem quitMI;

  @FXML
  private TextArea myTextArea;

  @FXML
  void openAction(ActionEvent event) {
    handleOpenClick();
  }

  @FXML
  void saveAction(ActionEvent event) {
    
    // The saveMI is disabled until
    // saveAsAction is invoked at least
    // one time.

    handleSaveClick();
  }

  @FXML
  void saveAsAction(ActionEvent event) {
    handleSaveAsClick();
  }
  
  @FXML
  void quitAction(ActionEvent event) {
    
    // If you wanted to be fancy,
    // you could do a check of the
    // current application's data
    // to see if the file has been
    // saved before exiting and
    // losing any unsaved data.
    
    System.exit(0);
  }

  @FXML
  void initialize() {}
  
  
  
  ////////////////////////////////////////
  // The following methods and variables
  // might be separated into a utility
  // class in a more flushed out
  // JavaFX application.
  
  File dataFile = null;
  
  private void handleOpenClick() {
    
    // A fuller JavaFX app would
    // want to perform a check
    // to make sure the current
    // data has been saved.
    
    //creating JavaFX file chooser
    FileChooser fc = new FileChooser();
    fc.setTitle("Get Text");
    fc.getExtensionFilters().addAll(
        new ExtensionFilter(
            "Text Files", 
            "*.txt"),
        new ExtensionFilter(
            "All Files", 
            "*.*"));
    
    //showing the file chooser
    File phil = 
        fc.showOpenDialog(
            Main.primaryStage);
    
    // checking that a file was
    // chosen by the user
    if (phil != null) {
      
      // opening a scanner
      try (Scanner scan = 
          new Scanner(phil)) {
        
        // grabbing the file data
        String content = 
            scan
            .useDelimiter("\\Z")
            .next();
        myTextArea.setText(content);
        
        // saving the file for
        // use by the saveMI
        dataFile = phil;
        
        // enabling saveMI
        saveMI.setDisable(false);
        
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      }
    }
  }
  
  private void handleSaveAsClick() {
    FileChooser fc = new FileChooser();
    fc.setTitle("Save Text");
    fc.getExtensionFilters().addAll(
        new ExtensionFilter(
            "Text Files", 
            "*.txt"),
        new ExtensionFilter(
            "All Files", 
            "*.*"));
    File phil = 
        fc.showSaveDialog(
            Main.primaryStage);
    if (phil != null) {
      try (PrintStream ps = 
          new PrintStream(phil)) {

        ps.print(myTextArea.getText());
        
        // saving the file for
        // use by the saveMI
        dataFile = phil;
                
        // enabling saveMI
        saveMI.setDisable(false);

      } catch (FileNotFoundException e) {
        e.printStackTrace();
      }
    }
  }

  private void handleSaveClick() {
    try (PrintStream ps = 
        new PrintStream(dataFile)) {
      
      ps.print(myTextArea.getText());
      
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }
  }
}

 

For “opening” and “saving as,” I create a JavaFX FileChooser and add a few valid extensions. In this case the only extensions that make sense were extensions of any and text. This is done with JavaFX ExtensionFilters.

ExtensionFilters are simple to create and use with JavaFX FileChoosers. Once you have an instance of the FileChooser, make a call to the FileChooser’s getExtensionFilters() method, and then call addAll(). The addAll() method in our example only takes two anonymous ExtensionFilters, but there is no limit to the number of ExtensionFilters the method can take. You could just as easily have used 20 as two ExtensionFilters as parameters to the allAll() method.

The JavaFX ExtensionFilter constructor takes a short description of the file type the filter accepts as it’s first parameter. The rest of the parameters are the list of acceptable file extensions the application takes. File extensions are always in the format of *.<extension text>

So that’s all there is to a JavaFX File menu. It’s pretty straightforward if you’ve ever written a File menu before.

Leave a Reply

Your email address will not be published. Required fields are marked *