JavaFX Fundamentals - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 14. JavaFX Fundamentals

The JavaFX 8.0 API is Java’s next generation GUI toolkit for developers to build rich cross-platform applications. JavaFX 8.0 is an update from JavaFX 2.2 that’s based on a scene graph paradigm (retained mode) as opposed to the traditional immediate mode style rendering. JavaFX’s scene graph is a tree-like data structure that maintains vector-based graphic nodes. The goal of JavaFX is to be used across many types of devices such as mobile devices, smartphones, TVs, tablet computers, and desktops.

Before the creation of JavaFX, the development of rich Internet applications involved the gathering of many separate libraries and APIs to achieve highly functional applications. These separate libraries include Media, UI controls, Web, 3D, and 2D APIs. Because integrating these APIs can be rather difficult, the talented engineers at Sun Microsystems (now Oracle) created a new set of JavaFX libraries that combine all the same capabilities under one roof. JavaFX is the Swiss Army Knife of GUIs. JavaFX 8 is a pure Java (language) API that allows developers to leverage existing Java libraries and tools.

Depending on who you talk to, you will likely encounter different definitions of “user experience” (or in the UI world, UX). But one fact still remains—users will always demand better content and increased usability from GUI applications. In light of this fact, developers and designers often work together to craft applications to fulfill this demand. JavaFX provides a toolkit that enables both the developer and designer (in some cases, they are the same person) to create functional yet esthetically pleasing applications. Another thing to acknowledge is that if you are developing a game, media player, or the usual enterprise application, JavaFX will not only assist in developing richer UIs but you’ll also find that the APIs are extremely well designed to greatly improve developer productivity (I’m all about the user of the API’s perspective).

There are entire books written on JavaFX, and it would be impossible to cover all the capabilities of the toolkit. Hopefully, these recipes can steer you in the right direction by providing practical and real-world examples. So I encourage you to explore other resources to gain further insight into JavaFX. I highly recommend the books: Pro JavaFX Platform (Apress, 2009), Pro JavaFX 2.0 Platform (Apress, 2012), Pro JavaFX 8, and JavaFX 8: Introduction by Example (Apress 2014). These books go in depth to help you create professional grade applications. In this chapter you will learn the fundamentals of JavaFX to rapidly develop Rich Internet applications. It provides you with a solid foundation for working with JavaFX.

Image Note For releases of JavaFX prior to JavaFX 8, the SDK was a separate download from the standard JDK. That is, the JavaFX 1.x and 2.x SDKs had to be downloaded and installed separately. JavaFX 8 changes that requirement, as it comes as part of JDK 8. This book covers JavaFX 8 only, although many of the solutions may function properly on JavaFX 2.x. If you need to install JavaFX 2.x, refer to the online documentation (http://docs.oracle.com/javafx/) or a book that covers JavaFX 2.x, such as JavaFX 2.0: Introduction by Example, which was written by Carl Dea and published by Apress. To see the online JavaFX 8 documentation, visit http://docs.oracle.com/javase/8/javase-clienttechnologies.htm.

14-1. Creating a Simple User Interface

Problem

You want to create, code, compile, and run a simple JavaFX Hello World application.

Solution 1

Develop a JavaFX Hello World application using the JavaFX project-creation wizard in the NetBeans IDE.

CREATING A JAVAFX HELLO WORLD APPLICATION IN NETBEANS

To quickly get started with creating, coding, compiling, and running a simple JavaFX Hello World application using the NetBeans IDE, follow these steps:

1. Launch NetBeans IDE.

2. From the File menu, select New Project.

3. Under Choose Project and Categories, select the JavaFX folder.

4. Under Projects, select the JavaFX Application and click Next.

a. Note: If this is your first JavaFX project in NetBeans, the JavaFX module may automatically activate at this time.

5. Specify HelloWorldMain for your project name.

6. Change or accept the defaults for the Project Location and Project Folder fields.

7. Ensure that the JavaFX Platform is set to JDK 1.8. Leave the Create a Custom Preloader box checked.

8. Make sure the Create Application Class option is selected. Click Finish.

9. In the NetBeans IDE on the Projects tab, select the newly created project. Open the Project Properties dialog box to verify that the Source/Binary format settings are JDK 8. Click Sources under Categories.

10.After closing the Java Platform Manager window, click OK to close the Project Properties window.

11.To run and test your JavaFX Hello World application, access the Run menu and select Run Main Project. You could also right-click on the project directory and choose Run from the contextual menu.

Figure 14-1 shows a simple JavaFX Hello World application launched from the NetBeans IDE.

9781430268277_Fig14-01.jpg

Figure 14-1. JavaFX Hello World launched from the NetBeans IDE

Solution 2

Use your favorite editor to code your JavaFX Hello World application. Once the Java file is created, you will use the command-line prompt to compile and run your JavaFX application. Following are the steps to create a JavaFX Hello World application to be compiled and run on the command-line prompt.

CREATING A JAVAFX HELLO WORLD APPLICATION IN A TEXT EDITOR

To quickly get started:

1. Copy and paste the following code into your favorite editor and save the file as HelloWorldMain.java.

The following source code is a JavaFX Hello World application:

package org.java8recipes.chapter14.recipe14_01;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import javafx.scene.Group;
public class HelloWorldMain extends Application {

final Group root = new Group();
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}

@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World");
Scene scene = new Scene(root, 300, 250);
Button btn = new Button();
btn.setLayoutX(100);
btn.setLayoutY(80);
btn.setText("Hello World");
btn.setOnAction((event) -> {
System.out.println("Hello World");
});
root.getChildren().add(btn);
primaryStage.setScene(scene);
primaryStage.show();
}
}

2. After saving the file named HelloWorldMain.java, use the command-line prompt to navigate to the file.

3. Compile the source code file HelloWorldMain.java using the Java compiler javac:

javac -d . HelloWorldMain.java

4. Run and test your JavaFX Hello World application. Assuming you are located in the same directory as the HelloWorldMain.java file, type the following command to run your JavaFX Hello World application from the command-line prompt:

java org.java8recipes.chapter14.recipe14_01.HelloWorldMain

image Note This class can also be created within an existing JDK 8 application. For instance, the project that contains the sources for this book contains all of the JavaFX recipes in the org.java8recipes.chapter14 source package. This is possible since JavaFX no longer requires additional configuration; it is already part of any JDK 8 project.

How It Works

Following are descriptions of the two solutions. Both solutions require JavaFX 8 or JavaFX 2.x along with JDK 7. Solution 1 demonstrates how to build a JavaFX application using the NetBeans IDE. Solution 2 covers the development of a simple JavaFX application via your favorite text editor, and use of the command-line or terminal to compile and execute JavaFX programs.

The NetBeans IDE makes it very easy to develop a JavaFX application via a JavaFX project. In fact, NetBeans provides a template Hello World application after following the JavaFX project-creation wizard. This is a great solution for beginning any JavaFX application, as it provides a great starting point for building more sophisticated solutions.

To create a simple JavaFX Hello World application using your favorite text editor, follow Solution 2, Steps 1 and 2. To compile and run your Hello World program on the command line, follow Solution 2, Steps 3 and 4. Once you enter the source code into your favorite editor and save the source file, compile and run the JavaFX program. Open the command-line or terminal window and navigate to the directory location of the Java file named HelloWorldMain.java.

Here, we review a way to compile the file using the command javac -d . HelloWorldMain.java. You will notice the -d . before the file name. This lets the Java compiler know where to put class files based on their package name. In this scenario, the HelloWorldMainpackage statement is helloworldmain, which will create a subdirectory under the current directory. The following commands will compile and run the JavaFX Hello World application:

cd \<path to project>\org\java8recipes\chapter14\recipe14_01

javac –d . HelloWorldMain.java

java helloworldmain.HelloWorldMain

Image Note There are many ways to package and deploy JavaFX applications. To learn more, see “Learning How to Deploy and Package JavaFX Applications” at http://docs.oracle.com/javafx/2/deployment/jfxpub-deployment.htm. For in-depth JavaFX deployment strategies, see Oracle’s “Deploying JavaFX Applications” at http://docs.oracle.com/javafx/2/deployment/deployment_toolkit.htm.

In both solutions you’ll notice in the source code that JavaFX applications extend the javafx.application.Application class. The Application class provides application lifecycle functions such as launching and stopping during runtime. This also provides a mechanism for Java applications to launch JavaFX GUI components in a threadsafe manner. Keep in mind that synonymous to Java Swing’s event dispatch thread, JavaFX has its own JavaFX application thread. New in JavaFX 8, it is possible for the event dispatch thread and the JavaFX application thread to be merged (see Recipe 14-18).

Taking a look at the code, in the main() method’s entry point you launch the JavaFX application by simply passing in the command-line arguments to the Application.launch() method. Once the application is in a ready state, the framework internals will invoke the start()method to begin. When the start() method is invoked, a JavaFX javafx.stage.Stage object is available for the developer to use and manipulate.

You’ll notice that some objects are oddly named, such as Stage and Scene. The designers of the API have modeled things similar to a theater or a play in which actors perform in front of an audience. With this same analogy, in order to show a play, there are basically one-to-many scenes that actors perform in. And, of course, all scenes are performed on a stage. In JavaFX the Stage is equivalent to an application window similar to Java Swing API JFrame or JDialog. You may think of a Scene object as a content pane capable of holding zero-to-many Nodeobjects. A Node is a fundamental base class for all scene graph nodes to be rendered. A scene graph is a tree data structure that maintains an internal model of all nodes or graphical objects that are part of an application. Commonly used nodes are UI controls and Shape objects. Similar to a tree data structure, a scene graph will contain children nodes by using a container class Group. You’ll learn more about the Group class later when you look at the ObservableList, but for now think of them as Java Lists or Collections that are capable of holding Nodes.

Once the child nodes have been added, you set the primaryStage’s (Stage) scene and call the show() method on the Stage object to show the JavaFX window.

One last thing: in this chapter most of the example applications are structured the same as this example, in which recipe code solutions will reside inside the start() method. Most of the recipes in this chapter follow the same pattern. For the sake of brevity, much of the boilerplate code is not shown. To see the full source listings of all the recipes, download the source code from the book’s website.

14-2. Drawing Text

Problem

You want to draw custom text within a JavaFX application.

Solution

Create Text nodes to be placed on the JavaFX scene graph by utilizing the javafx.scene.text.Text class. As Text nodes are to be placed on the scene graph, you decide you want to create randomly positioned Text nodes rotated around their (x, y) positions scattered about the scene area.

The following code implements a JavaFX application that displays Text nodes scattered about the scene graph with random positions and colors:

primaryStage.setTitle("Chapter 14-2 Drawing Text");
Group root = new Group();
Scene scene = new Scene(root, 300, 250, Color.WHITE);
Random rand = new Random(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
int x = rand.nextInt((int) scene.getWidth());
int y = rand.nextInt((int) scene.getHeight());
int red = rand.nextInt(255);
int green = rand.nextInt(255);
int blue = rand.nextInt(255);

Text text = new Text(x, y, "Java 8 Recipes");

int rot = rand.nextInt(360);
text.setFill(Color.rgb(red, green, blue, .99));
text.setRotate(rot);
root.getChildren().add(text);
}

primaryStage.setScene(scene);
primaryStage.show();

Figure 14-2 shows random Text nodes scattered about the JavaFX scene graph.

9781430268277_Fig14-02.jpg

Figure 14-2. Drawing text in random places

How It Works

To draw text in JavaFX, you create a javafx.scene.text.Text node to be placed on the scene graph (javafx.scene.Scene). In this example you’ll notice text objects with random colors and positions scattered about the Scene area.

First, you create a loop to generate random (x,y) coordinates to position Text nodes. Second, you create random color components between (0–255 RGB) to be applied to the Text nodes. Third, the rotation angle (in degrees) is a randomly generated value between (0–360 degrees) to cause the text to be slanted. The following code creates random values that will be assigned to a Text node’s position, color, and rotation:

int x = rand.nextInt((int) scene.getWidth());
int y = rand.nextInt((int) scene.getHeight());
int red = rand.nextInt(255);
int green = rand.nextInt(255);
int blue = rand.nextInt(255);
int rot = rand.nextInt(360);

Once the random values are generated, they will be applied to the Text nodes, which will be drawn onto the scene graph. The following code snippet applies position (x, y), color (RGB), and rotation (angle in degrees) onto the Text node:

Text text = new Text(x, y, "Java 8 Recipes");
text.setFill(Color.rgb(red, green, blue, .99));
text.setRotate(rot);

root.getChildren().add(text);

You will begin to see the power of the scene graph API by its ease of use. Text nodes can be easily manipulated as if they were Shapes. Well, actually they are Shapes. Defined in the inheritance hierarchy, Text nodes extend from the javafx.scene.shape.Shape class and are therefore capable of doing interesting things such as being filled with colors or rotated about an angle. Although the text is colorized, this still tends to be somewhat boring. However, in the next recipe you will learn how to change a text’s font.

14-3. Changing Text Fonts

Problem

You want to change text fonts and add special effects to the Text nodes.

Solution 1

Create a JavaFX application that uses the following classes to set the text font and apply embedded effects to Text nodes:

· javafx.scene.text.Font

· javafx.scene.effect.DropShadow

· javafx.scene.effect.Reflection

The code that follows sets the font and applies effects to Text nodes. It uses the Serif, SanSerif, Dialog, and Monospaced fonts along with the drop shadow and reflection effects:

primaryStage.setTitle(“Chapter 14-3 Changing Text Fonts”);
Group root = new Group();
Scene scene = new Scene(root, 330, 250, Color.WHITE);

// Serif with drop shadow
Text java8Recipes2 = new Text(50, 50, “Java 8 Recipes”);
Font serif = Font.font(“Serif”, 30);
java8Recipes2.setFont(serif);
java8Recipes2.setFill(Color.RED);
DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(2.0f);
dropShadow.setOffsetY(2.0f);
dropShadow.setColor(Color.rgb(50, 50, 50, .588));
java8Recipes2.setEffect(dropShadow);
root.getChildren().add(java8Recipes2);

