JavaFX Already Covered
JavaFX Already Covered
Like all successful languages, Java continues to evolve and improve. This also applies to its
libraries. One of the most important examples of this evolutionary process is found in its GUI
frameworks. The original GUI framework was the AWT. Because of its several limitations, it
was soon followed by Swing, which offered a far superior approach to creating GUIs. Swing was
so successful that it has remained the primary Java GUI framework for over a decade. (And a
decade is a long time in the fast-moving world of programming!) However, Swing was designed
when the enterprise application dominated software development. Today, consumer applications,
and especially mobile apps, have risen in importance, and such applications often demand a GUI
that has “visual sparkle.” Furthermore, no matter the type of application, the trend is toward
more exciting visual effects. To better handle these types of GUIs, a new approach was needed,
and this lead to the creation of JavaFX. JavaFX is Java’s next-generation client platform and
GUI framework. JavaFX provides a powerful, streamlined, flexible framework that
simplifies the creation of modern, visually exciting GUIs. As such, it is a very large system,
and, as was the case with Swing, it is not possible to describe it fully in this book. Instead, the
purpose of this and the next two chapters is to introduce several of its key features and
techniques. Once you understand the fundamentals, you will find it easy to explore other aspects
of JavaFX on your own.
The original JavaFX was based on a scripting language called JavaFX Script. However,
JavaFX Script has been discontinued. Beginning with the release of JavaFX 2.0, JavaFX
has been programmed in Java itself and provides a comprehensive API. JavaFX also
supports FXML, which can be (but is not required to be) used to specify the user interface.
JavaFX has been bundled with Java since JDK 7, update 4. The latest version of JavaFX is
JavaFX 8, which is bundled with JDK 8. (The version number is 8 to align with the JDK
version. Thus, the numbers 3 through 7 were skipped.) Because, at the time of this writing,
JavaFX 8 represents the latest version of JavaFX, it is the version of JavaFX discussed here.
Furthermore, when the term JavaFX is used in this and the following chapters, it refers to
JavaFX 8.
NOTE This and the following two chapters assume that you have a basic understanding of event
handling, and Swing fundamentals.
The init( ) method is called when the application begins execution. It is used to perform various
initializations. As will be explained, however, it cannot be used to create a stage or build a scene.
If no initializations are required, this method need not be overridden because an empty, default
version is provided.
The start( ) method is called after init( ). This is where your application begins and it can be
used to construct and set the scene. Notice that it is passed a reference to a Stage object. This is
the stage provided by the run-time system and is the primary stage. (You can also create other
stages, but you won’t need to for simple applications.) Notice that this method is abstract. Thus,
it must be overridden by your application When your application is terminated, the stop( )
method is called. It is here that you can handle any cleanup or shutdown chores. In cases in
which no such actions are needed, an empty, default version is provided.
Launching a JavaFX Application
To start a free-standing JavaFX application, you must call the launch( ) method defined by
Application. It has two forms. Here is the one used in this chapter:
public static void launch(String ... args)
Here, args is a possibly empty list of strings that typically specify command-line arguments.
When called, launch( ) causes the application to be constructed, followed by calls to init( ) and
start( ). The launch( ) method will not return until after the application has terminated. This
version of launch( ) starts the subclass of Application from which launch( ) is called. The
second form of launch( ) lets you specify a class other than the enclosing class to start. Before
moving on, it is necessary to make an important point: JavaFX applications that have been
packaged by using the javafxpackager tool (or its equivalent in an IDE) do not need to include a
call to launch( ). However, its inclusion often simplifies the test/debug cycle, and it lets the
program be used without the creation of a JAR file. Thus, it is included in all of the JavaFX
programs in this book.
A JavaFX Application Skeleton
All JavaFX applications share the same basic skeleton. Therefore, before looking at any more
JavaFX features, it will be useful to see what that skeleton looks like. In addition to showing the
general form of a JavaFX application, the skeleton also illustrates how to launch the application
and demonstrates when the life-cycle methods are called. A message noting when each life-cycle
method is called is displayed on the console. The complete skeleton is shown here:
// A JavaFX application skeleton.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
public class JavaFXSkel extends Application {
public static void main(String[] args) {
System.out.println("Launching JavaFX application.");
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the init() method.
public void init() { System.out.println("Inside the init() method.");
}
// Override the start() method.
public void start(Stage myStage) {
System.out.println("Inside the start() method.");
// Give the stage a title.
myStage.setTitle("JavaFX Skeleton.");
// Create a root node. In this case, a flow layout pane is used, but several alternatives exist.
FlowPane rootNode = new FlowPane();
// Create a scene.
Scene myScene = new Scene(rootNode, 800, 500);
// Set the scene on the stage.
myStage.setScene(myScene);
// Show the stage and its scene.
myStage.show();
}
// Override the stop() method.
public void stop() {
System.out.println("Inside the stop() method.");
}
}
Although the skeleton is quite short, it can be compiled and run. It produces the
window shown here:
myStage.setTitle("JavaFX Skeleton.");
Although this step is not necessarily required, it is customary for stand-alone applications. This
title becomes the name of the main application window. Next, a root node for a scene is created.
The root node is the only node in a scene graph that does not have a parent. In this case, a
FlowPane is used for the root node, but there are several other classes that can be used for the
root.
FlowPane rootNode = new FlowPane();
As mentioned, a FlowPane is a layout manager that uses a flow layout. This is a layout in which
elements are positioned line-by-line, with lines wrapping as needed. (Thus, it works much like
the FlowLayout class used by the AWT and Swing.) In this case, a horizontal flow is used, but it
is possible to specify a vertical flow. Although not needed by this skeletal application, it is also
possible to specify other layout properties, such as a vertical and horizontal gap between
elements, and an alignment. You will see an example of this later in this chapter.
Scene provides several versions of its constructor. The one used here creates a scene that has the
specified root with the specified width and height. It is shown here:
Notice that the type of rootnode is Parent. It is a subclass of Node and encapsulates nodes that
can have children. Also notice that the width and the height are double values. This lets you pass
fractional values, if needed. In the skeleton, the root is rootNode, the width is 300 and the height
is 200.
The next line in the program sets myScene as the scene for myStage:
myStage.setScene(myScene);
Here, setScene( ) is a method defined by Stage that sets the scene to that specified by its
argument. In cases in which you don’t make further use of the scene, you can combine the
previous two steps, as shown here:
myStage.setScene(new Scene(rootNode, 300, 200));
Because of its compactness, this form will be used by most of the subsequent examples. The last
line in start( ) displays the stage and its scene:
myStage.show();
In essence, show( ) shows the window that was created by the stage and screen. When you close
the application, its window is removed from the screen and the stop( ) method is called by the
JavaFX run-time system. In this case, stop( ) simply displays a message on the console,
illustrating when it is called. However, stop( ) would not normally display anything.
Furthermore, if your application does not need to handle any shutdown actions, there is no
reason to override stop( ) because an empty, default implementation is provided.
One important advantage of JavaFX is that the same program can be run in a variety of different
execution environments. For example, you can run a JavaFX program as a stand-alone desktop
application, inside a web browser, or as a Web Start application. However, different ancillary
files may be needed in some cases, for example, an HTML file or a JavaNetwork Launch
Protocol (JNLP) file.
In general, a JavaFX program is compiled like any other Java program. However, because of the
need for additional support for various execution environments, the easiest way to compile a
JavaFX application is to use an Integrated Development Environment (IDE) that fully supports
JavaFX programming, such as NetBeans. Just follow the instructions for the IDE you are using.
Alternatively, if you want to compile and test a JavaFX application using the commandline tools,
you can easily do so. Just compile and run the application in the normal way, using javac and
java. Be aware that using the command-line compiler neither creates any HTML or JNLP files
that would be needed if you want to run the application in a way other than as a stand-alone
application, nor does it create a JAR file for the program. To create these files, you need to use a
tool such as javafxpackager.
The Application Thread
In the preceding discussion, it was mentioned that you cannot use the init( ) method to construct
a stage or scene. You also cannot create these items inside the application’s constructor. The
reason is that a stage or scene must be constructed on the application thread. However, the
application’s constructor and the init( ) method are called on the main thread, also called the
launcher thread. Thus, they can’t be used to construct a stage or scene. Instead, you must use the
start( ) method, as the skeleton demonstrates, to create the initial GUI because start( ) is called
on the application thread.
Furthermore, any changes to the GUI currently displayed must be made from the application
thread. Fortunately, in JavaFX, events are sent to your program on the application thread.
Therefore, event handlers can be used to interact with the GUI. The stop( ) method is also called
on the application thread.
A Simple JavaFX Control: Label
The primary ingredient in most user interfaces is the control because a control enables the user to
interact with the application. As you would expect, JavaFX supplies a rich assortment of
controls. The simplest control is the label because it just displays a message, which, in this
example, is text. Although quite easy to use, the label is a good way to introduce the techniques
needed to begin building a scene graph.
The JavaFX label is an instance of the Label class, which is packaged in javafx.scene.control.
Label inherits Labeled and Control, among other classes. The Labeled class defines several
features that are common to all labeled elements (that is, those that can contain text), and
Control defines features related to all controls.
Label defines three constructors. The one we will use here is
Label(String str)
Here, str is the string that is displayed.
Once you have created a label (or any other control), it must be added to the scene’s content,
which means adding it to the scene graph. To do this, you will first call getChildren( ) on the
root node of the scene graph. It returns a list of the child nodes in the form of an
ObservableList<Node>. ObservableList is packaged in javafx.collections, and it inherits
java.util.List, which means that it supports all of the features available to a list as defined by the
Collections Framework. Using the returned list of child nodes, you can add the label to the list by
calling add( ), passing in a reference to the label.
The following program puts the preceding discussion into action by creating a simple JavaFX
application that displays a label:
// Demonstrate a JavaFX label.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
public class JavaFXLabelDemo extends Application {
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Demonstrate a JavaFX label.");
// Use a FlowPane for the root node.
FlowPane rootNode = new FlowPane();
// Create a scene.
Scene myScene = new Scene(rootNode, 300, 200);
// Set the scene on the stage.
myStage.setScene(myScene);
// Create a label.
Label myLabel = new Label("This is a JavaFX label");
// Add the label to the scene graph.
rootNode.getChildren().add(myLabel);
// Show the stage and its scene.
myStage.show();
}
}
This program produces the following window:
Here, handler is the handler being registered. As mentioned, often you will use an anonymous
inner class or lambda expression for the handler. The setOnAction( ) method sets the property
onAction, which stores a reference to the handler. As with all other Java event handling, your
handler must respond to the event as fast as possible and then return. If your handler consumes
too much time, it will noticeably slow down the application. For lengthy operations, you must
use a separate thread of execution.
Let’s examine a few key portions of this program. First, notice how buttons are createdby these
two lines:
Button btnAlpha = new Button("Alpha");
Button btnBeta = new Button("Beta");
This creates two text-based buttons. The first displays the string Alpha; the second displays Beta.
Next, an action event handler is set for each of these buttons. The sequence for the Alpha button
is shown here:
// Handle the action events for the Alpha button.
btnAlpha.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
response.setText("Alpha was pressed.");
}
});
As explained, buttons respond to events of type ActionEvent. To register a handler for these
events, the setOnAction( ) method is called on the button. It uses an anonymous inner class to
implement the EventHandler interface. (Recall that EventHandler defines only the handle( )
method.) Inside handle( ), the text in the response label is set to reflect the fact that the Alpha
button was pressed. Notice that this is done by calling the setText( ) method on the label. Events
are handled by the Beta button in the same way. Note that response is declared as a field within
FXEventDemo, rather than as a local variable. This is because it is accessed within the button
event handlers, which are anonymous inner classes. After the event handlers have been set, the
response label and the buttons btnAlpha and btnBeta are added to the scene graph by using a
call to addAll( ):
rootNode.getChildren().addAll(btnAlpha, btnBeta, response);
The addAll( ) method adds a list of nodes to the invoking parent node. Of course, these nodes
could have been added by three separate calls to add( ), but the addAll( ) method is more
convenient to use in this situation. There are two other things of interest in this program that
relate to the way the controls are displayed in the window. First, when the root node is created,
this statement is used:
FlowPane rootNode = new FlowPane(10, 10);
Here, the FlowPane constructor is passed two values. These specify the horizontal and vertical
gap that will be left around elements in the scene. If these gaps are not specified, then two
elements (such as two buttons) would be positioned in such a way that no space is between them.
Thus, the controls would run together, creating a very unappealing user interface. Specifying
gaps prevents this. The second point of interest is the following line, which sets the alignment of
the elements in the FlowPane:
rootNode.setAlignment(Pos.CENTER);
Here, the alignment of the elements is centered. This is done by calling setAlignment( ) on the
FlowPane. The value Pos.CENTER specifies that both a vertical and horizontal center will be
used. Other alignments are possible. Pos is an enumeration that specifies alignment constants. It
is packaged in javafx.geometry.
Before moving on, one more point needs to be made. The preceding program used anonymous
inner classes to handle button events. However, because the EventHandler interface defines
only one abstract method, handle( ), a lambda expression could have passed to setOnAction( ),
instead. In this case, the parameter type of setOnAction( ) would supply the target context for
the lambda expression. For example, here is the handler for the Alpha button, rewritten to use a
lambda:
btnAlpha.setOnAction( (ae) ->
response.setText("Alpha was pressed.")
);
Notice that the lambda expression is more compact than the anonymous inner class. Because
lambda expressions are a new feature just recently added to Java, but the anonymous inner class
is a widely used construct, readily understood by nearly all Java programmers, the event handlers
in subsequent examples will use anonymous inner classes. This will also allow the examples to
be compiled by readers using JDK 7 (which does not support lambdas). However, on your own,
you might want to experiment with converting them to lambda expressions. It is a good way to
gain experience using lambdas in your own code.
As mentioned early on, JavaFX handles rendering tasks for you automatically, rather than you
handling them manually. This is one of the most important ways that JavaFX improves on
Swing. As you may know, in Swing or the AWT, you must call the repaint( ) method to cause a
window to be repainted. Furthermore, your application needs to store the window contents,
redrawing them when painting is requested. JavaFX eliminates this tedious mechanism because
it keeps track of what you display in a scene and redisplays that scene as needed. This is called
retained mode. With this approach, there is no need to call a method like repaint( ). Rendering is
automatic.
One place that JavaFX’s approach to rendering is especially helpful is when displayinggraphics
objects, such as lines, circles, and rectangles. JavaFX’s graphics methods are foundin the
GraphicsContext class, which is part of java.scene.canvas. These methods can beused to draw
directly on the surface of a canvas, which is encapsulated by the Canvas classin
java.scene.canvas. When you draw something, such as a line, on a canvas, JavaFX
automatically renders it whenever it needs to be redisplayed.
Before you can draw on a canvas, you must perform two steps. First, you must create aCanvas
instance. Second, you must obtain a GraphicsContext object that refers to thatcanvas. You can
then use the GraphicsContext to draw output on the canvas.The Canvas class is derived from
Node; thus it can be used as a node in a scene graph.
Canvas defines two constructors. One is the default constructor, and the other is the oneshown
here:
Canvas(double width, double height)
Here, width and height specify the dimensions of the canvas.
To obtain a GraphicsContext that refers to a canvas, call getGraphicsContext2D( ).Here is its
general form:
GraphicsContext getGraphicsContext2D( )
It draws a line from startX,startY to endX,endY, using the current stroke, which can be a
solidcolor or some more complex style.
To draw a rectangle, use either strokeRect( ) or fillRect( ), shown here:
The upper-left corner of the rectangle is at topX,topY. The width and height parameters specify
its width and height. The strokeRect( ) method draws the outline of a rectangle using stroke,
and fillRect( ) fills the rectangle with the current fill. The current fill can beas simple as a solid
color or something more complex.
To draw an ellipse, use either strokeOval( ) or fillOval( ), shown next:
The upper-left corner of the rectangle that bounds the ellipse is at topX,topY. The width and
height parameters specify its width and height. The strokeOval( ) method draws the outline of
an ellipse using the current stroke, and fillOval( ) fills the oval with the current fill. To draw a
circle, pass the same value for width and height.
You can draw text on a canvas by using the strokeText( ) and fillText( ) methods. We
will use this version of fillText( ):
It displays str starting at the location specified by topX,topY, filling the text with the current
fill.You can set the font and font size of the text being displayed by using setFont( ). You can
obtain the font used by the canvas by calling getFont( ). By default, the system font is used. You
can create a new font by constructing a Font object. Font is packaged in javafx.scene.text. For
example, you can create a default font of a specified size by using this constructor:
Font(double fontSize)
Here, fontSize specifies the size of the font.
You can specify the fill and stroke using these two methods defined by Canvas:
void setFill(Paint newFill)
void setStroke(Paint newStroke)
Notice that the parameter of both methods is of type Paint. This is an abstract class packaged in
javafx.scene.paint. Its subclasses define fills and strokes. The one we will use is Color, which
simply describes a solid color. Color defines several static fields that specify a wide array of
colors, such as Color.BLUE, Color.RED, Color.GREEN, and so on.
The following program uses the aforementioned methods to demonstrate drawing on a canvas. It
first displays a few graphic objects on the canvas. Then, each time the Change Color button is
pressed, the color of three of the objects changes color. If you run the program,you will see that
the shapes whose color is not changed are unaffected by the change in color of the other objects.
Furthermore, if you try covering and then uncovering the window,
you will see that the canvas is automatically repainted, without any other actions on the part
of your program. Sample output is shown here:
// Demonstrate drawing.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.shape.*;
import javafx.scene.canvas.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
public class DirectDrawDemo extends Application {
GraphicsContext gc;
Color[] colors = { Color.RED, Color.BLUE, Color.GREEN, Color.BLACK };
int colorIdx = 0;
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Draw Directly to a Canvas.");
// Use a FlowPane for the root node.
FlowPane rootNode = new FlowPane();
// Center the nodes in the scene.
rootNode.setAlignment(Pos.CENTER);
// Create a scene.
Scene myScene = new Scene(rootNode, 450, 450);
// Set the scene on the stage.
myStage.setScene(myScene);
// Create a canvas.
Canvas myCanvas = new Canvas(400, 400);
// Get the graphics context for the canvas.
gc = myCanvas.getGraphicsContext2D();
// Create a push button.
Button btnChangeColor = new Button("Change Color");
// Handle the action events for the Change Color button.
btnChangeColor.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
// Set the stroke and fill color.
gc.setStroke(colors[colorIdx]);
gc.setFill(colors[colorIdx]);
// Redraw the line, text, and filled rectangle in the new color. This leaves the color of the
other //nodes unchanged.
gc.strokeLine(0, 0, 200, 200);
gc.fillText("This is drawn on the canvas.", 60, 50);
gc.fillRect(100, 320, 300, 40);
// Change the color.
colorIdx++;
if(colorIdx == colors.length) colorIdx= 0;
}
});
// Draw initial output on the canvas.
gc.strokeLine(0, 0, 200, 200);
gc.strokeOval(100, 100, 200, 200);
gc.strokeRect(0, 200, 50, 200);
gc.fillOval(0, 0, 20, 20);
gc.fillRect(100, 320, 300, 40);
// Set the font size to 20 and draw text.
gc.setFont(new Font(20));
gc.fillText("This is drawn on the canvas.", 60, 50);
// Add the canvas and button to the scene graph.
rootNode.getChildren().addAll(myCanvas, btnChangeColor);
// Show the stage and its scene.
myStage.show();
}
}
It is important to emphasize that GraphicsContext supports many more operations thanthose
demonstrated by the preceding program. For example, you can apply varioustransforms,
rotations, and effects. Despite its power, its various features are easy to masterand use. One other
point: A canvas is transparent. Therefore, if you stack canvases, thecontents of both will show.
This may be useful in some situations.
NOTE javafx.scene.shape contains several classes that can also be used to draw various types of
graphical shapes, such as circles, arcs, and lines. These are represented by nodes and can,
therefore,be directly part of the scene graph. You will want to explore these on your own.
Exploring JavaFXControls
The previous chapter described several of the core concepts relating to the JavaFX GUI
framework. In the process, it introduced two controls: the label and the button. This chapter
continues the discussion of JavaFX controls. It begins by describing how to include images in a
label and button. It then presents an overview of several more JavaFX controls, including check
boxes, lists, and trees. Keep in mind that JavaFX is a rich and powerful framework.The purpose
of this chapter is to introduce a representative sampling of the JavaFX controls and to describe
several common techniques. Once you understand the basics, you will be able to easily learn the
other controls.
The JavaFX control classes discussed in this chapter are shown here:
These and the other JavaFX controls are packaged in javafx.scene.control. Also discussed are
the Image and ImageView classes, which provide support for images in controls; Tooltip,
which is used to add tooltips to a control; as well as various effects and transforms.
Using Image and ImageView
Several of JavaFX’s controls let you include an image. For example, in addition to text, you can
specify an image in a label or a button. Furthermore, you can embed stand-alone images in a
scene directly. At the foundation for JavaFX’s support for images are two classes: Image and
ImageView. Image encapsulates the image, itself, and ImageView manages the display of an
image. Both classes are packaged in javafx.scene.image.The Image class loads an image from
either an InputStream, a URL, or a path to the image file. Image defines several constructors;
this is the one we will use:
Image(String url)
Here, url specifies a URL or a path to a file that supplies the image. The argument is assumed
to refer to a path if it does not constitute a properly formed URL. Otherwise, the image is loaded
from the URL. The examples that follow will load images from files on the local filesystem.
Other constructors let you specify various options, such as the image’s width and height. One
other point: Image is not derived from Node. Thus, it cannot, itself, be part of a scene graph.
Once you have an Image, you will use ImageView to display it. ImageView is derived from
Node, which means that it can be part of a scene graph. ImageView defines three constructors.
The first one we will use is shown here:
ImageView(Image image)
This constructor creates an ImageView that uses image for its image. Putting the preceding
discussion into action, here is a program that loads an image of an hourglass and displays it via
ImageView. The hourglass image is contained in a file called hourglass.png, which is assumed
to be in the local directory.
// Load and display an image.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.geometry.*;
import javafx.scene.image.*;
public class ImageDemo extends Application {
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Display an Image");
// Use a FlowPane for the root node.
FlowPane rootNode = new FlowPane();
// Use center alignment.
rootNode.setAlignment(Pos.CENTER);
// Create a scene.
Scene myScene = new Scene(rootNode, 300, 200);
// Set the scene on the stage.
myStage.setScene(myScene);
// Create an image.
Image hourglass = new Image("hourglass.png");
// Create an image view that uses the image.
ImageView hourglassIV = new ImageView(hourglass);
// Add the image to the scene graph.
rootNode.getChildren().add(hourglassIV);
// Show the stage and its scene.
myStage.show();
}
}
Sample output from the program is shown here:
In the program, pay close attention to the following sequence that loads the image andthen
creates an ImageView that uses that image:
// Create an image.
Image hourglass = new Image("HourGlass.png");
// Create an image view that uses the image.
ImageView hourglassIV = new ImageView(hourglass);
As explained, an image by itself cannot be added to the scene graph. It must first beembedded in
an ImageView.In cases in which you won’t make further use of the image, you can specify a
URL orfilename when creating an ImageView. In this case, there is no need to explicitly create
anImage. Instead, an Image instance containing the specified image is constructed
automaticallyand embedded in the ImageView. Here is the ImageView constructor that does
this:
ImageView(String url)
Here, url specifies the URL or the path to a file that contains the image.
Adding an Image to a Label
As explained in the previous chapter, the Label class encapsulates a label. It can display a text
message, a graphic, or both. So far, we have used it to display only text, but it is easy to add an
image. To do so, use this form of Label’s constructor:
Label(String str, Node image)
Here, str specifies the text message and image specifies the image. Notice that the image is of
type Node. This allows great flexibility in the type of image added to the label, but for our
purposes, the image type will be ImageView. Here is a program that demonstrates a label that
includes a graphic. It creates a label that displays the string "Hourglass" and shows the image of
an hourglass that is loaded from
the hourglass.png file.
// Demonstrate an image in a label.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.geometry.*;
import javafx.scene.image.*;
public class LabelImageDemo extends Application {
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Use an Image in a Label");
// Use a FlowPane for the root node.
FlowPane rootNode = new FlowPane();
// Use center alignment.
rootNode.setAlignment(Pos.CENTER);
// Create a scene.
Scene myScene = new Scene(rootNode, 300, 200);
// Set the scene on the stage.
myStage.setScene(myScene);
// Create an ImageView that contains the specified image.
ImageView hourglassIV = new ImageView("hourglass.png");
// Create a label that contains both an image and text.
Label hourglassLabel = new Label("Hourglass", hourglassIV);
// Add the label to the scene graph.
rootNode.getChildren().add(hourglassLabel);
// Show the stage and its scene.
myStage.show();
}
}
Here is the window produced by the program:
As you can see, both the image and the text are displayed. Notice that the text is to the right of
the image. This is the default. You can change the relative positions of the image and text by
calling setContentDisplay( ) on the label. It is shown here:
final void setContentDisplay(ContentDisplay position)
The value passed to position determines how the text and image is displayed. It must be one of
these values, which are defined by the ContentDisplay enumeration:
With the exception of TEXT_ONLY and GRAPHIC_ONLY, the values specify the locationof
the image. For example, if you add this line to the preceding
program:hourglassLabel.setContentDisplay(ContentDisplay.TOP);the image of the hourglass
will be above the text, as shown here:
The other two values let you display either just the text or just the image. This might be useful if
your application wants to use an image at some times, and not at others, for example.(If you want
only an image, you can simply display it without using a label, as described in the previous
section.)You can also add an image to a label after it has been constructed by using the
setGraphic( ) method. It is shown here:
final void setGraphic(Node image)
Here, image specifies the image to add.
Using an Image with a Button
Button is JavaFX’s class for push buttons. The procedure for adding an image to a button is
similar to that used to add an image to a label. First obtain an ImageView of the image. Then
add it to the button. One way to add the image is to use this constructor:
Here, str specifies the text that is displayed within the button and image specifies the image. You
can specify the position of the image relative to the text by using setContentDisplay( )in the
same way as just described for Label. Here is an example that displays two buttons that contain
images. The first shows an hourglass. The second shows an analog clock. When a button is
pressed, the selected timepiece is reported. Notice that the text is displayed beneath the image.
// Use an image with a button.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.image.*;
public class ButtonImageDemo extends Application {
Label response;
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Use Images with Buttons");
// Use a FlowPane for the root node. In this case,
// vertical and horizontal gaps of 10.
FlowPane rootNode = new FlowPane(10, 10);
// Center the controls in the scene.
rootNode.setAlignment(Pos.CENTER);
// Create a scene.
Scene myScene = new Scene(rootNode, 250, 450);
// Set the scene on the stage.
myStage.setScene(myScene);
// Create a label.
response = new Label("Push a Button");
// Create two image-based buttons.
Button btnHourglass = new Button("Hourglass",new ImageView("hourglass.png"));
Button btnAnalogClock = new Button("Analog Clock", new ImageView("analog.png"));
// Position the text under the image.
btnHourglass.setContentDisplay(ContentDisplay.TOP);
btnAnalogClock.setContentDisplay(ContentDisplay.TOP);
// Handle the action events for the hourglass button.
btnHourglass.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
response.setText("Hourglass Pressed");
}
});
// Handle the action events for the analog clock button.
btnAnalogClock.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
response.setText("Analog Clock Pressed");
}
});
// Add the label and buttons to the scene graph.
rootNode.getChildren().addAll(btnHourglass, btnAnalogClock, response);
// Show the stage and its scene.
myStage.show();
}
}
The output produced by this program is shown here:
If you want a button that contains only the image, pass a null string for the text whenconstructing
the button and then call setContentDisplay( ), passing in the
parameterContentDisplay.GRAPHIC_ONLY. For example, if you make these modifications to
theprevious program, the output will look like this:
ToggleButton
A useful variation on the push button is called the toggle button. A toggle button looks just like a
push button, but it acts differently because it has two states: pushed and released. That is, when
you press a toggle button, it stays pressed rather than popping back up as a regular push button
does. When you press the toggle button a second time, it releases(pops up). Therefore, each time
a toggle button is pushed, it toggles between these two states. In JavaFX, a toggle button is
encapsulated in the ToggleButton class. Like Button,ToggleButton is also derived from
ButtonBase. It implements the Toggle interface, which defines functionality common to all
types of two-state buttons.
ToggleButton defines three constructors. This is the one we will use:
ToggleButton(String str)
Here, str is the text displayed in the button. Another constructor allows you to include an image.
Like other buttons, a ToggleButton generates an action event when it is pressed. Because
ToggleButton defines a two-state control, it is commonly used to let the user select an option.
When the button is pressed, the option is selected. When the button is released, the option is
deselected. For this reason, a program usually needs to determine the toggle button’s state. To do
this, use the isSelected( ) method, shown here:
RadioButton
Another type of button provided by JavaFX is the radio button. Radio buttons are a group of
mutually exclusive buttons, in which only one button can be selected at any one time. They are
supported by the RadioButton class, which extends both ButtonBase and ToggleButton. It also
implements the Toggle interface. Thus, a radio button is a specialized form of a togglebutton. To
create a radio button, we will use the following constructor:
RadioButton(String str)
Here, str is the label for the button. Like other buttons, when a RadioButton is used, an action
event is generated.For their mutually exclusive nature to be activated, radio buttons must be
configured into a group. Only one of the buttons in the group can be selected at any time. For
example,if a user presses a radio button that is in a group, any previously selected button in
thatgroup is automatically deselected. A button group is created by the ToggleGroup
class,which is packaged in javafx.scene.control. ToggleGroup provides only a default
constructor.Radio buttons are added to the toggle group by calling the setToggleGroup( )
method,defined by ToggleButton, on the button. It is shown here:
Here, tg is a reference to the toggle button group to which the button is added. After all radio
buttons have been added to the same group, their mutually exclusive behavior willbe enabled.In
general, when radio buttons are used in a group, one of the buttons is selected when the group is
first displayed in the GUI. Here are two ways to do this.
First, you can call setSelected( ) on the button that you want to select. It is defined
byToggleButton (which is a superclass of RadioButton). It is shown here:
In the program, pay special attention to how the radiobuttons and the toggle group are created.
First, the buttonsare created using this sequence:
RadioButton rbTrain = new RadioButton("Train");
RadioButton rbCar = new RadioButton("Car");
RadioButton rbPlane = new RadioButton("Airplane");
Next, a ToggleGroup is constructed:
ToggleGroup tg = new ToggleGroup();
Finally, each radio button is added to the toggle group:
rbTrain.setToggleGroup(tg);
rbCar.setToggleGroup(tg);
rbPlane.setToggleGroup(tg);
As explained, radio buttons must be part of a toggle group in order for their mutuallyexclusive
behavior to be activated.
After the event handlers for each radio button have been defined, the rbTrain button isselected
by calling fire( ) on it. This causes that button to be selected and an action event tobe generated
for it. This causes the button to be initialized with the default selection.
It returns a reference to the Toggle that is selected. In this case, the return value is cast
toRadioButton because this is the type of button in the group.The second thing to notice is the
use of a visual separator, which is created by thissequence:
The Separator class creates a line, which can be either vertical or horizontal. By default,
itcreates a horizontal line. (A second constructor lets you choose a vertical separator.)
Separatorhelps visually organize the layout of controls. It is packaged in javafx.scene.control.
Next,the width of the separator line is set by calling setPrefWidth( ), passing in the width.
CheckBox
The CheckBox class encapsulates the functionality of a check box. Its immediate superclassis
ButtonBase. Although you are no doubt familiar with check boxes because they are widelyused
controls, the JavaFX check box is a bit more sophisticated than you may at first think.This is
because CheckBox supports three states. The first two are checked or unchecked, asyou would
expect, and this is the default behavior. The third state is indeterminate (also calledundefined). It
is typically used to indicate that the state of the check box has not been set orthat it is not
relevant to a specific situation. If you need the indeterminate state, you willneed to explicitly
enable it.
CheckBox defines two constructors. The first is the default constructor. The second letsyou
specify a string that identifies the box. It is shown here:
CheckBox(String str)
It creates a check box that has the text specified by str as a label. As with other buttons,
aCheckBox generates an action event when it is selected.Here is a program that demonstrates
check boxes. It displays check boxes that let theuser select various deployment options, which
are Web, Desktop, and Mobile. Each time acheck box state changes, an action event is generated
and handled by displaying the newstate (selected or cleared) and by displaying a list of all
selected boxes.
// Demonstrate Check Boxes.
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.geometry.*;
public class CheckboxDemo extends Application {
CheckBox cbWeb;
CheckBox cbDesktop;
CheckBox cbMobile;
Label response;
Label allTargets;
String targets = "";
public static void main(String[] args) {
// Start the JavaFX application by calling launch().
launch(args);
}
// Override the start() method.
public void start(Stage myStage) {
// Give the stage a title.
myStage.setTitle("Demonstrate Checkboxes");
// Use a FlowPane for the root node. In this case,
// vertical and horizontal gaps of 10.
FlowPane rootNode = new FlowPane(10, 10);
// Center the controls in the scene.
rootNode.setAlignment(Pos.CENTER);
// Create a scene.
Scene myScene = new Scene(rootNode, 230, 140);
// Set the scene on the stage.
myStage.setScene(myScene);
Label heading = new Label("Select Deployment Options");
// Create a label that will report the state of the
// selected check box.
response = new Label("No Deployment Selected");
// Create a label that will report all targets selected.
allTargets = new Label("Target List: <none>");
// Create the check boxes.
cbWeb = new CheckBox("Web");
cbDesktop = new CheckBox("Desktop");
cbMobile = new CheckBox("Mobile");
// Handle action events for the check boxes.
cbWeb.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
if(cbWeb.isSelected())
response.setText("Web deployment selected.");
else
response.setText("Web deployment cleared.");
showAll();
}
});
cbDesktop.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
if(cbDesktop.isSelected())
response.setText("Desktop deployment selected.");
else
response.setText("Desktop deployment cleared.");
showAll();
}
});
cbMobile.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
if(cbMobile.isSelected())
response.setText("Mobile deployment selected.");
else
response.setText("Mobile deployment cleared.");
showAll();
}
});
// Use a separator to better organize the layout.
Separator separator = new Separator();
separator.setPrefWidth(200);
// Add controls to the scene graph.
rootNode.getChildren().addAll(heading, separator, cbWeb, cbDesktop,
cbMobile, response, allTargets);
// Show the stage and its scene.
myStage.show();
}
// Update and show the targets list.
void showAll() {
targets = "";
if(cbWeb.isSelected()) targets = "Web ";
if(cbDesktop.isSelected()) targets += "Desktop ";
if(cbMobile.isSelected()) targets += "Mobile";
if(targets.equals("")) targets = "<none>";
allTargets.setText("Target List: " + targets);
}
}
Sample output is shown here:
The operation of this program is straightforward. Each time a check box is changed,an action
command is generated. To determine if the box is checked or unchecked, theisSelected( )
method is called.As mentioned, by default, CheckBox implements two states: checked and
unchecked.If you want to add the indeterminate state, it must be explicitly enabled. To do this,
callsetAllowIndeterminate( ), shown here:
In this case, if enable is true, the indeterminate state is enabled. Otherwise, it is disabled.When
the indeterminate state is enabled, the user can select between checked, unchecked,and
indeterminate.You can determine if a check box is in the indeterminate state by
callingisIndeterminate( ), shown here:
ListView
Another commonly used control is the list view, which in JavaFX is encapsulated by
ListView.List views are controls that display a list of entries from which you can select one or
more.Because of their ability to make efficient use of limited screen space, list views are
popularalternatives to other types of selection controls.
ListView is a generic class that is declared like this:
class ListView<T>
Here, T specifies the type of entries stored in the list view. Often, these are entries of typeString,
but other types are also allowed.
ListView defines two constructors. The first is the default constructor, which creates anempty
ListView. The second lets you specify the list of entries in the list. It is shown here:
ListView(ObservableList<T>list)
Here, list specifies a list of the items that will be displayed. It is an object of
typeObservableList, which defines a list of observable objects. It inherits java.util.List. Thus,it
supports the standard collection methods. ObservableList is packaged in
javafx.collections.Probably the easiest way to create an ObservableList for use in a ListView is
to use thefactory method observableArrayList( ), which is a static method defined by the
FXCollectionsclass (which is also packaged in javafx.collections). The version we will use is
shown here:
In this case, E specifies the type of elements, which are passed via elements.By default, a
ListView allows only one item in the list to be selected at any one time.
However, you can allow multiple selections by changing the selection mode. For now, wewill
use the default, single-selection model.Although ListView provides a default size, sometimes
you will want to set the preferredheight and/or width to best match your needs. One way to do
this is to call thesetPrefHeight( ) and setPrefWidth( ) methods, shown here:
Alternatively, you can use a single call to set both dimensions at the same time by use
ofsetPrefSize( ), shown here:
void setPrefSize(double width, double height)
There are two basic ways in which you can use a ListView. First, you can ignore
eventsgenerated by the list and simply obtain the selection in the list when your program needs
it.Second, you can monitor the list for changes by registering a change listener. This lets
yourespond each time the user changes a selection in the list. This is the approach used here.To
listen for change events, you must first obtain the selection model used by theListView. This is
done by calling getSelectionModel( ) on the list. It is shown here:
In the program, pay special attention to how the ListView is constructed. First,
anObservableList is created by this line:
ObservableList<String> transportTypes =
FXCollections.observableArrayList( "Train", "Car", "Airplane" );
It uses the observableArrayList( ) method to create a list of strings. Then, the ObservableList
is used to initialize a ListView, as shown here:
ListView<String> lvTransport = new ListView<String>(transportTypes);
The program then sets the preferred width and height of the control.Now, notice how the
selection model is obtained for lvTransport:
MultipleSelectionModel<String> lvSelModel =lvTransport.getSelectionModel();
As explained, ListView uses MultipleSelectionModel, even when only a single selection
isallowed. The selectedItemProperty( ) method is then called on the model and a changelistener
is registered to the returned item.
ListView Scrollbars
One very useful feature of ListView is that when the number of items in the list exceeds
thenumber that can be displayed within its dimensions, scrollbars are automatically added.
Forexample, if you change the declaration of transportTypes so that it includes "Bicycle"
and"Walking", as shown here:
ObservableList<String> transportTypes =
FXCollections.observableArrayList( "Train", "Car", "Airplane",
"Bicycle", "Walking" );
the lvTransport control now looks like the one shown here:
ObservableList<T> getSelectedItems( )
lvTransport.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
It enables multiple-selection mode for lvTransport. Finally, replace the change eventhandler
with the one shown here:
lvSelModel.selectedItemProperty().addListener(
new ChangeListener<String>() {
public void changed(ObservableValue<? extends String> changed,String oldVal, String newVal)
{
String selItems = "";
ObservableList<String> selected =
lvTransport.getSelectionModel().getSelectedItems();
// Display the selections.
for(int i=0; i < selected.size(); i++)
selItems += "\n " + selected.get(i);
response.setText("All transports selected: " + selItems);
}
});
After making these changes, the program will display all selected forms of transports, as
the following output shows: