A JavaFX Text Editor: Part 1
Recently, I've been building a specialized file editor with JavaFX 2.x for my day job. While I can't share all the details of that project here, I can share the key findings I've made along the way. I've built a scaled-down editor app that illustrates a lot of what makes JavaFX awesome for building client GUI applications. In fact, despite all my work with JavaFX over the past 2+ years, there are many features I've just never worked with closely. Let's take a look at three of these features now: the TabPane
, MenuBar
, and FileChooser
interfaces. In the second part, we'll explore keyboard processing, the WebView
component, and the file editor itself.
Multi-Tab Editor
I've never created a multi-tabbed interface with JavaFX. It's not that I avoided it for any particular reason, it's just that I never had a reason to until recently. With JavaFX, it took me all of five minutes to learn what I needed. Here's a summary with some code.
First, create a TabPane
component to contain the actual tabs:
TabPane tabPane = new TabPane();
Next, create a Tab
to and place it into the TabPane
:
Tab tab = new Tab(); tabPane.getTabs().add(tab);
Next, add content to the Tab
itself (this can be a control or a layout component such as an HBox, VBox, Group, and so on):
tab.setContent(content.getRoot());
Set the tab's title text:
tab.setText("My New Tab");
By default, the tab is added to the end of existing tabs and is not selected. If you'd like the tab to become the selected, visible tab, call the following code:
SingleSelectionModel<Tab> selectionModel = tabPane.getSelectionModel(); selectionModel.select(tab);
If you'd like to know when the selected tab changes, add the following code:
tabPane.getSelectionModel().selectedItemProperty().addListener( new ChangeListener<Tab>() { @Override public void changed( ObservableValue<? extends Tab> tab, Tab oldTab, Tab newTab) { // Process event here... } });
The end result is a simple set of tabs in your application UI, as shown here.

Next, let's examine another common navigation tool: menus.
JavaFX Menus and Processing
Believe it or not, I've never needed to create application menus with JavaFX until recently. All of my work with JavaFX has been building custom JavaFX controls to be integrated into an existing Java Swing application; hence no need for menus. Again, it took only a matter of minutes to learn the API and build the menu system my application needed. Here's how.
First, create a MenuBar
component to hold the menu choices themselves:
MenuBar menuBar = new MenuBar();
Next, create a Menu
entry, such as one that says "File"
:
Menu menuFile = new Menu("File");
Next, add a set of menu items (under the File
menu in this case), such as "New"
, "Open"
, and "Save"
:
MenuItem menuFileNew = new MenuItem("New"); MenuItem menuFileOpen = new MenuItem("Open"); MenuItem menuFileSave = new MenuItem("Save"); menuFile.getItems().addAll( menuFileNew, menuFileOpen, menuFileSave);
What good is a menu item if don't know when a user chooses it? With JavaFX, you simply add an EventHandler
:
menuFileNew.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { // Process the user menu choice here… } });
To add all of your Menu Entry
objects to a MenuBar
, simply call addAll()
:
Menu menuView = new Menu("View"); // … menuBar.getMenus().addAll(menuFile, menuView);
After adding the menus and menu items, you have something similar to this:

Simple File Editor Beginnings
Below is the complete menu and tab code in the beginnings of the simple JavaFX file editor application. In the next part, we'll fill in the remaining details of the application, and I'll present all of the code.
public class JavaFXSimpleEditor extends Application { private static final String BROWSER = "Browser"; private static final String EDITOR = "new editor"; private static int browserCnt = 1; private Stage primaryStage; private TabPane tabPane; private Vector<SimpleEditor> editors = new Vector(); private SimpleEditor currentEditor = null; private Stage getStage() { return primaryStage; } @Override public void start(Stage primaryStage) { this.primaryStage = primaryStage; // Add an empty editor to the tab pane tabPane = new TabPane(); tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() { @Override public void changed(ObservableValue<? extends Tab> tab, Tab oldTab, Tab newTab) { // As the current tab changes, reset the var that tracks // the editor in view. This is used for tracking modified // editors as the user types currentEditor = null; } }); // Create main app menu MenuBar menuBar = new MenuBar(); // File menu and subitems Menu menuFile = new Menu("File"); MenuItem menuFileNew = new MenuItem("New"); menuFileNew.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { createNew(EDITOR); } }); MenuItem menuFileOpen = new MenuItem("Open"); menuFileOpen.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { chooseAndLoadFile(); } }); MenuItem menuFileSave = new MenuItem("Save"); menuFileSave.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { saveFileRev(); } }); MenuItem menuFileExit = new MenuItem("Exit"); menuFileExit.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { getStage().close(); } }); menuFile.getItems().addAll( menuFileNew, menuFileOpen, menuFileSave, new SeparatorMenuItem(), menuFileExit); Menu menuView = new Menu("View"); MenuItem menuViewURL = new MenuItem("Web Page"); menuViewURL.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { createNew(BROWSER); } }); menuView.getItems().addAll(menuViewURL); menuBar.getMenus().addAll(menuFile, menuView); // layout the scene VBox layout = VBoxBuilder.create().spacing(10).children(menuBar, tabPane).build(); layout.setFillWidth(true); // display the scene final Scene scene = new Scene(layout, 800, 600); scene.setOnKeyPressed(new EventHandler<KeyEvent>() { public void handle(KeyEvent ke) { // ... } }); // Bind the tab pane width/height to the scene tabPane.prefWidthProperty().bind(scene.widthProperty()); tabPane.prefHeightProperty().bind(scene.heightProperty()); // Certain keys only come through on key release events // such as backspace, enter, and delete scene.setOnKeyReleased(new EventHandler<KeyEvent>() { public void handle(KeyEvent ke) { // ... } }); scene.setOnKeyTyped(new EventHandler<KeyEvent>() { public void handle(KeyEvent ke) { // ... } }); // Make sure one new editor is open by default createNew(EDITOR); primaryStage.setScene(scene); primaryStage.setTitle("Simple Editor / Browser"); primaryStage.show(); } /* …. */ public static void main(String[] args) { launch(args); } }