// SanSerif
Text java8Recipes3 = new Text(50, 100, “Java 8 Recipes”);
Font sanSerif = Font.font(“SanSerif”, 30);
java8Recipes3.setFont(sanSerif);
java8Recipes3.setFill(Color.BLUE);
root.getChildren().add(java8Recipes3);

// Dialog
Text java8Recipes4 = new Text(50, 150, “Java 8 Recipes”);
Font dialogFont = Font.font(“Dialog”, 30);
java8Recipes4.setFont(dialogFont);
java8Recipes4.setFill(Color.rgb(0, 255, 0));
root.getChildren().add(java8Recipes4);

// Monospaced
Text java8Recipes5 = new Text(50, 200, “Java 8 Recipes”);
Font monoFont = Font.font(“Monospaced”, 30);
java8Recipes5.setFont(monoFont);
java8Recipes5.setFill(Color.BLACK);
root.getChildren().add(java8Recipes5);

Reflection refl = new Reflection();
refl.setFraction(0.8f);
java8Recipes5.setEffect(refl);

primaryStage.setScene(scene);
primaryStage.show();

Figure 14-3 shows the JavaFX application with various font styles and effects (drop shadow and reflection) applied to the Text nodes.

9781430268277_Fig14-03.jpg

Figure 14-3. Changing text fonts

Solution 2

Make use of the new TextFlow node to assist in stringing rich text together. Use an FXML file to construct an object graph, and then apply CSS styles to the nodes of the graph within the FXML. This solution provides a better path for those who are more comfortable working in a markup language than in Java code. It also demonstrates how to use a style sheet to declare the styles for your application.

First, let’s take a look at the FXML that’s used to construct the layout. The following lines of markup construct a scene graph that contains a Pane enclosing a TextFlow. The TextFlow contains a series of Text nodes, each of which has different styles applied. The following listing contains the sources for textfonts.fxml.

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

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<Scene width="200" height="75" fill="white" xmlns:fx="http://javafx.com/fxml">
<stylesheets>
<URL value="@textfonts.css"/>
</stylesheets>
<Pane fx:id="pane">

<TextFlow styleClass="mainmessage">
<Text styleClass="span1">Hello </Text>
<Text text=" "/>
<Text styleClass="span2, large">Java</Text>
<Text styleClass="span3, slant">FX</Text>
<Text text=" "/>
<Text styleClass="cool">8</Text>

</TextFlow>
</Pane>
</Scene>

Within the FXML, a cascading style sheet named textfonts.css is imported. The following listing contains the styles, which reside in textfonts.css.

.mainmessage {
-fx-font-family: "Helvetica";
-fx-font-size: 30px;
}

.span1 {
-fx-color: "red";
}

.span2 {
-fx-font-family: "Serif";
-fx-font-size: 30px;
-fx-color: "red";
}

.span3 {
-fx-font-family: "Serif";
-fx-font-size: 30px;
-fx-fill: "orange";
-fx-font-style: italic;
}

.cool {
-fx-effect: dropshadow(gaussian, gray, 8, 0.5, 8, 8);
}

Lastly, a standard JavaFX application class is used to instantiate the example. The following sources are taken from ChangingTextFontsSolution2.java, and they demonstrate how to load the FXML and construct the stage.

@Override
public void start(Stage stage) throws Exception {
stage.setTitle("Chapter 14-3 Changing Text Fonts Using TextFlow and FXML");
stage.setScene((Scene) FXMLLoader.load(getClass().getResource("textfonts.fxml")));
stage.show();
}

The resulting application will render a scene that resembles the result shown in Figure 14-4.

9781430268277_Fig14-04.jpg

Figure 14-4. TextFlow and FXML

How It Works

Solution 1 demonstrates how to apply fonts to text using standard Java code. Vector-based graphics allow you to scale shapes and apply effects without issues of pixilation (jaggies). JavaFX nodes use vector-based graphics. In each Text node, you can create and set the font to be rendered onto the scene graph. Here is the code to create and set the font on a Text node:

Text java8Recipes2 = new Text(50, 50, "Java 8 Recipes");
Font serif = Font.font("Serif", 30);
Java8Recipes2.setFont(serif);

In solution 1, the drop shadow is a real effect (DropShadow) object and is actually applied to a single Text node instance. The DropShadow object is set to be positioned based on an x and y offset in relation to the Text node. You also can set the color of the shadow; here we set it to gray with a .588 opacity. Following is an example of setting a Text node’s effect property with a drop shadow effect (DropShadow):

DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(2.0f);
dropShadow.setOffsetY(2.0f);
dropShadow.setColor(Color.rgb(50, 50, 50, .588));
java8Recipes2.setEffect(dropShadow);

Although this recipe is about setting text fonts, it also applied effects to Text nodes. Another effect has been added (just kicking it up a notch). While creating the last Text node using the monospaced font, the popular reflection effect is applied. The code following code is set so that .8 or 80 percent of the reflection is shown. The reflection values range from zero (0%) to one (100%). The following code snippet implements a reflection of 80% with a float value of 0.8f:

Reflection refl = new Reflection();
refl.setFraction(0.8f);

java8Recipes5.setEffect(refl);

Solution 2 demonstrates how to construct a user interface using FXML, CSS, and Java. While this recipe focuses on text and fonts, it is important to note that FXML solutions clearly follow a model-view-controller standard, separating UI code from business logic. It is also important to note that if the UI in this example were to contain buttons or other nodes that contained actions, a controller class would need to be created as well to embody the action logic.

In the second example, an FXML file contains the structured layout for the user interface, which consists of a Scene, Pane, TextFlow, and a series of Text nodes. The scene contains a <stylesheets> element, which is used to specify which style sheets to apply to the elements within the XML. The Pane node is used as a base for the layout, and it contains each of the other nodes within the UI. The TextFlow node is new in JavaFX 8, and it is a special layout that is designed to lay out rich text. The TextFlow can lay many different Text nodes into a single flow.

As you can see from the FXML, each of the Text nodes within the TextFlow have different styles associated with them, based on those styles that have been defined within the attached style sheet. The properties for styles in JavaFX style sheets are preceded by –fx-, and property names and values are separated by a colon and terminated by a semicolon (;). For the most part, JavaFX style properties align nicely with standard CSS properties. For a complete summary, refer to the documentation athttp://docs.oracle.com/javafx/2/css_tutorial/jfxpub-css_tutorial.htm.

The TextFlow uses the text and font of each node that is embedded within, as well as its own width and text alignment, to determine the placement of the text. Nodes other than Text can also be embedded within a TextFlow. When adding Text nodes to a TextFlow, you can set word wrap by specifying a maximum width of the TextFlow via the setMaxWidth() method. It is also possible to include a \n at the end of any strings within a Text node to initiate a line break. The following code performs the same solution as 1, but uses TextFlow to lay out theText nodes, rather than adding each to the scene graph separately.

primaryStage.setTitle(“Chapter 14-3 Changing Text Fonts”);
Group root = new Group();
Scene scene = new Scene(root, 330, 250, Color.WHITE);

// Serif with drop shadow
Text java8Recipes2 = new Text(50, 50, “Java 8 Recipes”);
Font serif = Font.font(“Serif”, 30);
java8Recipes2.setFont(serif);
java8Recipes2.setFill(Color.RED);
DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(2.0f);
dropShadow.setOffsetY(2.0f);
dropShadow.setColor(Color.rgb(50, 50, 50, .588));
java8Recipes2.setEffect(dropShadow);

// SanSerif
Text java8Recipes3 = new Text(50, 100, “Java 8 Recipes\n”);
Font sanSerif = Font.font(“SanSerif”, 30);
java8Recipes3.setFont(sanSerif);
java8Recipes3.setFill(Color.BLUE);

// Dialog
Text java8Recipes4 = new Text(50, 150, “Java 8 Recipes\n”);
Font dialogFont = Font.font(“Dialog”, 30);
java8Recipes4.setFont(dialogFont);
java8Recipes4.setFill(Color.rgb(0, 255, 0));

// Monospaced
Text java8Recipes5 = new Text(50, 200, “Java 8 Recipes”);
Font monoFont = Font.font(“Monospaced”, 30);
java8Recipes5.setFont(monoFont);
java8Recipes5.setFill(Color.BLACK);

Reflection refl = new Reflection();
refl.setFraction(0.8f);
java8Recipes5.setEffect(refl);
TextFlow flow = new TextFlow(java8Recipes2, java8Recipes3, java8Recipes4, java8Recipes5);

root.getChildren().add(flow);

There were a lot of concepts introduced within this recipe. You will learn more about FXML in a later recipe, or for more information you can see the online documentation at http://docs.oracle.com/javafx/2/get_started/fxml_tutorial.htm. You an learn more about the TextFlow layout reading the documentation at http://docs.oracle.com/javase/8/javafx/api/javafx/scene/text/TextFlow.html.

14-4. Creating Shapes

Problem

You want to create shapes to be placed on the scene graph.

Solution

Use JavaFX’s Arc, Circle, CubicCurve, Ellipse, Line, Path, Polygon, Polyline, QuadCurve, Rectangle, SVGPath, and Text classes in the javafx.scene.shape.* package. The following code draws various complex shapes. The first complex shape involves a cubic curve drawn in the shape of a sine wave. The next shape, called the ice cream cone, uses the path class that contains path elements (javafx.scene.shape.PathElement). The third shape is a Quadratic Bézier curve (QuadCurve) and it forms a smile. The final shape is a delectable donut. You can create this donut shape by subtracting two ellipses (one smaller and one larger):

@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-4 Creating Shapes");
Group root = new Group();
Scene scene = new Scene(root, 306, 550, Color.WHITE);

// CubicCurve
CubicCurve cubicCurve = new CubicCurve();
cubicCurve.setStartX(50);
cubicCurve.setStartY(75); // start pt (x1,y1)
cubicCurve.setControlX1(80);
cubicCurve.setControlY1(-25); // control pt1
cubicCurve.setControlX2(110);
cubicCurve.setControlY2(175); // control pt2
cubicCurve.setEndX(140);
cubicCurve.setEndY(75);
cubicCurve.setStrokeType(StrokeType.CENTERED);
cubicCurve.setStrokeWidth(1);
cubicCurve.setStroke(Color.BLACK);
cubicCurve.setStrokeWidth(3);
cubicCurve.setFill(Color.WHITE);

root.getChildren().add(cubicCurve);

// Ice cream
Path path = new Path();

MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(150);

QuadCurveTo quadCurveTo = new QuadCurveTo();
quadCurveTo.setX(150);
quadCurveTo.setY(150);
quadCurveTo.setControlX(100);
quadCurveTo.setControlY(50);

LineTo lineTo1 = new LineTo();
lineTo1.setX(50);
lineTo1.setY(150);

LineTo lineTo2 = new LineTo();
lineTo2.setX(100);
lineTo2.setY(275);

LineTo lineTo3 = new LineTo();
lineTo3.setX(150);
lineTo3.setY(150);
path.getElements().add(moveTo);
path.getElements().add(quadCurveTo);
path.getElements().add(lineTo1);
path.getElements().add(lineTo2);
path.getElements().add(lineTo3);
path.setTranslateY(30);
path.setStrokeWidth(3);
path.setStroke(Color.BLACK);

root.getChildren().add(path);

// QuadCurve create a smile
QuadCurve quad = new QuadCurve();
quad.setStartX(50);
quad.setStartY(50);
quad.setEndX(150);
quad.setEndY(50);
quad.setControlX(125);
quad.setControlY(150);
quad.setTranslateY(path.getBoundsInParent().getMaxY());
quad.setStrokeWidth(3);
quad.setStroke(Color.BLACK);
quad.setFill(Color.WHITE);

root.getChildren().add(quad);

// outer donut
Ellipse bigCircle = new Ellipse(100, 100, 50, 75/2);
//bigCircle.setTranslateY(quad.getBoundsInParent().getMaxY());
bigCircle.setStrokeWidth(3);
bigCircle.setStroke(Color.BLACK);
bigCircle.setFill(Color.WHITE);

// donut hole
Ellipse smallCircle = new Ellipse(100, 100, 35/2, 25/2);

// make a donut
Shape donut = Path.subtract(bigCircle, smallCircle);
donut.setStrokeWidth(1);
donut.setStroke(Color.BLACK);
// orange glaze
donut.setFill(Color.rgb(255, 200, 0));

// add drop shadow
DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(2.0f);
dropShadow.setOffsetY(2.0f);
dropShadow.setColor(Color.rgb(50, 50, 50, .588));

donut.setEffect(dropShadow);

// move slightly down for spacing
donut.setTranslateY(quad.getBoundsInParent().getMinY() + 10);

root.getChildren().add(donut);

primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-5 displays the sine wave, ice cream cone, smile, and donut shapes created using JavaFX.

9781430268277_Fig14-05.jpg

Figure 14-5. Creating shapes

How It Works

In this solution, you generated some basic 2D shapes. The first shape is a javafx.scene.shape.CubicCurve class, which allows you to construct a cubic curve (a “squiggly line”) effect. To create a cubic curve, simply look for the appropriate constructor to be instantiated. The following code snippet is used to create a javafx.scene.shape.CubicCurve instance:

CubicCurve cubicCurve = new CubicCurve();
cubicCurve.setStartX(50);
cubicCurve.setStartY(75); // start pt (x1,y1)
cubicCurve.setControlX1(80);
cubicCurve.setControlY1(-25); // control pt1
cubicCurve.setControlX2(110);
cubicCurve.setControlY2(175); // control pt2
cubicCurve.setEndX(140);
cubicCurve.setEndY(75);
cubicCurve.setStrokeType(StrokeType.CENTERED);
cubicCurve.setStrokeWidth(1);
cubicCurve.setStroke(Color.BLACK);
cubicCurve.setStrokeWidth(3);
cubicCurve.setFill(Color.WHITE);

You begin by instantiating a CubicCurve() instance. Next, the curve’s attributes are specified in any order by utilizing the object’s setter methods and passing a single value to each. Once you’re finished specifying values on the CubicCurve() object, you can add it to the scene graph using the following notation:

root.getChildren().add(cubicCurve);

