Channels ▼

Eric Bruno

Dr. Dobb's Bloggers

A JavaFX Text Editor: Part 1

November 18, 2012

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.

Tab

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:

Menu

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);
    }
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video