Monday, January 26, 2015

Dynamically adding JavaFX controls

The JavaFX SceneBuilder works very well for constructing a static GUI; that is, a GUI whose controls are determined entirely at compile-time.  But sometimes it is useful to be able to add new controls dynamically at run-time.  The example below demonstrates one way to do this.

In this example, the user creates Button objects on-the-fly.  They are displayed using a ListViewer and stored in an ObservableList.  Each button inverts the capitalization of its label when clicked.  The Controller class is given below; constructing the rest of the GUI is an exercise for the reader.

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;

public class Controller {
    @FXML
    private Button add;
    @FXML 
    private TextField button;
    @FXML 
    private ListView<Button> visibleList;
 
    private ObservableList<Button> buttons = 
        FXCollections.observableArrayList();
 
    @FXML
    protected void initialize() {
        visibleList.setItems(buttons);
    }
 
    @FXML
    void addName() {
        Button b = new Button(button.getText());
        buttons.add(b);
        b.addEventHandler(MouseEvent.MOUSE_CLICKED, 
            (event -> b.setText(invertCapitals(b.getText()))));
        button.setText("");
    }
 
    public static String invertCapitals(String other) {
        return other.chars().mapToObj(Controller::flipCap)
                            .map(c -> Character.toString(c))
                            .reduce("", (s, c) -> s + c);
    }
 
    public static Character flipCap(int c) {
        if (c >= 'A' && c <= 'Z') {
           return (char)(c - 'A' + 'a');
        } else if (c >= 'a' && c <= 'z') {
           return (char)(c - 'a' + 'A');
        } else {
           return (char)c;
        }
    }
}

Highlighted in red, the key to making this all work is the addName() method, working in combination with the ObservableList buttons.   This method creates a new Button object in response to every click of the add button.  This object is added to our ObservableList, which makes it visible in the ListViewer.  Finally, we employ a Java 8 lambda to implement the event handler.

There is a pretty long list of event types to which one might respond.  Check out the documentation for the MouseEvent and KeyEvent classes to get started.

Here's a sample run of the program:

Java 8: Processing character streams

A common operation in a functional language like Haskell involves doing some processing on every character in a string.  For example:

Prelude Data.Char> let flip c = if (isUpper c) then (toLower c) else (toUpper c) 
Prelude Data.Char> map flip "This is a TEST"
"tHIS IS A test"

Using the Java 8 stream libraries to do a similar task is a little tricky:

public class StringDemo1 {
  public static String invertCapitals(String other) {
    return other.chars()
      .mapToObj(StringDemo1::flipCap)
      .map(c -> Character.toString(c))
      .reduce("", (s, c) -> s + c);
  }
 
  public static Character flipCap(int c) {
    if (c >= 'A' && c <= 'Z') {
       return (char)(c - 'A' + 'a');
    } else if (c >= 'a' && c <= 'z') {
       return (char)(c - 'a' + 'A');
    } else {
       return (char)c;
    }
  }
}

First, we need to convert the string to a stream. That is what the chars() method does. Unfortunately, it creates an IntStream. We use the mapToObj() method to turn the IntStream into a Stream<Character>. Having done this, we use map() to turn it into a Stream<String>, and finally we can use reduce() to combine it all into a single string.

While this does get the job done, it is very inefficient, as a new String object must be allocated for each reduction.  The following variation uses collect() to use a StringBuilder to accumulate the new String efficiently:

public class StringDemo2 {
  public static String invertCapitals(String other) {
    return other.chars()
      .mapToObj(StringDemo1::flipCap)
      .map(c -> Character.toString(c))
      .collect(StringBuilder::new,StringBuilder::append,StringBuilder::append)
      .toString();
  }
}


Using collect() is arguably not as aesthetically pleasing as reduce().  Here is an explanation of the arguments:
  • The first argument generates the collection that will be the accumulation target.
  • The second argument appends an element to the collection.
  • The third argument joins two collections.
This particular example is odd because StringBuilder::append is an overloaded static method.  The first one appends a String; the second one appends a CharSequence, an interface that StringBuilder implements.

Having compared the aesthetics, what about performance?

I found that the version with collect() could process a 100,000 character string in 19 milliseconds, while the version with reduce() requires 6520 milliseconds.

My test program is below.  It provides a nice demonstration of passing functions as parameter values.

import java.util.function.Function;
import java.util.stream.IntStream;

public class StringDemoComparison {
  public static void main(String[] args) {
    String input = 
      IntStream.iterate(1, x -> 1 + x)
               .mapToObj(x -> Character.toString((char)(x % 58 + 65)))
               .limit(100000)
               .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
               .toString();
  
    runDemo(StringDemo1::invertCapitals, input);
    runDemo(StringDemo2::invertCapitals, input);
  }
 
  public static void runDemo(Function func, String input) {
    long start = System.currentTimeMillis();
    String result = func.apply(input);
    long duration = System.currentTimeMillis() - start;
    System.out.println(result.length());
    System.out.println("Duration for: " + func.toString() + " is: " + duration);
  }
}


Friday, January 23, 2015

JavaFX ObservableList

Continuing my exploration of JavaFX, another very useful feature is its collections library.  In particular, it includes automatically observable versions of the List and Map interfaces from the Java Collections framework.  These can enable very convenient GUI updates.

For example, consider a program that allows a user to enter names into a list.  JavaFX allows us to store our data internally in an ObservableList; the GUI automatically updates whenever ObservableList changes.  Here's a screenshot of the program in action:

To create the interface in SceneBuilder:

  • Create a BorderPane as the root container.
  • Add an HBox to the top of the BorderPane.
    • Place a Button and a TextField in the HBox.
  • Add a ListView to the center of the BorderPane.
The following is the Controller class I wrote to implement the functionality:
package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

public class Controller {
 @FXML
 private Button add;
 @FXML
 private TextField name;
 @FXML
 private ListView visibleList;
 
 private ObservableList names = 
                FXCollections.observableArrayList();
 
 @FXML
 private void initialize() {
  visibleList.setItems(names);
 }
 
 @FXML
 private void addName() {
  if (name.getText().length() > 0) {
   names.add(name.getText());
   name.setText("");
  }
 }
}

In SceneBuilder:

  • Set up the above class as the controller.
  • Bind the three controls to the corresponding variables.
  • Bind the actions for the Button and TextView to addName().
Some notes on the code:
  • The key step here is calling the setItems() method of the visibleList object.  This is what binds the ObservableList names to the GUI.  Once this method is called, any changes to names will automatically appear in the list.
  • Note that this call must be in the initialize() method.  The visibleList object is created after the constructor call, so we can't put this in the constructor.  JavaFX ensures that initialize() is called before any other GUI code runs.
  • By attaching addName() to both the Button and the TextView, the user can add a name either way.

Friday, January 16, 2015

Creating a minimal JavaFX user interface in Java 8

The functional programming features of Java 8 represent a very important improvement to the language. Another important improvement is the replacement of Swing by JavaFX as the primary graphical user interface framework.  I've been using Swing for about a decade, and I've now started looking at JavaFX.

There are some great tutorials on the web about the JavaFX user interface library.  But they often run too long for my attention span.  I prefer enough detail to get a minimal program working, from which I can then tinker on my own.  So in this post I will demonstrate a minimal user interface that shows the basics of how to use Scene Builder, and how to connect the interface built in Scene Builder to some Java code.

To set up Eclipse:
  • Get the all-in-one Eclipse download that has JavaFX already set up.  
  • Make sure JDK 1.8 is installed and set up as the default.  
    • Check Preferences > Java > Installed JREs to be sure.  
  • Also make sure that Preferences > Java > Compiler > JDK Compliance is also set to 1.8.
To set up Scene Builder in Eclipse:
  • Download Scene Builder
  • In Eclipse, go to Preferences > JavaFX and specify the path to the Scene Builder executable.
Once Eclipse is set up, create a new JavaFX project:
  • Go to File > New > Other > JavaFX > JavaFX Project and click "Next"
  • Call the project "minimalfx" and click "Finish"
If you check out the project on the left, it should look something like this:

The Main.java program will contain the following code.  It will run as is, producing an empty window.  The line in red is the code we will need to change in order to incorporate our own interface:

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