The ice cream cone shape is created using the javafx.scene.shape.Path class. As each path element is created and added to the Path object, each element is not considered a graph node (javafx.scene.Node). This means they do not extend from thejavafx.scene.shape.Shape class and cannot be a child node in a scene graph to be displayed. When looking at the Javadoc (see http://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Path.html), you will notice that a Path class extends from the Shape class that extends from the (javafx.scene.Node) class, and therefore a Path is a graph node, but path elements do not extend from the Shape class. Path elements actually extend from the javafx.scene.shape.PathElement class, which is only used in the context of a Path object. So you won’t be able to instantiate a LineTo class to be put in the scene graph. Just remember that the classes with To as a suffix are path elements, not real Shape nodes. For example, the MoveTo and LineTo object instances are Path elements added to aPath object, not shapes that can be added to the scene. The following are Path elements added to a Path object to draw an ice cream cone:

// Ice cream
Path path = new Path();

MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(150);

...// Additional Path Elements created.
LineTo lineTo1 = new LineTo();
lineTo1.setX(50);
lineTo1.setY(150);

...// Additional Path Elements created.

path.getElements().add(moveTo);
path.getElements().add(quadCurveTo);
path.getElements().add(lineTo1);

Rendering the QuadCurve (smile) object, you instantiate a new QuadCurve object and set each of the attributes accordingly. Again, each of the attributes accepts a single value.

Last is the tasty donut shape with a drop shadow effect, which is actually created by two circular ellipses. By subtracting the smaller ellipse (donut hole) from the larger ellipse area, a newly derived shape is created and returned using the Path.subtract() method. Following is the code snippet that creates the donut shape using the Path.subtract() method:

// outer donut
Ellipse bigCircle = ...//Outer shape area

// donut hole
Ellipse smallCircle = ...// Inner shape area

// make a donut
Shape donut = Path.subtract(bigCircle, smallCircle);

Next, a drop shadow effect is added to the donut. This time instead of drawing the shape twice, similar to a prior recipe, you draw it once and use the setEffect() method to apply a DropShadow object instance to the donut Shape object. Similar to the prior technique, you set the offset of the shadow by calling setOffsetX() and setOffsetY().

Image Note In the previous release, builder objects could be used to create shapes a bit more easily. However, the builder classes were removed from JavaFX 8 due to performance and bloating issues. If you’re maintaining code that utilizes builder classes, it is recommended that you to migrate away from them and make use of the standard objects, as demonstrated in this recipe.

14-5. Assigning Colors to Objects

Problem

You want to fill your shapes with simple colors and gradient colors.

Solution

In JavaFX, all shapes can be filled with simple colors and gradient colors. The following are the main classes used to fill shape nodes:

· javafx.scene.paint.Color

· javafx.scene.paint.LinearGradient

· javafx.scene.paint.Stop

· javafx.scene.paint.RadialGradient

The following code uses the preceding classes to add radial and linear gradient colors as well as transparent (alpha channel level) colors to shapes. This recipe uses an ellipse, rectangle, and rounded rectangle. A solid black line (as depicted in Figure 14-5) also appears in the recipe to demonstrate the transparency of the shape’s color.

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-5 Assigning Colors To Objects");
Group root = new Group();
Scene scene = new Scene(root, 350, 300, Color.WHITE);

Ellipse ellipse = new Ellipse(100, 50 + 70/2, 50, 70/2);
RadialGradient gradient1 = new RadialGradient(0,
.1, // focus angle
80, // focus distance
45, // centerX
120, // centerY
false, // proportional
CycleMethod.NO_CYCLE,
new Stop(0, Color.RED), new Stop(1, Color.BLACK));

ellipse.setFill(gradient1);
root.getChildren().add(ellipse);

// Create line
Line blackLine = new Line();
blackLine.setStartX(170);
blackLine.setStartY(30);
blackLine.setEndX(20);
blackLine.setEndY(140);
blackLine.setFill(Color.BLACK);
blackLine.setStrokeWidth(10.0f);
blackLine.setTranslateY(ellipse.prefHeight(-1) + ellipse.getLayoutY() + 10);

root.getChildren().add(blackLine);

// Create rectangle
Rectangle rectangle = new Rectangle();
rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(100);
rectangle.setHeight(70);
rectangle.setTranslateY(ellipse.prefHeight(-1) + ellipse.getLayoutY() + 10);

// Create linear gradient
LinearGradient linearGrad = new LinearGradient(
50, //startX
50, //startY
50, //endX
50 + rectangle.prefHeight(-1) + 25, //endY
false, //proportional
CycleMethod.NO_CYCLE,
new Stop(0.1f, Color.rgb(255, 200, 0, .784)),
new Stop(1.0f, Color.rgb(0, 0, 0, .784)));

rectangle.setFill(linearGrad);
root.getChildren().add(rectangle);

// Create rectangle with rounded corners
Rectangle roundRect = new Rectangle();
roundRect.setX(50);
roundRect.setY(50);
roundRect.setWidth(100);
roundRect.setHeight(70);
roundRect.setArcWidth(20);
roundRect.setArcHeight(20);
roundRect.setTranslateY(ellipse.prefHeight(-1) +
ellipse.getLayoutY() +
10 +
roundRect.prefHeight(-1) +
roundRect.getLayoutY() + 10);

LinearGradient cycleGrad = new LinearGradient(50,
50,
70,
70,
false,
CycleMethod.REFLECT,
new Stop(0f, Color.rgb(0, 255, 0, .784)),
new Stop(1.0f, Color.rgb(0, 0, 0, .784)));

roundRect.setFill(cycleGrad);
root.getChildren().add(roundRect);

primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-6 displays the various types of colorized fills that can be applied to shapes.

9781430268277_Fig14-06.jpg

Figure 14-6. Color shapes

How It Works

Figure 14-5 shows shapes displayed from top to bottom starting with an ellipse, rectangle, and a rounded rectangle having colored gradient fills. When drawing the eclipse shape, you will be using a radial gradient that appears as if it were a 3D spherical object. Next, a rectangle filled with a yellow semitransparent linear gradient is created. A thick black line shape was drawn behind the yellow rectangle to demonstrate the rectangle’s semitransparent color. Lastly, a rounded rectangle filled with a green-and-black reflective linear gradient resembling 3D tubes in a diagonal direction is generated.

The amazing thing about colors with gradients is that they can often make shapes appear three-dimensional. Gradient paint allows you to interpolate between two or more colors, which gives the shape depth. JavaFX provides two types of gradients: a radial (RadialGradient) and a linear (LinearGradient) gradient. A radial gradient (RadialGradient) is applied to the ellipse shape in the example.

Table 14-1 is taken from the JavaFX 8 Javadoc definitions found for the RadialGradient class (http://docs.oracle.com/javase/8/javafx/api/javafx/scene/paint/RadialGradient.html).

Table 14-1. RadialGradient Properties

Property

Data Type

Description

focusAngle

double

Angle in degrees from the center of the gradient to the focus point to which the first color is mapped

focusDistance

double

Distance from the center of the gradient to the focus point to which the first color is mapped

centerX

double

X coordinate of the center point of the gradient’s circle

centerY

double

Y coordinate of the center point of the gradient’s circle

radius

double

Radius of the circle defining the extents of the color gradient

proportional

boolean

Coordinates and sizes are proportional to the shape that this gradient fills

cycleMethod

CycleMethod

Cycle method applied to the gradient

opaque

stops

boolean

List<Stop>

Whether the paint is completely opaque

Gradient’s color specification

In this recipe, the focus angle is set to zero, the distance is set to .1, the center X and Y are set to (80,45), the radius is set to 120 pixels, the proportional is set to false, the cycle method is set to the no cycle (CycleMethod.NO_CYCLE), and the two color stop values are set to red (Color.RED) and black (Color.BLACK). These settings create a radial gradient by starting with the color red at a center position of (80, 45) (upper left of the ellipse) and then interpolating it to the color black with a distance of 120 pixels (radius).

Next, a rectangle having a yellow semitransparent linear gradient is created. A linear gradient (LinearGradient) paint is used for the yellow rectangle.

Table 14-2 is taken from the JavaFX 8.0 Javadoc definitions found for the LinearGradient class (see http://docs.oracle.com/javase/8/javafx/api/javafx/scene/paint/LinearGradient.html).

Table 14-2. LinearGradient Properties

Property

Data Type

Description

startX

double

X coordinate of the gradient axis start point

startY

double

Y coordinate of the gradient axis start point

endX

double

X coordinate of the gradient axis end point

endY

double

Y coordinate of the gradient axis end point

proportional

boolean

Whether the coordinates are proportional to the shape that this gradient fills

cycleMethod

opaque

CycleMethod

boolean

Cycle method applied to the gradient

Whether this paint is completely opaque

stops

List<Stop>

Gradient’s color specification

To create a linear gradient paint, you specify the startX, startY, endX, and endY for the start\end points. The start and end point coordinates denote where the gradient pattern starts and stops.

To create the second shape (yellow rectangle), set the start X and Y to (50, 50), the end X and Y to (50, 75), the proportional to false, the cycle method to no cycle (CycleMethod.NO_CYCLE), and the two color stop values to yellow (Color.YELLOW) and black (Color.BLACK), with an alpha transparency of .784. These settings provide a linear gradient for the rectangle from top to bottom, with a starting point of (50, 50) (top-left of the rectangle). It then interpolates to the color black (bottom-left of the rectangle).

Finally, you’ll notice a rounded rectangle with a repeating pattern of a gradient using green and black in a diagonal direction. This is a simple linear gradient paint that is the same as the linear gradient paint (LinearGradient), except that the start X, Y and the end X, Y are set in a diagonal position, and the cycle method is set to reflect (CycleMethod.REFLECT). When specifying the cycle method to reflect (CycleMethod.REFLECT), the gradient pattern will repeat or cycle between the colors. The following code snippet implements the rounded rectangle having a cycle method of reflect (CycleMethod.REFLECT):

LinearGradient cycleGrad = new LinearGradient(50,
50,
70,
70,
false,
CycleMethod.REFLECT,
new Stop(0f, Color.rgb(0, 255, 0, .784)),
new Stop(1.0f, Color.rgb(0, 0, 0, .784)));

14-6. Creating Menus

Problem

You want to create standard menus in your JavaFX applications.

Solution

Employ JavaFX’s menu controls to provide standardized menu capabilities such as check box menus, radio menus, submenus, and separators. The following are the main classes used to create menus.

· javafx.scene.control.MenuBar

· javafx.scene.control.Menu

· javafx.scene.control.MenuItem

The following code calls into play all the menu capabilities listed previously. The example code simulates a building security application containing menu options to turn on cameras, sound an alarm, and select contingency plans.

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-6 Creating Menus");
Group root = new Group();
Scene scene = new Scene(root, 300, 250, Color.WHITE);

MenuBar menuBar = new MenuBar();

// File menu - new, save, exit
Menu menu = new Menu("File");
menu.getItems().add(new MenuItem("New"));
menu.getItems().add(new MenuItem("Save"));
menu.getItems().add(new SeparatorMenuItem());
menu.getItems().add(new MenuItem("Exit"));

menuBar.getMenus().add(menu);

// Cameras menu - camera 1, camera 2
Menu tools = new Menu("Cameras");
CheckMenuItem item1 = new CheckMenuItem();
item1.setText("Show Camera 1");
item1.setSelected(true);
tools.getItems().add(item1);

CheckMenuItem item2 = new CheckMenuItem();
item2.setText("Show Camera 2");
item2.setSelected(true);
tools.getItems().add(item2);

menuBar.getMenus().add(tools);

// Alarm
Menu alarm = new Menu("Alarm");
ToggleGroup tGroup = new ToggleGroup();

RadioMenuItem soundAlarmItem = new RadioMenuItem();
soundAlarmItem.setToggleGroup(tGroup);
soundAlarmItem.setText("Sound Alarm");

RadioMenuItem stopAlarmItem = new RadioMenuItem();
stopAlarmItem.setToggleGroup(tGroup);
stopAlarmItem.setText("Alarm Off");
stopAlarmItem.setSelected(true);

alarm.getItems().add(soundAlarmItem);
alarm.getItems().add(stopAlarmItem);

Menu contingencyPlans = new Menu("Contingent Plans");
contingencyPlans.getItems().add(new CheckMenuItem("Self Destruct in T minus 50"));
contingencyPlans.getItems().add(new CheckMenuItem("Turn off the coffee machine "));
contingencyPlans.getItems().add(new CheckMenuItem("Run for your lives! "));

alarm.getItems().add(contingencyPlans);
menuBar.getMenus().add(alarm);

menuBar.prefWidthProperty().bind(primaryStage.widthProperty());

root.getChildren().add(menuBar);
primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-7 shows a simulated building security application containing checked, and submenu items.

9781430268277_Fig14-07.jpg

Figure 14-7. Creating menus

How It Works

Menus provide standard ways to allow users to select options from windowed platform applications. Menus should also have hot keys or keyboard equivalents. Users will often want to use the keyboard instead of the mouse to navigate the menu. This recipe parallels Recipe 14-8, and you’ll notice lots of similarities.

To create a menu, first create an instance of a MenuBar that will contain one-to-many menu (MenuItem) objects. Creating a menu bar:

MenuBar menuBar = new MenuBar();

Secondly, create menu (Menu) objects that contain one-to-many menu item (MenuItem) objects and other Menu objects making submenus. To create a menu:

Menu menu = new Menu("File");

Third, create menu items to be added to Menu objects, such as menu (MenuItem), check (CheckMenuItem), and radio menu items (RadioMenuItem). Menu items can have icons in them. We don’t showcase this in the recipe, but we encourage you to explore the various constructors for all menu items (MenuItem). When creating a radio menu item (RadioMenuItem), you should be aware of the ToggleGroup class. The ToggleGroup class is also used on regular radio buttons (RadioButtons) to allow only one selected option at any one time. The following code creates radio menu items (RadioMenuItems) to be added to a Menu object:

// Alarm
Menu alarm = new Menu("Alarm");
ToggleGroup tGroup = new ToggleGroup();

RadioMenuItem soundAlarmItem = new RadioMenuItem();
soundAlarmItem.setToggleGroup(tGroup);
soundAlarmItem.setText("Sound Alarm");

RadioMenuItem stopAlarmItem = new RadioMenuItem();
stopAlarmItem.setToggleGroup(tGroup);
stopAlarmItem.setText("Alarm Off");
stopAlarmItem.setSelected(true);

alarm.getItems().add(soundAlarmItem);
alarm.getItems().add(stopAlarmItem);

At times you may want to separate menu items with a visual line separator. To create a visual separator, create an instance of a SeparatorMenuItem class to be added to a menu via the getItems() method. The method getItems() returns an observable list of MenuItemobjects (ObservableList<MenuItem>). As you will see in Recipe 14-10, you can be notified when items in a collection are altered. The following code line adds a visual line separator (SeparatorMenuItem) to the menu:

menu.getItems().add(new SeparatorMenuItem());

Other menu items used are the check menu item (CheckMenuItem) and the radio menu item (RadioMenuItem), and they are similar to their counterparts in JavaFX UI controls check box (CheckBox) and radio button (RadioButton), respectively.

Prior to adding the menu bar to the scene, you will notice the bound property between the preferred width of the menu bar and the width of the Stage object via the bind() method. When binding these properties you will see the menu bar’s width stretch when the user resizes the screen. You will see how binding works in Recipe 14-9, “Binding Expressions.” This code snippet shows the binding between the menu bar’s width property and the stage’s width property.

menuBar.prefWidthProperty().bind(primaryStage.widthProperty());

root.getChildren().add(menuBar);

14-7. Adding Components to a Layout

Problem

You want to add UI components to a layout similar to a grid type layout for easy placement.

Solution

Use JavaFX’s javafx.scene.layout.GridPane class. This source code implements a simple UI form containing first and last name field controls and using the grid pane layout node (javafx.scene.layout.GridPane):

GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(5);
gridpane.setVgap(5);

Label fNameLbl = new Label(“First Name”);
TextField fNameFld = new TextField();
Label lNameLbl = new Label(“First Name”);
TextField lNameFld = new TextField();
Button saveButt = new Button(“Save”);

// First name label
GridPane.setHalignment(fNameLbl, HPos.RIGHT);
gridpane.add(fNameLbl, 0, 0);

// Last name label
GridPane.setHalignment(lNameLbl, HPos.RIGHT);
gridpane.add(lNameLbl, 0, 1);

// First name field
GridPane.setHalignment(fNameFld, HPos.LEFT);
gridpane.add(fNameFld, 1, 0);

// Last name field
GridPane.setHalignment(lNameFld, HPos.LEFT);
gridpane.add(lNameFld, 1, 1);

// Save button
GridPane.setHalignment(saveButt, HPos.RIGHT);
gridpane.add(saveButt, 1, 2);

root.getChildren().add(gridpane);

Figure 14-8 depicts a small form containing UI controls laid out using a grid pane layout node.

9781430268277_Fig14-08.jpg

Figure 14-8. Adding controls to a layout

How It Works

One of the greatest challenges in building user interfaces is how controls can be placed onto the display area. When developing GUI applications, it is ideal for an application to allow the users to move and adjust the size of their viewable area while maintaining a pleasant user experience. Similar to Java Swing, JavaFX layout has stock layouts that provide the most common ways to display UI controls on the scene graph. This recipe demonstrates the GridPane class.

Recall Recipe 14-4, in which you implemented a custom layout to display components in a grid-like manner. You may notice similarities, but we left a lot of implementation features out, such as adjusting min/max sizes, padding, and vertical alignments. Amazingly, the JavaFX team has created a robust grid-like layout called the GridPane.

First you create an instance of a GridPane. Next, you set the padding by using an instance of an Inset object. After setting the padding, you simply set the horizontal and vertical gap. The following code snippet instantiates a grid pane (GridPane) with padding, horizontal, and vertical gaps set to 5 (pixels):

GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(5);
gridpane.setVgap(5);

The padding is the top, right, bottom, and left spacing around the region's content in pixels. When obtaining the preferred size, the padding will be included in the calculation. Setting the horizontal and vertical gaps relate to the spacing between UI controls within the cells.

Next, simply place each UI control into its respective cell location. All cells are zero relative. Following is a code snippet that adds a Save button UI control into a grid pane layout node (GridPane) at cell (1, 2):

gridpane.add(saveButt, 1, 2);

The layout also allows you to horizontally or vertically align controls in the cell. The following code statement right-aligns the Save button:

GridPane.setHalignment(saveButt, HPos.RIGHT);

14-8. Generating Borders

Problem

You want to create and customize borders around an image.

Solution

Create an application to dynamically customize border regions using JavaFX’s CSS styling API.

The following code creates an application that has a CSS editor text area and a border view region surrounding an image. By default the editor’s text area will contain JavaFX styling selectors that create a dashed-blue line surrounding the image. You will have the opportunity to modify styling selector values in the CSS Editor by clicking the Bling! button to apply border settings.

primaryStage.setTitle("Chapter 14-8 Generating Borders");
Group root = new Group();
Scene scene = new Scene(root, 600, 330, Color.WHITE);

// create a grid pane
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(10);
gridpane.setVgap(10);

// label CSS Editor
Label cssEditorLbl = new Label("CSS Editor");
GridPane.setHalignment(cssEditorLbl, HPos.CENTER);
gridpane.add(cssEditorLbl, 0, 0);

// label Border View
Label borderLbl = new Label("Border View");
GridPane.setHalignment(borderLbl, HPos.CENTER);
gridpane.add(borderLbl, 1, 0);

// Text area for CSS editor
final TextArea cssEditorFld = new TextArea();
cssEditorFld.setPrefRowCount(10);
cssEditorFld.setPrefColumnCount(100);
cssEditorFld.setWrapText(true);
cssEditorFld.setPrefWidth(150);
GridPane.setHalignment(cssEditorFld, HPos.CENTER);
gridpane.add(cssEditorFld, 0, 1);

String cssDefault = "-fx-border-color: blue;\n"
+ "-fx-border-insets: 5;\n"
+ "-fx-border-width: 3;\n"
+ "-fx-border-style: dashed;\n";

cssEditorFld.setText(cssDefault);

// Border decorate the picture
final ImageView imv = new ImageView();
final Image image2 = new Image(GeneratingBorders.class.getResourceAsStream("smoke_glass_buttons1.png"));
imv.setImage(image2);

final HBox pictureRegion = new HBox();
pictureRegion.setStyle(cssDefault);
pictureRegion.getChildren().add(imv);
gridpane.add(pictureRegion, 1, 1);

Button apply = new Button("Bling!");
GridPane.setHalignment(apply, HPos.RIGHT);
gridpane.add(apply, 0, 2);

apply.setOnAction((e) -> {
pictureRegion.setStyle(cssEditorFld.getText());
});

root.getChildren().add(gridpane);
primaryStage.setScene(scene);
primaryStage.show();

Figure 14-9 illustrates the border customizer application.

9781430268277_Fig14-09.jpg

Figure 14-9. Generating borders

How It Works

JavaFX is capable of styling JavaFX nodes similar to Cascading Style Sheets (CSS) in the world of web development (also demonstrated in Recipe 14-3). This powerful API can alter a node’s background color, font, border, and many other attributes, essentially allowing the developer or designer to skin GUI controls using CSS.

This solution to this recipe allows users to enter JavaFX CSS styles in the left text area and, by clicking the Bling! button on the UI, apply the style around the image shown to the right. Based on the type of node, there are limitations to what styles can be set. To see a full listing of all style selectors, refer to the JavaFX CSS Reference Guide:

http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html.

In the first step of applying JavaFX CSS styles, you must determine which type of node you want to style. When setting attributes on various node types, you will discover that certain nodes have limitations. In this recipe, the intent was to put a border around the ImageView object. Because ImageView is not extending from Region, it doesn’t contain border style properties. So, to resolve this, simply create an HBox layout to contain the imageView and apply the JavaFX CSS against the HBox. The following code applies JavaFX CSS border styles to a horizontal box region (HBox) using the setStyle() method:

String cssDefault = "-fx-border-color: blue;\n"
+ "-fx-border-insets: 5;\n"
+ "-fx-border-width: 3;\n"
+ "-fx-border-style: dashed;\n";
final ImageView imv = new ImageView();
...//
final HBox pictureRegion = new HBox();
pictureRegion.setStyle(cssDefault);
pictureRegion.getChildren().add(imv);

14-9. Binding Expressions

Problem

You want to synchronize changes between two values.

Solution

Use the javafx.beans.binding.* and javafx.beans.property.* packages to bind variables. There is more than one scenario to consider when binding values or properties. This recipe demonstrates the following three binding strategies:

· Bidirectional binding on a Java Bean

· High-level binding using the Fluent API

· Low-level binding using javafx.beans.binding.* binding objects

The following code is a console application implementing these three strategies. The console application will output property values based on various binding scenarios. The first scenario is a bidirectional binding between a string property variable and a string property owned by a domain object (Contact), such as the firstName property. The next scenario is a high-level binding using a fluent interface API to calculate the area of rectangle. The last scenario is using a low-level binding strategy to calculate the volume of a sphere. The difference between the high-and low-level binding is that the high level uses methods such as multiply() and subtract() instead of the operators * and -. When using low-level binding, you use a derived NumberBinding class such as a DoubleBinding class. With a DoubleBinding class you override itscomputeValue() method so that you can use the familiar operators such as * and - to formulate complex math equations:

package org.java8recipes.chapter14.recipe14_09;

import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
* Recipe 14-9: Binding Expressions
* @author cdea
* Update: J. Juneau
*/
public class BindingExpressions {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println("Chapter 14-9 Binding Expressions\n");

System.out.println("Binding a Contact bean [Bi-directional binding]");
Contact contact = new Contact("John", "Doe");
StringProperty fname = new SimpleStringProperty();
fname.bindBidirectional(contact.firstNameProperty());
StringProperty lname = new SimpleStringProperty();
lname.bindBidirectional(contact.lastNameProperty());

System.out.println("Current - StringProperty values : " + fname.getValue() + "
" + lname.getValue());
System.out.println("Current - Contact values : " + contact.getFirstName() + "
" + contact.getLastName());

System.out.println("Modifying StringProperty values");
fname.setValue("Jane");
lname.setValue("Deer");

System.out.println("After - StringProperty values : " + fname.getValue() + "
" + lname.getValue());
System.out.println("After - Contact values : " + contact.getFirstName() + "
" + contact.getLastName());

System.out.println();
System.out.println("A Area of a Rectangle [High level Fluent API]");

// Area = width * height
final IntegerProperty width = new SimpleIntegerProperty(10);
final IntegerProperty height = new SimpleIntegerProperty(10);

NumberBinding area = width.multiply(height);

System.out.println("Current - Width and Height : " + width.get() + " " + height.get());
System.out.println("Current - Area of the Rectangle: " + area.getValue());
System.out.println("Modifying width and height");

width.set(100);
height.set(700);

System.out.println("After - Width and Height : " + width.get() + " " + height.get());
System.out.println("After - Area of the Rectangle: " + area.getValue());

System.out.println();
System.out.println("A Volume of a Sphere [low level API]");

// volume = 4/3 * pi r^3
final DoubleProperty radius = new SimpleDoubleProperty(2);

DoubleBinding volumeOfSphere = new DoubleBinding() {
{
super.bind(radius);
}

@Override
protected double computeValue() {
return (4 / 3 * Math.PI * Math.pow(radius.get(), 3));
}
};

System.out.println("Current - radius for Sphere: " + radius.get());
System.out.println("Current - volume for Sphere: " + volumeOfSphere.get());
System.out.println("Modifying DoubleProperty radius");

radius.set(50);
System.out.println("After - radius for Sphere: " + radius.get());
System.out.println("After - volume for Sphere: " + volumeOfSphere.get());

}
}

class Contact {

private SimpleStringProperty firstName = new SimpleStringProperty();
private SimpleStringProperty lastName = new SimpleStringProperty();

public Contact(String fn, String ln) {
firstName.setValue(fn);
lastName.setValue(ln);
}

public final String getFirstName() {
return firstName.getValue();
}

public StringProperty firstNameProperty() {
return firstName;
}

public final void setFirstName(String firstName) {
this.firstName.setValue(firstName);
}

public final String getLastName() {
return lastName.getValue();
}

public StringProperty lastNameProperty() {
return lastName;
}

public final void setLastName(String lastName) {
this.lastName.setValue(lastName);
}
}

The following output demonstrates the three binding scenarios:

Binding a Contact bean [Bi-directional binding]
Current - StringProperty values : John Doe
Current - Contact values : John Doe
Modifying StringProperty values
After - StringProperty values : Jane Deer
After - Contact values : Jane Deer

A Area of a Rectangle [High level Fluent API]
Current - Width and Height : 10 10
Current - Area of the Rectangle: 100
Modifying width and height
After - Width and Height : 100 700
After - Area of the Rectangle: 70000

A Volume of a Sphere [low level API]
Current - radius for Sphere: 2.0
Current - volume for Sphere: 25.132741228718345
Modifying DoubleProperty radius
After - radius for Sphere: 50.0
After - volume for Sphere: 392699.0816987241

How It Works

Binding implies that at least two values are being synchronized. This means when a dependent variable changes, the other variable changes. JavaFX provides many binding options that enable developers to synchronize properties in domain objects and GUI controls. This recipe demonstrates the three common binding scenarios.

One of the easiest ways to bind variables is using a bidirectional bind. This scenario is often used when domain objects contain data that will be bound to a GUI form. This recipe creates a simple contact (Contact) object containing a first name and last name. Notice the instance variables using the SimpleStringProperty class. Many of these classes, which end in Property, are javafx.beans.Observable classes that can all be bound. In order for these properties to be bound, they must be the same data type. In the preceding example, you create the first name and last name variables of type SimpleStringProperty outside the created Contact domain object. Once they have been created, you bind them bidirectionally to allow changes to update on either end. So if you change the domain object, the other bound properties are updated. And when the outside variables are modified, the domain object’s properties are updated. The following demonstrates bidirectional binding against string properties on a domain object (Contact):

Contact contact = new Contact("John", "Doe");
StringProperty fname = new SimpleStringProperty();
fname.bindBidirectional(contact.firstNameProperty());
StringProperty lname = new SimpleStringProperty();
lname.bindBidirectional(contact.lastNameProperty());

Next up is how to bind numbers. Binding numbers is simple when using the Fluent API. This high-level mechanism allows developers to bind variables to compute values using simple arithmetic. Basically, a formula is “bound” to change its result based on changes to the variables it’s bound to. Look at the Javadoc (http://docs.oracle.com/javase/8/javafx/api/javafx/beans/binding/Bindings.html) for details on all the available methods and number types. In this example, you simply create a formula for an area of a rectangle. The area (NumberBinding) is the binding, and its dependencies are the width and height (IntegerProperty) properties. When binding using the fluent interface API, you’ll notice the multiply() method. According to the Javadoc, all property classes inherit from theNumberExpressionBase class, which contains the number-based fluent interface APIs. The following code snippet uses the fluent interface API:

// Area = width * height
final IntegerProperty width = new SimpleIntegerProperty(10);
final IntegerProperty height = new SimpleIntegerProperty(10);
NumberBinding area = width.multiply(height);

The last scenario on binding numbers is considered more of a low-level approach. This allows developers to use primitives and more complex math operations. Here, you use a DoubleBinding class to solve the volume of a sphere given the radius. You begin by implementing thecomputeValue() method to perform the calculation of the volume. Shown is the low-level binding scenario to compute the volume of a sphere by overriding the computeValue() method:

final DoubleProperty radius = new SimpleDoubleProperty(2);

DoubleBinding volumeOfSphere = new DoubleBinding() {
{
super.bind(radius);
}

@Override
protected double computeValue() {
return (4 / 3 * Math.PI * Math.pow(radius.get(), 3));
}
};

14-10. Creating and Working with Observable Lists

Problem

You want to create a GUI application containing two list view controls that allow users to pass items between the two lists.

Solution

You can take advantage of JavaFX’s javafx.collections.ObservableList and javafx.scene.control.ListView classes to provide a model-view-controller (MVC) mechanism that updates the UI’s list view control whenever the backend list is manipulated.

The following code creates a GUI application containing two lists that allow users to send items contained in one list to the other. Here you will create a contrived application to pick candidates to be considered heroes. The user picks potential candidates from the list on the left to be moved into the list on the right to be considered heroes. This demonstrates UI list controls’ (ListView) ability to be synchronized with backend store lists (ObservableList).

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-10 Creating and Working with ObservableLists");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);

// create a grid pane
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(10);
gridpane.setVgap(10);

// candidates label
Label candidatesLbl = new Label("Candidates");
GridPane.setHalignment(candidatesLbl, HPos.CENTER);
gridpane.add(candidatesLbl, 0, 0);

Label heroesLbl = new Label("Heroes");
gridpane.add(heroesLbl, 2, 0);
GridPane.setHalignment(heroesLbl, HPos.CENTER);

// candidates
final ObservableList<String> candidates = FXCollections.observableArrayList("Super man",
"Spider man",
"Wolverine",
"Police",
"Fire Rescue",
"Soldiers",
"Dad & Mom",
"Doctor",
"Politician",
"Pastor",
"Teacher");
final ListView<String> candidatesListView = new ListView<>(candidates);
candidatesListView.setPrefWidth(150);
candidatesListView.setPrefHeight(150);

gridpane.add(candidatesListView, 0, 1);

// heros
final ObservableList<String> heroes = FXCollections.observableArrayList();
final ListView<String> heroListView = new ListView<>(heroes);
heroListView.setPrefWidth(150);
heroListView.setPrefHeight(150);

gridpane.add(heroListView, 2, 1);

// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction((e) -> {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
});

// deselect heroes
Button sendLeftButton = new Button("<");
sendLeftButton.setOnAction((e) -> {
String notHero = heroListView.getSelectionModel().getSelectedItem();
if (notHero != null) {
heroListView.getSelectionModel().clearSelection();
heroes.remove(notHero);
candidates.add(notHero);
}
});

VBox vbox = new VBox(5);
vbox.getChildren().addAll(sendRightButton,sendLeftButton);

gridpane.add(vbox, 1, 1);
GridPane.setConstraints(vbox, 1, 1, 1, 2,HPos.CENTER, VPos.CENTER);

root.getChildren().add(gridpane);
primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-10 depicts the hero selection application.

9781430268277_Fig14-10.jpg

Figure 14-10. ListViews and ObservableLists

How It Works

When dealing with Java collections you’ll notice there are so many useful container classes that represent all kinds of data structures. One commonly used collection is the java.util.ArrayList class. When building applications with domain objects that contain an ArrayList, developers can easily manipulate objects inside the collection. But, in the past (back in the day), when using Java Swing components combined with collections was a challenge, especially updating the GUI to reflect changes in the domain object. How do you resolve this issue? Well, JavaFX’s ObservableList to the rescue!

Speaking of rescue, this recipe demonstrates a GUI application to allow users to choose their favorite heroes. This is quite similar to application screens that manage user roles by adding or removing items from list box components. In JavaFX, use a ListView control to hold string objects. Before creating an instance of a ListView, the ObservableList containing the candidates is created. In the example, you’ll notice the use of a factory class called FXCollections, in which you can pass in common collection types to be wrapped and returned to the caller as an ObservableList. This recipe passes in an array of strings instead of an ArrayList, so hopefully you get the idea about how to use the FXCollections class. Be sure to use it wisely: “With great power, there must also come great responsibility.” This code line calls theFXCollections class to return an observable list (ObservableList):

ObservableList<String> candidates = FXCollections.observableArrayList(...);

After creating an ObservableList, a ListView class is instantiated using a constructor that receives the observable list. Shown here is code to create and populate a ListView object:

ListView<String> candidatesListView = new ListView<String>(candidates);

In the last item of business, the code will manipulate the ObservableLists as if they were java.util.ArrayLists. Once manipulated, the ListView will be notified and automatically updated to reflect the changes of the ObservableList. The following code snippet implements the event handler and action event when the user presses the send right button:

// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction((e) -> {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
});

When setting an action, you implement an EventHandler via a lambda expression to listen for a button press event. When a button press event arrives, the code will determine which item in the ListView was selected. Once the item was determined, you clear the selection, remove the item, and add the item to the hero’s ObservableList.

14-11. Generating a Background Process

Problem

You want to create a GUI application that simulates a long-running process using background processing while displaying the progress to the users.

Solution

Create an application typical of a dialog box that shows the progress indicators while copying files in the background. The following are the main classes used in this recipe:

· javafx.scene.control.ProgressBar

· javafx.scene.control.ProgressIndicator

· javafx.concurrent.Task classes

The following source code is an application that simulates a file copy dialog box displaying progress indicators and performing background processes:

package org.java8recipes.chapter14.recipe14_11;

import java.util.Random;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class BackgroundProcesses extends Application {

static Task copyWorker;
final int numFiles = 30;

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}

@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-11 Background Processes");
Group root = new Group();
Scene scene = new Scene(root, 330, 120, Color.WHITE);

BorderPane mainPane = new BorderPane();
mainPane.layoutXProperty().bind(scene.widthProperty().subtract(mainPane.widthProperty()).
divide(2));
root.getChildren().add(mainPane);

final Label label = new Label("Files Transfer:");
final ProgressBar progressBar = new ProgressBar(0);
final ProgressIndicator progressIndicator = new ProgressIndicator(0);

final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(label, progressBar, progressIndicator);
mainPane.setTop(hb);

final Button startButton = new Button("Start");
final Button cancelButton = new Button("Cancel");
final TextArea textArea = new TextArea();
textArea.setEditable(false);
textArea.setPrefSize(200, 70);
final HBox hb2 = new HBox();
hb2.setSpacing(5);
hb2.setAlignment(Pos.CENTER);
hb2.getChildren().addAll(startButton, cancelButton, textArea);
mainPane.setBottom(hb2);

// wire up start button
startButton.setOnAction((e) -> {
startButton.setDisable(true);
progressBar.setProgress(0);
progressIndicator.setProgress(0);
textArea.setText("");
cancelButton.setDisable(false);
copyWorker = createWorker(numFiles);

// wire up progress bar
progressBar.progressProperty().unbind();
progressBar.progressProperty().bind(copyWorker.progressProperty());
progressIndicator.progressProperty().unbind();
progressIndicator.progressProperty().bind(copyWorker.progressProperty());

// append to text area box
copyWorker.messageProperty().addListener(new ChangeListener<String>() {

public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
textArea.appendText(newValue + "\n");
}
});

new Thread(copyWorker).start();
});

// cancel button will kill worker and reset.
cancelButton.setOnAction((e) -> {
startButton.setDisable(false);
cancelButton.setDisable(true);
copyWorker.cancel(true);

// reset
progressBar.progressProperty().unbind();
progressBar.setProgress(0);
progressIndicator.progressProperty().unbind();
progressIndicator.setProgress(0);
textArea.appendText("File transfer was cancelled.");
});

primaryStage.setScene(scene);
primaryStage.show();
}

public Task createWorker(final int numFiles) {
return new Task() {

@Override
protected Object call() throws Exception {
for (int i = 0; i < numFiles; i++) {
long elapsedTime = System.currentTimeMillis();
copyFile("some file", "some dest file");
elapsedTime = System.currentTimeMillis() - elapsedTime;
String status = elapsedTime + " milliseconds";

// queue up status
updateMessage(status);
updateProgress(i + 1, numFiles);
}
return true;
}
};
}

public void copyFile(String src, String dest) throws InterruptedException {
// simulate a long time
Random rnd = new Random(System.currentTimeMillis());
long millis = rnd.nextInt(1000);
Thread.sleep(millis);
}
}

Figure 14-11 shows the Background Processes application, which simulates a file copy window.

9781430268277_Fig14-11.jpg

Figure 14-11. Background processes

How It Works

One of the main pitfalls of GUI development is knowing when and how to delegate work (Threads). You are constantly reminded of thread safety, especially when it comes to blocking the GUI thread. When using the Java Swing API, the SwingWorker object must be implemented to defer non-GUI work off of the event dispatch thread (EDT). Similar patterns and principles still apply in the world of JavaFX.

You begin by creating not one but two progress controls to show the user the work being done. One is a progress bar and the other is a progress indicator. The progress indicator shows a percentage below the indicator icon. The following code snippet shows the initial creation of progress controls:

final ProgressBar progressBar = new ProgressBar(0);
final ProgressIndicator progressIndicator = new ProgressIndicator(0);

Next, you create a worker thread via the createWorker() method. The createWorker() convenience method will instantiate and return a javafx.concurrent.Task object, which is similar to the Java Swing’s SwingWorker class. Unlike the SwingWorker class, theTask object is greatly simplified and easier to use. If you compare the last recipe you will notice that none of the GUI controls is passed into the Task. The clever JavaFX team has created observable properties that allow you to bind against. This fosters a more event-driven approach to handling work (tasks). When creating an instance of a Task object you implement the call() method to perform work in the background. During the work being done, you may wish to queue up intermediate results such as progress or text info. For this, you can call theupdateProgress() and updateMessage() methods. These methods will update information in a threadsafe manner so that the observer of the progress properties will be able to update the GUI safely without blocking the GUI thread. The following code snippet demonstrates the ability to queue up messages and progress:

// queue up status
updateMessage(status);
updateProgress(i + 1, numFiles);

After creating a worker Task, you unbind any old tasks bound to the progress controls. Once the progress controls are unbound, you then bind the progress controls to the newly created Task object called copyWorker. Shown here is the code used to rebind a new Task object to the progress UI controls:

// wire up progress bar
progressBar.progressProperty().unbind();
progressBar.progressProperty().bind(copyWorker.progressProperty());
progressIndicator.progressProperty().unbind();
progressIndicator.progressProperty().bind(copyWorker.progressProperty());

Next, you implement a ChangeListener to append the queued results into the TextArea control. Another remarkable thing about JavaFX properties is that you can attach many listeners similar to Java Swing components. Finally the worker and controls are all wired up to spawn a thread to go off in the background. The following code line shows how to launch a Task worker object:

new Thread(copyWorker).start();

Lastly, the Cancel button will simply call the Task object’s cancel() method to kill the process. Once the task is cancelled the progress controls are reset. Once a worker Task is cancelled it cannot be reused. When pressed, the Start button recreates a new Task. If you want a more robust solution, you should look at the javafx.concurrent.Service class. The following code line will cancel a Task worker object:

copyWorker.cancel(true);

14-12. Associating Keyboard Sequences with Applications

Problem

You want to create keyboard shortcuts for menu options.

Solution

Create an application that will use JavaFX’s key combination APIs. The main classes you will be using are shown here:

· javafx.scene.input.KeyCode

· javafx.scene.input.KeyCodeCombination

· javafx.scene.input.KeyCombination

The following source code listing is an application that displays the available keyboard shortcuts that are bound to the menu items. When the user performs a keyboard shortcut, the application will display the key combination on the screen:

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-12 Associating Keyboard Sequences");
Group root = new Group();
Scene scene = new Scene(root, 530, 300, Color.WHITE);

final StringProperty statusProperty = new SimpleStringProperty();

InnerShadow iShadow = new InnerShadow();
iShadow.setOffsetX(3.5f);
iShadow.setOffsetY(3.5f);

final Text status = new Text();
status.setEffect(iShadow);
status.setX(100);
status.setY(50);
status.setFill(Color.LIME);
status.setFont(Font.font(null, FontWeight.BOLD, 35));
status.setTranslateY(50);

status.textProperty().bind(statusProperty);
statusProperty.set("Keyboard Shortcuts \nCtrl-N, \nCtrl-S, \nCtrl-X");
root.getChildren().add(status);

MenuBar menuBar = new MenuBar();
menuBar.prefWidthProperty().bind(primaryStage.widthProperty());
root.getChildren().add(menuBar);

Menu menu = new Menu("File");
menuBar.getMenus().add(menu);

MenuItem newItem = new MenuItem();
newItem.setText("New");
newItem.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN));
newItem.setOnAction((e) -> {
statusProperty.set("Ctrl-N");
});
menu.getItems().add(newItem);

MenuItem saveItem = new MenuItem();
saveItem.setText("Save");
saveItem.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN));
saveItem.setOnAction((e) -> {
statusProperty.set("Ctrl-S");
});
menu.getItems().add(saveItem);

