Channels ▼

Eric Bruno

Dr. Dobb's Bloggers

JavaFX 8: Combining New Language Features: Part 1

May 14, 2014

About two years ago, I wrote about a custom container that I created, which allows you to position other JavaFX controls side by side in a grid where you can drag the space between them to resize the individual controls. This is useful when showing lists of content side-by-side, for example, or any other case where you might want to adjust component sizing using a mouse. Additionally, the components will automatically resize if you shrink or enlarge the containing window. Consult this past blog to refresh yourself on the details of the SizeView custom container.

In this blog and the next few, we'll revisit this container, upgrade it to Java SE 8 and JavaFX 8, and combine it with the latest Java EE concepts to build an "Enterprise JavaFX" application to illustrate the power of Java 8 end-to-end. Not only will it run on any desktop or tablet that can run Java (i.e., a Mac, Windows 8 desktop or laptop, or a Microsoft Surface Pro tablet), but it will also run on embedded devices such as a Raspberry Pi. At the end of this blog series, the final JavaFX application will run in Kiosk mode on a Pi, where it will allow you to view and manipulate files on a remote server as though they were local. Before we dive in, let's take a quick look at what's new in JavaFX 8.

JavaFX 8 Features

JavaFX 8 brings with it all of the enhancements to the Java SE 8 JVM and language, including Lambdas, default methods, and so on. It also includes:

  • A new embedded JavaFX runtime with ARM support to work on constrained devices such as those similar to and including a Raspberry Pi
  • Support for GPU acceleration, as well as software-based acceleration for devices that lack a GPU
  • Integrated window management: X windows not required
  • OpenGLES2
  • The new Modena GUI theme, which looks consistent and attractive across platforms (Mac, Windows, and Linux)
  • 3D text support
  • Rich text support
  • HTML5 improvements via WebView (including the use of Nashorn)
  • New controls, such as TreeTableView and DatePicker
  • Multi-touch support for tablets such as the Microsoft Surface Pro
  • Integrated virtual keyboard (for tablets)
  • Hires/retina screen support
  • SwingNode: to include Swing code in your JavaFX application
  • Printing support enhanced

The Java File Manager

It's no secret that JavaFX is Java's client API going forward, and that Swing will more than likely no longer be enhanced. Instead, if you're interested in building platform-independent GUI applications that run on desktops and embedded devices with graphics support, JavaFX is the way to go. Not only is JavaFX easy to use, packed full of support for animations, gradients, special effects, and 3D features, JavaFX is really just Java, and there's no need to download anything else: JavaFX comes with the latest JDKs, including versions 7 and 8.

To build the basic file manager application, I'm going to combine another custom control I built, which I call the JavaFX HeaderList. It contains a base JavaFX ListView control to display a list of entries, but it adds an attractive list header, and search/filter functionality so that you can find an entry (or entries) within a large list of data quickly (see Figure 1).

header list
Figure 1: The HeaderList custom JavaFX control.

As you type into the list's search field, the contents dynamically update to filter out all entries except those that contain the matching text. I've written about similar functionality for a JavaScript list in the past.

JavaFX and Lambdas

If you look at the code from my previous blog on the SizeView container, or any JavaFX application for that matter, you'll see event-handling code that looks like this:


        Rectangle rect = ...;
        rect.setOnMouseEntered(
                new EventHandler<MouseEvent>() {
                    public void handle(MouseEvent me) {
                        setCursor(Cursor.W_RESIZE);
                    }
                });

With Java SE 8's Lambda expressions, this is simplified nicely to look like this:


        rect.setOnMouseEntered( me -> {
            setCursor(Cursor.W_RESIZE);
        });