public class Main extends Application {
 @Override
 public void start(Stage primaryStage) {
  try {
   BorderPane root = new BorderPane();
   Scene scene = new Scene(root,400,400);
   primaryStage.setScene(scene);
   primaryStage.show();
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
 
 public static void main(String[] args) {
  launch(args);
 }
}

Creating an interface with interesting behavior requires two things:
  • Creating an XML file describing the interface components.
  • Modifying the above code to interact with the components in the XML file.
To this end, then, we proceed by creating the XML file as follows:
  • Go to File > New > Other > Create FXML Document.
  • Give it the name "Gui".
  • Right-click on it and select "Open with SceneBuilder"
The Scene Builder window will look something like this:


The current interface is specified in the lower left corner, under the "Document" header.  Available components are given in the upper left corner, under the "Library" header, but separated into categories.  Click on "Controls" and drag a Button into the "CENTER" of the BorderPane.  It should then look like this:


Note that the middle area now contains a representation of our interface.  Select Preview > Show Preview in Window to see what it would look like as a running program:


Now let's modify our Main.java code so that it will run this interface: 
  • Save the interface in SceneBuilder.  It is a separate program from Eclipse, so although you can run it from Eclipse its updates are not automatically reflected therein.
  • Refresh the entire project in Eclipse.  
  • Go to Main.java.  As we can see in the code above, it creates an empty BorderPane that does nothing.  We want our newly created BorderPane to be in there instead.  So replace the original BorderPane creation code (in red) with the following:
  •             
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(Main.class.getResource("Gui.fxml"));
    BorderPane root = (BorderPane) loader.load();
    
  • Run Main. It should now look like this:

Note the 400x400 size.  That's the size set in Main.java, which we can change as we see fit.

Having created the interface using SceneBuilder, and having linked it up with our code, the last thing we will do is add behavior to the Button.  Create a new Java class called Controller.  Then enter the following code:

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class Controller {
 @FXML
 private Button clickMe;
 
 private int numClicks;
 
 public Controller() {}
 
 @FXML
 private void initialize() {
  numClicks = 0;
 }
 
 @FXML
 private void clickHandler() {
  numClicks += 1;
  clickMe.setText(numClicks % 2 == 0 ? "Even" : "Odd");
 }
}

Some notes about the code:
  • The @FXML annotation allows these otherwise private elements to be accessed by SceneBuilder.  These items become options we can select within SceneBuilder for our interface.
  • The initialize() method is called when the interface is created.  Typical constructor tasks go there.
  • The JavaFX components often have the same names as the original Java Abstract Windowing Toolkit.  It's important to get the imports right to make sure we have the right components.
Now we're ready to connect the interface to its handler, the Controller class:
  • Back in SceneBuilder, select "Controller" under the Document menu.
  • In the Controller Class box, enter "application.Controller".
  • Now click on the Button we added earlier.  On the right, select the "Code" item.
  • Look for the fx:id field under the Identity header.  This will be near the top of the Code region.
  • There will be a drop-down menu with a single option: clickMe.  
    • Select it.  
    • This binds the object in SceneBuilder to the named object in the Controller class.
  • Under the Main header, go to the "On Action" field.  In the drop-down menu, select clickHandler.  
  • Save the interface in SceneBuilder and refresh the project in Eclipse.
  • SceneBuilder should look like the following at this point:

From within Eclipse, run Main once again.  Once clicking begins, the button should now respond as programmed.

And that's all there is to it.  Happy tinkering!

Monday, January 12, 2015

Java 8: A functional programming language (?)

I'm preparing to teach some Java courses this semester.  I hadn't had a chance to look at Java 8 until this past week, and so far I am impressed.  This post is not really a tutorial; it's more of a small demonstration of the possibilities.

The key to Java 8's functional features is the new stream library.  A Stream in Java is a lazy sequence of values.  Each of the collection classes in the Collections framework has a .stream() method to get a stream view of its contents.  The Arrays class also contains a static method to get a stream view of an array.  The following example, which converts each command-line argument to an integer and prints the sum of the positive values only, demonstrates the framework nicely:

 import java.util.Arrays;  
 public class SimpleDemo1 {  
      public static void main(String[] args) {  
           System.out.println              
              (Arrays.stream(args).map(s -> Integer.parseInt(s))  
                                  .filter(x -> x > 0)  
                                  .reduce(0, (x, y) -> x + y));  
      }  
 }  

Here is an equivalent program in Haskell:

 module Main where  
 import System.Environment  
 main = do args <- getArgs  
           putStrLn $ show 
                    $ foldr (+) 0 
                    $ filter (\x -> x > 0) 
                    (map read args :: [Integer])

Here are some noteworthy similarities:
  • Both languages use -> to separate anonymous function arguments from the code. 
  • Java now includes (limited) type inference and an implicit return.
  • Both use lazy evaluation of the streams.
  • Both implementations have a similar amount of text:
    • Haskell: 30 words, 223 characters
    • Java: 31 words, 264 characters
And some noteworthy differences:
  • The fundamental paradigms of each language dictate the order in which the computations are written.  Note how the Haskell example has the higher-order functions placed in the reverse order of the Java example.
  • Lazy evaluation remains pervasive in Haskell, while being confined to this new little corner of Java.
  • The syntactic overhead of converting to streams in Java is not too bad, but it is not trivial either.  Still, for Java especially, this is an impressive reduction of boilerplate.
I will definitely be including this material in my Data Structures course this semester.  I plan to post periodically regarding how I approach the topic and how the students respond.