menu.getItems().add(new SeparatorMenuItem());

MenuItem exitItem = new MenuItem();
exitItem.setText("Exit");
exitItem.setAccelerator(new KeyCodeCombination(KeyCode.X, KeyCombination.CONTROL_DOWN));
exitItem.setOnAction((e) -> {
statusProperty.set("Ctrl-X");
});
menu.getItems().add(exitItem);

primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-12 displays an application that demonstrates keyboard shortcuts.

9781430268277_Fig14-12.jpg

Figure 14-12. Keyboard sequences/shortcuts

How It Works

The solution to this recipe demonstrates how to create key combination or keyboard shortcuts using the new javafx.scene.input.KeyCodeCombination and javafx.scene.input.KeyCombination classes. Seeing that the previous recipe was a tad boring, we decided to make things a little more interesting here. This recipe displays Text nodes onto the scene graph when the user performs the key combinations. When displaying the Text nodes, we applied an inner shadow effect. The following code snippet creates a Text node with an inner shadow effect:

InnerShadow iShadow = new InnerShadow();
iShadow.setOffsetX(3.5f);
iShadow.setOffsetY(3.5f);

final Text status = new Text();
status.setEffect(iShadow);
status.setX(100);
status.setY(50);
status.setFill(Color.LIME);
status.setFont(Font.font(null, FontWeight.BOLD, 35));
status.setTranslateY(50);

To create a keyboard shortcut, you simply call a menu or button control’s setAccelerator() method. In this recipe, the key combination are set using the MenuItem node’s setAccelerator() method. The following code line specifies the key combinations for a Ctrl-N:

MenuItem newItem = new MenuItem();
newItem.setText("New");
newItem.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN));
newItem.setOnAction((e) -> {
statusProperty.set("Ctrl-N");
});

As you can see from the code, when the accelerator (key combination) is pressed in the example, the onAction ActionEvent is triggered. It sets the statusProperty value to Ctrl-N via a lambda expression.

14-13. Creating and Working with Tables

Problem

You want to display items in a UI table control similar to Java Swing’s JTable component.

Solution

Create an application using JavaFX’s javafx.scene.control.TableView class. The TableView control provides the equivalent functionality to Swing’s JTable component.

To exercise the TableView control you will be creating an application that will display bosses and employees. On the left you will implement a ListView control containing bosses, and employees (subordinates) will be displayed in a TableView control on the right.

Shown here is the source code of a simple domain (Person) class to represent a boss or an employee to be displayed in a ListView or TableView control:

package org.java7recipes.chapter15.recipe15_14;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class Person {

private StringProperty aliasName;
private StringProperty firstName;
private StringProperty lastName;
private ObservableList<Person> employees = FXCollections.observableArrayList();

public final void setAliasName(String value) {
aliasNameProperty().set(value);
}

public final String getAliasName() {
return aliasNameProperty().get();
}

public StringProperty aliasNameProperty() {
if (aliasName == null) {
aliasName = new SimpleStringProperty();
}
return aliasName;
}

public final void setFirstName(String value) {
firstNameProperty().set(value);
}

public final String getFirstName() {
return firstNameProperty().get();
}

public StringProperty firstNameProperty() {
if (firstName == null) {
firstName = new SimpleStringProperty();
}
return firstName;
}

public final void setLastName(String value) {
lastNameProperty().set(value);
}

public final String getLastName() {
return lastNameProperty().get();
}

public StringProperty lastNameProperty() {
if (lastName == null) {
lastName = new SimpleStringProperty();
}
return lastName;
}

public ObservableList<Person> employeesProperty() {
return employees;
}

public Person(String alias, String firstName, String lastName) {
setAliasName(alias);
setFirstName(firstName);
setLastName(lastName);
}

}

The following is the main application code. It displays a list view component on the left containing bosses and a table view control on the right containing employees:

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-13 Working with Tables");
Group root = new Group();
Scene scene = new Scene(root, 500, 250, Color.WHITE);

// create a grid pane
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(10);
gridpane.setVgap(10);

// candidates label
Label candidatesLbl = new Label("Boss");
GridPane.setHalignment(candidatesLbl, HPos.CENTER);
gridpane.add(candidatesLbl, 0, 0);

// List of leaders
ObservableList<Person> leaders = getPeople();
final ListView<Person> leaderListView = new ListView<>(leaders);
leaderListView.setPrefWidth(150);
leaderListView.setPrefHeight(150);

// display first and last name with tooltip using alias
leaderListView.setCellFactory(new Callback<ListView<Person>, ListCell<Person>>() {
@Override
public ListCell<Person> call(ListView<Person> param) {
final Label leadLbl = new Label();
final Tooltip tooltip = new Tooltip();
final ListCell<Person> cell = new ListCell<Person>() {
@Override
public void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
leadLbl.setText(item.getAliasName());
setText(item.getFirstName() + " " + item.getLastName());
tooltip.setText(item.getAliasName());
setTooltip(tooltip);
}
}
}; // ListCell
return cell;

}
}); // setCellFactory

gridpane.add(leaderListView, 0, 1);

Label emplLbl = new Label("Employees");
gridpane.add(emplLbl, 2, 0);
GridPane.setHalignment(emplLbl, HPos.CENTER);

final TableView<Person> employeeTableView = new TableView<>();
employeeTableView.setPrefWidth(300);

final ObservableList<Person> teamMembers = FXCollections.observableArrayList();
employeeTableView.setItems(teamMembers);

TableColumn<Person, String> aliasNameCol = new TableColumn<>("Alias");
aliasNameCol.setEditable(true);
aliasNameCol.setCellValueFactory(new PropertyValueFactory("aliasName"));

aliasNameCol.setPrefWidth(employeeTableView.getPrefWidth() / 3);

TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
firstNameCol.setPrefWidth(employeeTableView.getPrefWidth() / 3);

TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
lastNameCol.setPrefWidth(employeeTableView.getPrefWidth() / 3);

employeeTableView.getColumns().setAll(aliasNameCol, firstNameCol, lastNameCol);
gridpane.add(employeeTableView, 2, 1);

// selection listening
leaderListView.getSelectionModel().selectedItemProperty().addListener(
(ObservableValue<? extends Person> observable,
Person oldValue, Person newValue) -> {
if (observable != null && observable.getValue() != null) {
teamMembers.clear();
teamMembers.addAll(observable.getValue().employeesProperty());
}
});

root.getChildren().add(gridpane);

primaryStage.setScene(scene);
primaryStage.show();
}

The following code shows the getPeople() method contained in the WorkingWithTables main application class. This method populates the UI TableView control shown previously:

private ObservableList<Person> getPeople() {
ObservableList<Person> people = FXCollections.<Person>observableArrayList();
Person docX = new Person(“Professor X”, “Charles”, “Xavier”);
docX.employeesProperty().add(new Person(“Wolverine”, “James”, “Howlett”));
docX.employeesProperty().add(new Person(“Cyclops”, “Scott”, “Summers”));
docX.employeesProperty().add(new Person(“Storm”, “Ororo”, “Munroe”));

Person magneto = new Person(“Magneto”, “Max”, “Eisenhardt”);
magneto.employeesProperty().add(new Person(“Juggernaut”, “Cain”, “Marko”));
magneto.employeesProperty().add(new Person(“Mystique”, “Raven”, “Darkhölme”));
magneto.employeesProperty().add(new Person(“Sabretooth”, “Victor”, “Creed”));

Person biker = new Person(“Mountain Biker”, “Jonathan”, “Gennick”);
biker.employeesProperty().add(new Person(“JavaJuneau”, “Joshua”, “Juneau”));
biker.employeesProperty().add(new Person(“Freddy”, “Freddy”, “Guime”));
biker.employeesProperty().add(new Person(“Mark”, “Mark”, “Beaty”));
biker.employeesProperty().add(new Person(“John”, “John”, “O’Conner”));
biker.employeesProperty().add(new Person(“D-Man”, “Carl”, “Dea”));

people.add(docX);
people.add(magneto);
people.add(biker);

return people;
}

Figure 14-13 displays the application that demonstrates JavaFX’s TableView control.

9781430268277_Fig14-13.jpg

Figure 14-13. Working with tables

How It Works

Just for fun we created a simple GUI to display employees and their bosses. You notice in Figure 14-13 on the left is a list of people (the bosses). When users select a boss, their employees will be shown to in the TableView area to the right. You’ll also notice the tooltip when you hover over the selected boss.

Before considering the TableView control, it’s important that you understand the ListView that is responsible for updating the TableView. In model-view fashion, an ObservableList is created that contains all the bosses for the ListView control’s constructor. This code calls the bosses leaders. The following code creates a ListView control:

// List of leaders
ObservableList<Person> leaders = getPeople();
final ListView<Person> leaderListView = new ListView<Person>(leaders);

Next, create a cell factory to properly display the person’s name in the ListView control. Because each item is a Person object, the ListView does not know how to render each row in the ListView control. You simply create a javafx.util.Callback generic type object by specifying the ListView<Person> and a ListCell<Person> data types. If you’re using a trusty IDE such as NetBeans, it will pregenerate things such as the implementing method call(). Next is the variable cell of type ListCell<Person> (within the call() method), in which you create a lambda expression. The lambda expression contains an implementation for an updateItem() method. To implement the updateItem() method, obtain the person information and update the Label control (leadLbl). Lastly, you set the tooltip to the associated text.

You then create a TableView control to display the employee base on the selected boss from the ListView. When creating a TableView, first create the column headers. Use the following code to create a table column:

TableColumn<String> firstNameCol = new TableColumn<String>("First Name");
firstNameCol.setProperty("firstName");

Once you have created a column, you’ll notice the setProperty() method, which is responsible for calling the person Bean’s property. When the list of employees is put into the TableView, it will know how to pull the properties to be placed in each cell in the table.

Last is the implementation of the selection listener on the ListViewer in JavaFX, called a selection item property (selectionItemProperty). Create and add a ChangeListener to listen to selection events. When a user selects a boss, the TableView is cleared and populated with the boss’ employees. Actually it is the magic of the ObservableList that notifies the TableView of changes. To populate the TableView via the teamMembers (ObservableList) variable, use this code:

teamMembers.clear();
teamMembers.addAll(observable.getValue().employeesProperty());

14-14. Organizing the UI with Split Views

Problem

You want to split up a GUI screen by using split divider controls.

Solution

Use JavaFX’s split pane control. The javafx.scene.control.SplitPane class is a UI control that enables you to divide a screen into frame-like regions. The split control allows users to move the divider between any two split regions with the mouse.

Shown here is the code used to create the GUI application that utilizes the javafx.scene.control.SplitPane class. That class divides the screen into three windowed regions. The three windowed regions are a left column, an upper-right region, and a lower-right region. In addition, Text nodes are added to the three regions.

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-14 Organizing UI with Split Views");
Group root = new Group();
Scene scene = new Scene(root, 350, 250, Color.WHITE);

// Left and right split pane
SplitPane splitPane = new SplitPane();
splitPane.prefWidthProperty().bind(scene.widthProperty());
splitPane.prefHeightProperty().bind(scene.heightProperty());

//List<Node> items = splitPane.getItems();
VBox leftArea = new VBox(10);

for (int i = 0; i < 5; i++) {
HBox rowBox = new HBox(20);
final Text leftText = new Text();
leftText.setText("Left " + i);
leftText.setTranslateX(20);
leftText.setFill(Color.BLUE);
leftText.setFont(Font.font(null, FontWeight.BOLD, 20));

rowBox.getChildren().add(leftText);
leftArea.getChildren().add(rowBox);
}
leftArea.setAlignment(Pos.CENTER);

// Upper and lower split pane
SplitPane splitPane2 = new SplitPane();
splitPane2.setOrientation(Orientation.VERTICAL);
splitPane2.prefWidthProperty().bind(scene.widthProperty());
splitPane2.prefHeightProperty().bind(scene.heightProperty());

HBox centerArea = new HBox();

InnerShadow iShadow = new InnerShadow();
iShadow.setOffsetX(3.5f);
iShadow.setOffsetY(3.5f);

final Text upperRight = new Text();
upperRight.setText("Upper Right");
upperRight.setX(100);
upperRight.setY(50);
upperRight.setEffect(iShadow);
upperRight.setFill(Color.LIME);
upperRight.setFont(Font.font(null, FontWeight.BOLD, 35));
upperRight.setTranslateY(50);
centerArea.getChildren().add(upperRight);

HBox rightArea = new HBox();

final Text lowerRight = new Text();
lowerRight.setText("Lower Right");
lowerRight.setX(100);
lowerRight.setY(50);
lowerRight.setEffect(iShadow);
lowerRight.setFill(Color.RED);
lowerRight.setFont(Font.font(null, FontWeight.BOLD, 35));
lowerRight.setTranslateY(50);
rightArea.getChildren().add(lowerRight);

splitPane2.getItems().add(centerArea);
splitPane2.getItems().add(rightArea);

// add left area
splitPane.getItems().add(leftArea);

// add right area
splitPane.getItems().add(splitPane2);

// evenly position divider
ObservableList<SplitPane.Divider> dividers = splitPane.getDividers();
for (int i = 0; i < dividers.size(); i++) {
dividers.get(i).setPosition((i + 1.0) / 3);
}

HBox hbox = new HBox();
hbox.getChildren().add(splitPane);
root.getChildren().add(hbox);

primaryStage.setScene(scene);
primaryStage.show();
}

Figure 14-14 depicts the application using split pane controls.

9781430268277_Fig14-14.jpg

Figure 14-14. Split views

How It Works

If you’ve ever seen a simple RSS reader or the Javadocs, you’ll notice that the screen is divided into sections with dividers. This recipe creates three areas—the left, upper right, and lower right.

You begin by creating a SplitPane that divides the left from the right area of the scene. Then you bind its width and height properties to the scene so the areas will take up the available space as the user resizes the stage. Next, you create a VBox layout control representing the left area. In the VBox (leftArea), you loop to generate a series of Text nodes. Next, generate the right side of the split pane. The following code snippet allows the split pane control (SplitPane) to divide horizontally:

SplitPane splitPane = new SplitPane();
splitPane.prefWidthProperty().bind(scene.widthProperty());
splitPane.prefHeightProperty().bind(scene.heightProperty());

Now you create the SplitPane to divide the area vertically, which will form the upper-right and lower-right region. Shown here is the code used to split a window region vertically:

// Upper and lower split pane
SplitPane splitPane2 = new SplitPane();
splitPane2.setOrientation(Orientation.VERTICAL);

At last you assemble the split panes and adjust the dividers to be positioned so that the screen real estate is divided evenly. The following code assembles the split panes and iterates through the list of dividers to update their positions:

splitPane.getItems().add(splitPane2);

// evenly position divider
ObservableList<SplitPane.Divider> dividers = splitPane.getDividers();
for (int i = 0; i < dividers.size(); i++) {
dividers.get(i).setPosition((i + 1.0) / 3);
}

HBox hbox = new HBox();
hbox.getChildren().add(splitPane);
root.getChildren().add(hbox);

14-15. Adding Tabs to the UI

Problem

You want to create a GUI application with tabs.

Solution

Use JavaFX’s tab and tab pane control. The tab (javafx.scene.control.Tab) and tab pane control (javafx.scene.control.TabPane) classes allow you to place graph nodes in individual tabs.

The following code example creates a simple application having menu options that allow users to choose a tab orientation. The available tab orientations are top, bottom, left, and right.

public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 14-15 Adding Tabs to a UI");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);

TabPane tabPane = new TabPane();

MenuBar menuBar = new MenuBar();

EventHandler<ActionEvent> action = changeTabPlacement(tabPane);

Menu menu = new Menu("Tab Side");
MenuItem left = new MenuItem("Left");

left.setOnAction(action);
menu.getItems().add(left);

MenuItem right = new MenuItem("Right");
right.setOnAction(action);
menu.getItems().add(right);

MenuItem top = new MenuItem("Top");
top.setOnAction(action);
menu.getItems().add(top);

MenuItem bottom = new MenuItem("Bottom");
bottom.setOnAction(action);
menu.getItems().add(bottom);

menuBar.getMenus().add(menu);

BorderPane borderPane = new BorderPane();

// generate 10 tabs
for (int i = 0; i < 10; i++) {
Tab tab = new Tab();
tab.setText("Tab" + i);
HBox hbox = new HBox();
hbox.getChildren().add(new Label("Tab" + i));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
}

// add tab pane
borderPane.setCenter(tabPane);

// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());

// add menu bar
borderPane.setTop(menuBar);

// add border Pane
root.getChildren().add(borderPane);

primaryStage.setScene(scene);
primaryStage.show();
}

private EventHandler<ActionEvent> changeTabPlacement(final TabPane tabPane) {
return (ActionEvent event) -> {
MenuItem mItem = (MenuItem) event.getSource();
String side = mItem.getText();
if ("left".equalsIgnoreCase(side)) {
tabPane.setSide(Side.LEFT);
} else if ("right".equalsIgnoreCase(side)) {
tabPane.setSide(Side.RIGHT);
} else if ("top".equalsIgnoreCase(side)) {
tabPane.setSide(Side.TOP);
} else if ("bottom".equalsIgnoreCase(side)) {
tabPane.setSide(Side.BOTTOM);
}
};
}

Figure 14-15 displays the tabs application, which allows users to change the tab orientation.

9781430268277_Fig14-15.jpg

Figure 14-15. TabPane

How It Works

When you use the TabPane control, you may already know the orientation in which you want your tabs to appear. This application allows you to set the orientation by the left, right, top, and bottom menu options.

If you’re familiar with the Swing API, you may notice that the JavaFX TabPane is very similar to the Swing JTabbedPanel. Instead of adding JPanels, you simply add javafx.scene.control.Tab instances. The following code snippet adds Tab controls to a TabPanecontrol:

TabPane tabPane = new TabPane();
Tab tab = new Tab();
tab.setText("Tab" + i);
tabPane.getTabs().add(tab);

When you’re changing the orientation the TabPane control, use the setSide() method. The following code sets the orientation of the TabPane control:

tabPane.setSide(Side.BOTTOM);

In this recipe, a Menu is used to change the orientation of the TabPane control. Different orientations were assigned to the different MenuItem nodes of the Menu, and an EventHandler identified as changeTabPlacement is used to change the orientation when the differentMenuItem is selected. The EventHandler simply inspects the text of the MenuItem to determine which orientation should be applied to the TabPane.

14-16. Developing a Dialog Box

Problem

You want to create an application that contains a dialog box containing some text fields for user entry.

Solution

Use JavaFX’s stage (javafx.stage.Stage) and scene (javafx.scene.Scene) APIs to create a dialog box.

The following source code listing is an application that simulates a change password dialog box. The application contains menu options to pop up the dialog box. In addition to using the menu options, users can set the dialog box’s modal state (modality).

public class DevelopingADialog extends Application {

static Stage LOGIN_DIALOG;
static int dx = 1;
static int dy = 1;

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}

private static Stage createLoginDialog(Stage parent, boolean modal) {
if (LOGIN_DIALOG != null) {
LOGIN_DIALOG.close();
}
return new MyDialog(parent, modal, "Welcome to JavaFX!");
}

@Override
public void start(final Stage primaryStage) {
primaryStage.setTitle("Chapter 14-16 Developing a Dialog");
Group root = new Group();
Scene scene = new Scene(root, 433, 312, Color.WHITE);

MenuBar menuBar = new MenuBar();
menuBar.prefWidthProperty().bind(primaryStage.widthProperty());

Menu menu = new Menu("Home");

// add change password menu itme
MenuItem newItem = new MenuItem("Change Password", null);
newItem.setOnAction((ActionEvent event) -> {
if (LOGIN_DIALOG == null) {
LOGIN_DIALOG = createLoginDialog(primaryStage, true);
}
LOGIN_DIALOG.sizeToScene();
LOGIN_DIALOG.show();
});

menu.getItems().add(newItem);

// add separator
menu.getItems().add(new SeparatorMenuItem());

// add non modal menu item
ToggleGroup modalGroup = new ToggleGroup();
RadioMenuItem nonModalItem = new RadioMenuItem();
nonModalItem.setToggleGroup(modalGroup);
nonModalItem.setText("Non Modal");
nonModalItem.setSelected(true);

nonModalItem.setOnAction((ActionEvent event) -> {
LOGIN_DIALOG = createLoginDialog(primaryStage, false);
});

menu.getItems().add(nonModalItem);

// add modal selection
RadioMenuItem modalItem = new RadioMenuItem();
modalItem.setToggleGroup(modalGroup);
modalItem.setText("Modal");
modalItem.setSelected(true);

modalItem.setOnAction((ActionEvent event) -> {
LOGIN_DIALOG = createLoginDialog(primaryStage, true);
});
menu.getItems().add(modalItem);

// add separator
menu.getItems().add(new SeparatorMenuItem());

// add exit
MenuItem exitItem = new MenuItem("Exit", null);
exitItem.setMnemonicParsing(true);
exitItem.setAccelerator(new KeyCodeCombination(KeyCode.X, KeyCombination.CONTROL_DOWN));
exitItem.setOnAction((ActionEvent event) -> {
Platform.exit();
});
menu.getItems().add(exitItem);

// add menu
menuBar.getMenus().add(menu);

// menu bar to window
root.getChildren().add(menuBar);

primaryStage.setScene(scene);
primaryStage.show();

addBouncyBall(scene);
}

private void addBouncyBall(final Scene scene) {

final Circle ball = new Circle(100, 100, 20);
RadialGradient gradient1 = new RadialGradient(0,
.1,
100,
100,
20,
false,
CycleMethod.NO_CYCLE,
new Stop(0, Color.RED),
new Stop(1, Color.BLACK));

ball.setFill(gradient1);

final Group root = (Group) scene.getRoot();
root.getChildren().add(ball);

Timeline tl = new Timeline();
tl.setCycleCount(Animation.INDEFINITE);
KeyFrame moveBall = new KeyFrame(Duration.seconds(.0200), (ActionEvent event) -> {
double xMin = ball.getBoundsInParent().getMinX();
double yMin = ball.getBoundsInParent().getMinY();
double xMax = ball.getBoundsInParent().getMaxX();
double yMax = ball.getBoundsInParent().getMaxY();

// Collision - boundaries
if (xMin < 0 || xMax > scene.getWidth()) {
dx = dx * -1;
}
if (yMin < 0 || yMax > scene.getHeight()) {
dy = dy * -1;
}

ball.setTranslateX(ball.getTranslateX() + dx);
ball.setTranslateY(ball.getTranslateY() + dy);
});

tl.getKeyFrames().add(moveBall);
tl.play();
}
}

class MyDialog extends Stage {

public MyDialog(Stage owner, boolean modality, String title) {
super();
initOwner(owner);
Modality m = modality ? Modality.APPLICATION_MODAL : Modality.NONE;
initModality(m);
setOpacity(.90);
setTitle(title);
Group root = new Group();
Scene scene = new Scene(root, 250, 150, Color.WHITE);
setScene(scene);

GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(5);
gridpane.setVgap(5);

Label mainLabel = new Label("Enter User Name & Password");
gridpane.add(mainLabel, 1, 0, 2, 1);

Label userNameLbl = new Label("User Name: ");
gridpane.add(userNameLbl, 0, 1);

Label passwordLbl = new Label("Password: ");
gridpane.add(passwordLbl, 0, 2);

// username text field
final TextField userNameFld = new TextField("Admin");
gridpane.add(userNameFld, 1, 1);

// password field
final PasswordField passwordFld = new PasswordField();
passwordFld.setText("drowssap");
gridpane.add(passwordFld, 1, 2);

Button login = new Button("Change");
login.setOnAction((ActionEvent event) -> {
close();
});
gridpane.add(login, 1, 3);
GridPane.setHalignment(login, HPos.RIGHT);
root.getChildren().add(gridpane);
}
}

Figure 14-16 depicts the change password dialog box application with the non-modal option enabled.

9781430268277_Fig14-16.jpg

Figure 14-16. Developing a dialog box

How It Works

To create dialogs, JavaFX uses another instance of a javafx.stage.Stage class to be displayed to the user. Similar to extending from a JDialog class in Swing, you simply extend from a Stage class. You have the opportunity to pass in the owning window in the constructor, which then calls the initOwner() method. The modal state of the dialog box can be set using the initModality() method. The following class extends from the Stage class, having a constructor initializing the owning stage and modal state:

class MyDialog extends Stage {

public MyDialog(Stage owner, boolean modality, String title) {
super();
initOwner(owner);
Modality m = modality ? Modality.APPLICATION_MODAL : Modality.NONE;
initModality(m);

...// The rest of the class

The rest of the code creates a scene (Scene) similar to the main application’s start() method. Because login forms are pretty boring, we decided to create an animation of a bouncing ball while the user is busy changing the password in the dialog box. (You will see more about creating animation in future recipes.)

When the menu item for Change Password is selected, the createLoginDialog method checks to see if there is already an instance of MyDialog instantiated. If so, it closes that instance and generates a new one. The newly created dialog is then displayed. Similarly, theRadioMenuItem controls call the createLoginDialog method, passing different Boolean values to indicate whether the instantiated MyDialog instance should be set to modal or not. As mentioned earlier, the bouncy ball has no bearing on the dialog; it’s just added for effect.

14-17. Printing with JavaFX

Problem

You want to provide the ability to print a designated node in your application scene graph.

Solution

Utilize the JavaFX Print API, new to JavaFX 8, to print designated nodes, and to construct sophisticated print dialogs. In this solution, a JavaFX application for drawing is generated. The drawing application allows you to print the canvas via a Print button. When the Print button is invoked, a dialog is opened that provides printing options such as printer and layout selection.

The following code is used to construct the application stage, including all buttons and drawing features. This first class does not contain any of the printing logic—you’ll see that next—and these sources are being shown to make it easy to follow along with the example.

public class PrintingWithJavaFX extends Application {

static Stage PRINT_DIALOG;

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(PrintingWithJavaFX.class, args);
}

private static Stage createPrintDialog(Stage parent, boolean modal, Canvas node) {
if (PRINT_DIALOG != null) {
PRINT_DIALOG.close();
}
// Copy canvas
WritableImage wim = new WritableImage(300, 300);
node.snapshot(null, wim);
ImageView iv = new ImageView();
iv.setImage(wim);
return new PrintDialog(parent, modal, "Printing Menu", iv);
}

@Override
public void start(Stage primaryStage) {

StackPane root = new StackPane();
Canvas canvas = new Canvas(300, 300);
final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();

final Button printButton = new Button("Print");
final BooleanProperty printingProperty = new SimpleBooleanProperty(false);
printButton.setOnAction(actionEvent-> {
printingProperty.set(true);
if (PRINT_DIALOG == null) {
PRINT_DIALOG = createPrintDialog(primaryStage, true, canvas);
}
PRINT_DIALOG.sizeToScene();
PRINT_DIALOG.show();
});
printButton.setTranslateX(3);

final Button resetButton = new Button("Reset");
resetButton.setOnAction(actionEvent-> {
graphicsContext.clearRect(1, 1,
graphicsContext.getCanvas().getWidth()-2,
graphicsContext.getCanvas().getHeight()-2);
});
resetButton.setTranslateX(10);

// Set up the pen color chooser
ChoiceBox colorChooser = new ChoiceBox(FXCollections.observableArrayList(
"Black", "Blue", "Red", "Green", "Brown", "Orange")
);
// Select the first option by default
colorChooser.getSelectionModel().selectFirst();

colorChooser.getSelectionModel().selectedIndexProperty().addListener(
(ChangeListener)(ov, old, newval) -> {
Number idx = (Number)newval;
Color newColor;
switch(idx.intValue()){
case 0: newColor = Color.BLACK;
break;
case 1: newColor = Color.BLUE;
break;
case 2: newColor = Color.RED;
break;
case 3: newColor = Color.GREEN;
break;
case 4: newColor = Color.BROWN;
break;
case 5: newColor = Color.ORANGE;
break;
default: newColor = Color.BLACK;
break;
}
graphicsContext.setStroke(newColor);

});
colorChooser.setTranslateX(5);

ChoiceBox sizeChooser = new ChoiceBox(FXCollections.observableArrayList(
"1", "2", "3", "4", "5")
);
// Select the first option by default
sizeChooser.getSelectionModel().selectFirst();

sizeChooser.getSelectionModel().selectedIndexProperty().addListener(
(ChangeListener)(ov, old, newval) -> {
Number idx = (Number)newval;

switch(idx.intValue()){
case 0: graphicsContext.setLineWidth(1);
break;
case 1: graphicsContext.setLineWidth(2);
break;
case 2: graphicsContext.setLineWidth(3);
break;
case 3: graphicsContext.setLineWidth(4);
break;
case 4: graphicsContext.setLineWidth(5);
break;
default: graphicsContext.setLineWidth(1);
break;
}
});
sizeChooser.setTranslateX(5);

canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (MouseEvent event) -> {
graphicsContext.beginPath();
graphicsContext.moveTo(event.getX(), event.getY());
graphicsContext.stroke();
});

canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (MouseEvent event) -> {
graphicsContext.lineTo(event.getX(), event.getY());
graphicsContext.stroke();
});

canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (MouseEvent event) -> {
});