It gets reduced to essentially the only the code that matters: changing the cursor as the mouse enters the shape's region. By the way, this works for controls and other JavaFX components as well. Lambdas are a welcome enhancement that seem made for UI event-handling code like this. For a more in-depth discussion on Lambda's, don't miss Cay Horstmann's excellent explanation here. After reading that, it should be clear that me is the MouseEvent object, which you can reference in the enclosed code between the brackets (my example doesn't use it but it could).

Most uses of Lambda's in JavaFX 8 are straightforward and beneficial like this example. However, I was challenged to convert the following non-Lambda code:


        // Listen for changes to the pane's child nodes
        ObservableList<Node> children = this.getChildren();
        children.addListener(
                new ListChangeListener<Node>() {
                    public void onChanged(ListChangeListener.Change change) {
                       // your code here...
                    }
                });

In summary, the intent is to listen for changes to an observable list of items (such as those that you place within a ListView control, or a JavaFX container). With it, you can be notified as items are added to it, removed, or changed. In this example, it's not important what we do when the list changes. Instead, how do you use a Lambda expression here?

At first, I tried the following:

        // Error: ambiguous since there are two addListener() methods
        children.addListener(changed -> {
            // your code here...
        }

Since there are two addListener methods, one that takes an InvalidationListener, and another that takes a ListChangedListener, we need to be more expressive. So I tried the following:

        // Error: this syntax is invalid
        children.addListener( (ListChangeListener<Node> changed) -> {
        }

The compiler won't accept the syntax above. Instead, you need to form the expression as a type cast on the parameter:


        children.addListener((ListChangeListener<Node>) (changed) -> {
            // your code here...
        });

Voila! Once I figured that out, I was able to simplify much of my JavaFX code; in this case, event and change listeners.

Handling Directories and Files

I decided a somewhat useful JavaFX sample application would be one that could be used on any Java-compatible device to manipulate files. For now, the sample application begins with the user's home directory on a local filesystem. But thanks to Java's relatively new FileSystem object, and its brand new Streams API and other subtle enhancements, we'll build a version of this JavaFX File Manager application that will work on local and remote filesystems via various protocols with minimal change.

First, let's explore how this works with the local filesystem. To begin, I wanted to build a File Manager application that works similar to how I use my Mac's Finder: in column mode. As I click on a folder, that folder's contents are displayed in the next column, and so on, until I choose a file (see Figure 2).

MacOS
Figure 2: A Mac OS X Finder in "column mode."

So my application contains a loop at startup that creates a number of HeaderList controls, places them within a SizeView container, and populates the first HeaderList with the contents of my home directory:


    // Create a few empty lists
    for ( int n = 0 ; n < CONTROLS; n++ ) {
        HeaderList list = new HeaderList("" , true);
        nodes.add( list );

        // Lambda that calls method to handle list mouse clicks            
        list.getList().setOnMouseClicked(me -> { 
            onListClicked(me,list);
        });
    }

    container = new SizeView();
    container.getChildren().setAll(nodes);

    // Create Scene graph and size components here

    // Show contents of user's home directory
    populateList( System.getProperty("user.home"), // path
                  null,                            // filename
                  0);                              // list index

The main code to populate a list traverses the home directory's contents (or the contents of a subsequently selected directory) like so:

    public void populateList(String path, String filename, int listIndex) {
        HeaderList list = nodes.elementAt(listIndex);
        list.getList().getSelectionModel().clearSelection();
        list.setHeader(path);
        
        // get file list
        List<String> results = new ArrayList<String>();
        File[] files = new File(path).listFiles();
        if ( files == null )
            return;

        for (File file : files) {
            if ( ! file.isHidden() ) {
                if ( file.isFile() ) {
                    results.add(file.getName());
                }
                else if ( file.isDirectory() ) {
                    results.add("<" + file.getName()+">" );
                }
            }
        }            

        list.setItems(results.toArray());
    }

Handling mouse clicks is a bit more complex (and in my sample application it's a bit hacked), but you get the point. The algorithm is as below; feel free to download and inspect the code if you're curious but it will get more interested in the next blog:

  • If the user clicks on a directory, check the list number and:
    • If there’s an available list to the right:
      • Display selected directory's contents there
      • Clear all remaining right-hand lists
    • If there are no available lists:
      • Add a new HeaderList on the right
      • Display selected directory's contents there
    • If the user clicks on a file
      • Open the file using the default viewer

As a result, what starts like the following:

JavaFX

Looks like this as you traverse your directory structure:

JavaFX

If you run the sample application, you'll notice that as you expand the application window, each column grows to take up the additional space and show more of the contents. In the next blog, we'll begin retrofitting the code to use some of the newer features of Java to make it easy to display files over various protocols (i.e., FTP).

Happy coding!
-EJB

JavaFX 8: Combining Language Features, Part 2

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