HBox buttonBox = new HBox();
buttonBox.getChildren().addAll(printButton, colorChooser, sizeChooser, resetButton);

initDraw(graphicsContext, canvas.getLayoutX(), canvas.getLayoutY());

BorderPane container = new BorderPane();
container.setTop(buttonBox);

container.setCenter(canvas);

root.getChildren().add(container);
Scene scene = new Scene(root, 400, 400);
primaryStage.setTitle("Recipe 14-17: Printing from JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
}

private void initDraw(GraphicsContext gc, double x, double y){
double canvasWidth = gc.getCanvas().getWidth();
double canvasHeight = gc.getCanvas().getHeight();

gc.fill();
gc.strokeRect(
x, //x of the upper left corner
y, //y of the upper left corner
canvasWidth, //width of the rectangle
canvasHeight); //height of the rectangle

//gc.setFill(Color.RED);
//gc.setStroke(Color.BLUE);
//gc.setLineWidth(1);

}

}

Next, you will take a look at the sources to create the PrintDialog class, which contains all of the application’s printing logic. When use press the Print button, the dialog opens. It contains a handful of nodes that use the JavaFX Print API.

class PrintDialog extends Stage {

public PrintDialog(Stage owner, boolean modality, String title, Node printNode) {
super();
initOwner(owner);
Modality m = modality ? Modality.APPLICATION_MODAL : Modality.NONE;
initModality(m);
setOpacity(.90);
setTitle(title);
Group root = new Group();
Scene scene = new Scene(root, 450, 150, Color.WHITE);
setScene(scene);

GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(5);
gridpane.setVgap(5);

Label printerLabel = new Label("Printer: ");
gridpane.add(printerLabel, 0, 1);

Label layoutLabel = new Label("Layout: ");
gridpane.add(layoutLabel, 0, 2);

final Printer selectedPrinter = Printer.getDefaultPrinter();
// printer pick list
ChoiceBox printerChooser = new ChoiceBox(FXCollections.observableArrayList(
Printer.getAllPrinters())
);
// Select the first option by default
printerChooser.getSelectionModel().selectFirst();

gridpane.add(printerChooser, 1, 1);

ChoiceBox layoutChooser = new ChoiceBox(FXCollections.observableArrayList(
"Portait", "Landscape")
);
layoutChooser.getSelectionModel().selectFirst();

layoutChooser.getSelectionModel().selectedIndexProperty().addListener(
(ChangeListener)(ov, old, newval) -> {
Number idx = (Number)newval;
switch(idx.intValue()){
case 0: selectedPrinter.createPageLayout(Paper.A0, PageOrientation.
PORTRAIT, Printer.MarginType.EQUAL);
break;
case 1: selectedPrinter.createPageLayout(Paper.A0, PageOrientation.
LANDSCAPE, Printer.MarginType.EQUAL);
break;

default: selectedPrinter.createPageLayout(Paper.A0, PageOrientation.
PORTRAIT, Printer.MarginType.EQUAL);
break;
}
});
gridpane.add(layoutChooser,1,2);
Button printButton = new Button("Print");
printButton.setOnAction((ActionEvent event) -> {
print(printNode, selectedPrinter);
});
gridpane.add(printButton, 0, 3);

GridPane.setHalignment(printButton, HPos.RIGHT);
root.getChildren().add(gridpane);
}

public void print(final Node node, Printer printer) {

PrinterJob job = PrinterJob.createPrinterJob();
job.setPrinter(printer);
if (job != null) {
boolean success = job.printPage(node);
if (success) {
job.endJob();
}
}
}
}

Figure 14-17 shows the application. The area within the canvas (drawing area) is printed using the dialog (see Figure 14-18).

9781430268277_Fig14-17.jpg

Figure 14-17. JavaFX drawing application with print functionality

9781430268277_Fig14-18.jpg

Figure 14-18. Printing the menu by utilizing the JavaFX Print API

How It Works

In releases of JavaFX prior to JavaFX 8, there was no standard API for printing portions of an application stage. In JavaFX 8, a Print API has been added to standardize the way in which printing features are handled. The API also makes it easy to enable applications with printing functionality using very little code. The API is quite large, as it contains a number of classes, but it is very straight-forward and easy to use.

To enable print functionality for a specified node, start by working with the javafx.print.PrinterJob class, as it contains all of the functionality for generating a very simple printing task. To send a node to the default system printer, simply invokePrintJob.createPrinterJob() to return a PrinterJob object. Once the object has been returned, check to ensure that it is not null, and then call its printPage() method, passing the node to be printed. The excerpt of the solution that contains this functionality is shown in the following lines of code:

public void print(final Node node, Printer printer) {

PrinterJob job = PrinterJob.createPrinterJob()
job.setPrinter(printer);
if (job != null) {
boolean success = job.printPage(node);
if (success) {
job.endJob();
}
}
}

While use of the PrinterJob is all that is required to send a node to the printer, the API allows for much more customization. Table 14-3 lists the different classes available in the API, along with a brief description of what they do.

Table 14-3. JavaFX Print API

Class Name

Description

JobSettings

Encapsulates settings for a print job

PageLayout

Encapsulates layout settings

PrintRange

Used to select the range or constrain print pages

Paper

Encapsulates the paper sizes for printers

PaperSource

Input tray used for Paper

Printer

Represents the destination for a print job

PrinterAttributes

Encapsuates the attributes for a printer

PrinterJob

Used to invoke a JavaFX scene graph print

PrintResolution

Represents supported device resolution

In the example, a Print dialog is generated that allows users to select where to send the printjob. It also provides the controls to select the desired print layout (portait or landscape). The Printer.getDefaultPrinter() method can be invoked to return the default printer for the host machine. In the example, a ChoiceBox is used to display all of the printers that are available on the host by calling the Printer.getAllPrinters() method. The selected printer is then set on the PrinterJob within the print method, which sends the desired node to that printer.

The printer layout is chosen via another ChoiceBox, and the selected printer’s options are updated when a layout selection is made. The following line of code demonstrates how to set the layout to PageOrientation.PORTRAIT for a selected print:

selectedPrinter.createPageLayout(Paper.A0, PageOrientation.PORTRAIT, Printer.MarginType.EQUAL);

Any Node can be sent to a PrinterJob, but it is important to send a copy of the Node that you want to print, as the print task may modify that Node.

The Print API is large, but it is easy to understand. This recipe just scratches the surface on what is possible with the API. We recommend that you read through the Javadoc for more details, once you are ready to develop your own printer processes. However, this recipe should provide a basic understanding of how to get started. See the following link for the Javadoc: http://docs.oracle.com/javase/8/javafx/api/javafx/print/package-summary.html.

14-18. Embedding Swing Content in JavaFX

Problem

You want to embed some simple Java Swing content into a JavaFX application.

Solution

Create a JavaFX application and embed the Swing content into it using the SwingNode class. In the following example, a simple JavaFX application is used to toggle between a Swing-based user entry form and a JavaFX-based form. A JavaFX button within the application can be used to determine which of the forms should be displayed when the user clicks it.

First, let’s take a look at the code for the Swing form that is embedded into the JavaFX application. The code resides in a class entitled SwingForm.java.

import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class SwingForm extends JPanel {

JLabel formTitle, first, last, buttonLbl;
protected JTextField firstField, lastField;
public SwingForm(){

JPanel innerPanel = new JPanel();
GridLayout gl = new GridLayout(3,2);
innerPanel.setLayout(gl);

first = new JLabel("First Name:");
innerPanel.add(first);
firstField = new JTextField(10);
innerPanel.add(firstField);

last = new JLabel("Last Name:");
innerPanel.add(last);
lastField = new JTextField(10);
innerPanel.add(lastField);

JButton button = new JButton("Submit");
button.addActionListener((event) -> {
Platform.runLater(()-> {
UserEntryForm.fxLabel.setText("Message from Swing form...");
});
});
buttonLbl = new JLabel("Click Me:");
innerPanel.add(buttonLbl);
innerPanel.add(button);
add(innerPanel);
}
}

Next, let’s look at the JavaFX code that is used to create the graphical user interface, including the toggle button and the JavaFX form. Note that the Swing form is embedded using the SwingNode object.

public class UserEntryForm extends Application {

private static ToggleButton fxbutton;
private static GridPane grid;
public static Label fxLabel;

@Override
public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
createSwingContent(swingNode);
BorderPane pane = new BorderPane();
Image fxButtonIcon = new Image(
getClass().getResourceAsStream("images/duke1.gif"));
String buttonText = "Use Swing Form";
fxbutton = new ToggleButton(buttonText, new ImageView(fxButtonIcon));
fxbutton.setTooltip(
new Tooltip("This button chooses between the Swing and FX form"));
fxbutton.setStyle("-fx-font: 22 arial; -fx-base: #cce6ff;");
fxbutton.setAlignment(Pos.CENTER);
fxbutton.setOnAction((event)->{
ToggleButton toggle = (ToggleButton) event.getSource();
if(!toggle.isSelected()){
swingNode.setDisable(true);
swingNode.setVisible(false);
grid.setDisable(false);
grid.setVisible(true);
fxbutton.setText("Use Swing Form");
} else {
swingNode.setDisable(false);
swingNode.setVisible(true);
grid.setDisable(true);
grid.setVisible(false);
fxbutton.setText("Use JavaFX Form");
}
});
// Disable SwingNode by default
swingNode.setVisible(false);
Text appTitle = new Text("Swing/FX Form Demo");
appTitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));

pane.setTop(appTitle);
HBox formPanel = new HBox();
formPanel.setSpacing(10);
fxLabel = new Label("Message from JavaFX form...");

formPanel.getChildren().addAll(fxFormContent(), swingNode);

pane.setCenter(formPanel);
VBox vbox = new VBox();
vbox.getChildren().addAll(fxbutton, fxLabel);

pane.setBottom(vbox);

Scene scene = new Scene(pane, 700, 500);
stage.setScene(scene);
stage.setTitle("Swing Form Embedded In JavaFX");
stage.show();
}

private void createSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(() -> {
swingNode.setContent(new SwingForm());
});
}

private GridPane fxFormContent() {
grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));

Text scenetitle = new Text("Enter User");
scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
grid.add(scenetitle, 0, 0, 2, 1);

Label first = new Label("First Name:");
grid.add(first, 0, 1);

TextField firstField = new TextField();
grid.add(firstField, 1, 1);

Label last = new Label("Last Name:");
grid.add(last, 0, 2);

TextField lastField = new TextField();
grid.add(lastField, 1, 2);

Button messageButton = new Button("Click");
messageButton.setOnAction((event) ->{
fxLabel.setText("Message from JavaFX Form...");
});
grid.add(messageButton, 0,3);

return grid;

}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}

}

Upon invocation, the application looks like the one shown in Figure 14-19.

9781430268277_Fig14-19.jpg

Figure 14-19. Using SwingNode to embed a Swing form

How It Works

There are a great number of applications that have been written using the Java Swing framework. Sometimes it makes sense to make use of those applications from within a JavaFX application, or embed portions of those Swing applications where it makes sense. Thejavafx.embed.swing.SwingNode class makes it possible to embed a JComponent instance into a JavaFX application with little effort, by passing the JComponent to the SwingNode setContent() method. The content is repainted automatically and all events are forwarded to the JComponent instance without user intervention.

In the example to this recipe, a simple Java Swing form is embedded by instantiating a new SwingNode object and passing to it an instance of the class SwingForm. The Swing content should run on the Event Dispatch Thread (EDT), so any Swing access should be made on the EDT. That said, a new thread is created using SwingUtilities.invokeLater, and a lambda expression encapsulates the Runnable that is used to set the Swing content.

It is possible to interact with JavaFX content from within Swing code as well. To do so, you must run the JavaFX code within the JavaFX application thread by making a call to the javafx.application.Platform class and invoking the runLater() method, passing aRunnable. For instance, in the example code, the button in the Swing form can call back to the JavaFX label to change the text using the following code. Note that the JavaFX label is a public field, so it is accessible directly from within the Swing class.

JButton button = new JButton("Submit");
button.addActionListener((event) -> {
Platform.runLater(()-> {
UserEntryForm.fxLabel.setText("Message from Swing form...");
});
});

Image Note By default, the JavaFX application thread and the Swing Event Dispatch Thread (EDT) are separated. The EDT does not run the GUI code for a Swing application. However, in JavaFX, the platform GUI thread runs the application code. There is an experimental setting that enables single threading mode, which allows the JavaFX platform GUI thread to become the EDT when using Swing and JavaFX together. To enable the experimental setting, execute your code with the following option: Djavafx.embed.singleThread=true

By utilzing the new features of JavaFX 8, you can generate a JavaFX application that contains embedded Swing code that can communicate directly with the JavaFX code.

Summary

JavaFX is the successor to the Java Swing API. It enables developers to produce sophisticated and powerful user interfaces for the next generation of applications. This chapter provided you with a basic understanding of JavaFX, along with some of the most widely used JavaFX APIs. Over the course of the next few chapters, you’ll learn more about JavaFX, such as how to construct 3D objects and WebViews.