© Stephen Chin, Johan Vos and James Weaver 2019
S. Chin et al.The Definitive Guide to Modern Java Clients with JavaFXhttps://doi.org/10.1007/978-1-4842-4926-0_4

4. JavaFX Controls Deep Dive

Stephen Chin1 , Johan Vos2 and James Weaver3
(1)
Belmont, CA, USA
(2)
Leuven, Belgium
(3)
Marion, IN, USA
 

Written by Jonathan Giles

When JavaFX first shipped back in 2007, it did not have any user interface controls available for users to put into their user interfaces. Developers had to settle for either creating their own rudimentary UI controls or importing UI components from the Swing toolkit that also ships with Java. Starting with JavaFX 1.2, this situation started to improve, with the introduction of a number of critically important UI controls, such as Button, ProgressBar, and ListView. In subsequent releases, JavaFX began to attain a complete and well-regarded set of UI controls, providing the ability to construct user interfaces for applications that one would find inside a corporate environment.

This chapter will introduce most of the UI controls that exist in the core JavaFX 11 release. Due to the limited number of pages available in this chapter, the code samples are kept intentionally brief. Rest assured that the code repository for this book includes a comprehensive demo application that covers all JavaFX UI controls, with code that can be copy/pasted into your own applications.

The UI Controls Module

Beginning with JavaFX 9, almost all UI controls are now encapsulated within the javafx.controls module.1 This module is split into four exported packages, as shown in the following:
  • javafx.scene.chart: This package houses the charting components for building charts such as line, bar, area, pie, bubble, and scatter charts. These will not be covered as part of this chapter.

  • javafx.scene.control: This package houses the APIs for almost all user interface controls in JavaFX. This is the primary package we will cover in this chapter.

  • javafx.scene.control.cell: This package houses the APIs for a large number of pre-built “cell factories,” which we will cover in more depth when we get to the “Advanced Controls” section later in this chapter.

  • javafx.scene.control.skin: This package houses the “skins,” or the visual components, for each UI control. We won’t be covering this package in this chapter as it is beyond the scope of this book.

What Is a UI Control?

A valid question to ask in the context of JavaFX is: What exactly is a UI control? A simple definition might be that it is a visual component that forms a small part of a user interface and which is often interactive (but not always). In the strictest sense, a UI control extends from the Control class, but a more relaxed definition would allow for any component that extends from Node to be considered a UI control. For the sake of this chapter, most of the UI controls being discussed extend from the Control class.

This inevitably leads to the next question: What is the Control class? Control is a class that extends from Parent, which itself extends from Node. In the commonly referenced MVC2 nomenclature, a Control can be thought of as the model. In any user interface built using JavaFX UI controls, a developer should only ever interact with the Control classes, as these are where all API exists for manipulating and reading the state of the control.

Because the Control class extends from Node, it is afforded all abilities that Node has. This means a UI control can have effects, rotation, scaling, and numerous other properties modified, as necessary. Event handlers for mouse, swipe, drag, touch, key input, and more can also be added in the standard fashion. Adding UI controls to a scene graph is also handled in the same fashion as any Node – by adding it to a layout container with the relevant sizing information, layout constraints, and so on.

As of JavaFX 9, and as noted previously, the visuals of all UI controls, known as the skins, have also become public API in the javafx.scene.control.skin package. The reason the skins are public API is to enable developers to subclass them and to therefore override the default visuals of a UI control.

JavaFX Basic Controls

There exists a subset of UI controls in JavaFX that can be considered critical to almost all user interfaces, but are basic in the sense that they are simplistic to use, both from an end user perspective and a UI developer perspective. This section will introduce each of these basic controls in turn. As of JavaFX 11, the basic UI controls can be broken down into three subgroups:
  1. 1.

    "Labeled" controls: Button, CheckBox, Hyperlink, Label, RadioButton, and ToggleButton

     
  2. 2.

    "Text input" controls: TextField, TextArea, and PasswordField

     
  3. 3.

    "Other" simple controls: ProgressBar, ProgressIndicator, and Slider

     

Labeled Controls

Most controls that display read-only text extend from a common abstract superclass known as Labeled. This class specifies a common set of properties for handling alignment, fonts, graphic (and graphic positioning), wrapping, and so on, as well as, of course, for the text to be displayed itself. Because Labeled is abstract, it is not commonly used directly, but many of the actual UI controls extend from it, including Button, CheckBox, Hyperlink, Label, RadioButton, and ToggleButton. In addition to these basic controls, other more advanced controls (to be covered later in this chapter) also benefit from Labeled, including MenuButton, TitledPane, and ToggleButton.

The most important properties3 for Labeled are shown in Table 4-1.
Table 4-1

Properties of the Labeled class

Property

Type

Description

alignment

ObjectProperty<Pos>

Specifies how the text and graphic should be aligned.

contentDisplay

ObjectProperty<ContentDisplay>

Specifies the positioning of the graphic relative to the text.

font

ObjectProperty<Font>

The default font to use for text.

graphic

ObjectProperty<Node>

An optional icon for the Labeled.

textAlignment

ObjectProperty<TextAlignment>

Specifies the behavior for lines of text when text is multiline.

text

StringProperty

The text to display in the label.

wrapText

BooleanProperty

Specifies whether text should wrap into multiple lines when width is exceeded.

As noted, because the Labeled control is abstract, most developers do not use this class directly. Instead, they use one of the concrete subclasses that ship with JavaFX, which will be detailed in more depth now.

Label

Because Labeled is so comprehensive, the concrete Label class is extremely simple – it only adds one additional piece of API. This is called labelFor and is used to make keyboard navigation into UI controls using mnemonics simpler, as well as to improve text-to-speech output for people who are blind and partially sighted. It is best practice, when using a Label to describe another control (e.g., a Slider), to associate the Label instance with the Slider instance by saying label.labelFor(slider), for example. This means that when focus is given to the Slider control, the screen reading software for people with full or partial blindness can read out the text of the Label to help describe to users what the Slider is used for.

Button

The Button class enables users to execute some action by giving a visual affordance that can be clicked. When a user presses a button, it becomes “armed,” and when the mouse is released, it “fires,” before becoming “disarmed.” The most important properties for Button are shown in Table 4-2.
Table 4-2

Properties of the Button (and ButtonBase) class

Property

Type

Description

armed

ReadOnlyBooleanProperty4

Indicates whether the user is currently pressing the button.

cancelButton

BooleanProperty

If true, the button will handle Escape key presses.

defaultButton

BooleanProperty

If true, the button will handle Enter key presses.

onAction

ObjectProperty<EventHandler<ActionEvent>>

A callback that is executed when a Button is fired.

There is only one method of relevance, and that is the fire() method. This method can be called to programmatically fire the Button, resulting in the associated onAction event being called. The more common case is when a user directly clicks the button, and this has the same result – to fire the button and to cause whatever onAction event handler is installed to be called. The code to handle action events is shown in Listing 4-1.
var button = new Button("Click Me!");
button.setOnAction(event -> System.out.println("Button was clicked"));
Listing 4-1

Creating a JavaFX Button instance that handles clicks by printing to the console

CheckBox

Typically, a CheckBox enables a user to specify whether something is true or false. In JavaFX this is possible, but there is also the ability to show a third state: indeterminate. By default, the JavaFX CheckBox will toggle between checked and unchecked states only (and this is reflected in the selected property). To support toggling through the indeterminate state, developers must set the allowIndeterminate property to true. When this is enabled, the indeterminate property can be read, in conjunction with the selected property, to determine the state of the CheckBox.

Because CheckBox is a Labeled control, it supports displaying text and graphic adjacent to the checkbox. There are only a few additional properties of high importance, as shown in Table 4-3. A typical usage of CheckBox is shown in Listing 4-2.
Table 4-3

Properties of the CheckBox class

Property

Type

Description

allowIndeterminate

BooleanProperty

Determines if the CheckBox should toggle into the indeterminate state.

indeterminate

BooleanProperty

Specifies whether the CheckBox is currently indeterminate.

selected

BooleanProperty

Specifies whether the CheckBox is currently selected.

CheckBox cb = new CheckBox("Enable Power Plant");
cb.setIndeterminate(false);
cb.setOnAction(e -> log("Action event fired"));
cb.selectedProperty()
    .addListener(i -> log("Selected state change to " + cb.isSelected()));
Listing 4-2

Creating a CheckBox instance that is determinate (i.e., only toggles between selected and unselected)

Hyperlink

The Hyperlink control is essentially a Button control that is presented in the form of a hyperlink – text with an underline – much as one would expect to see on a web site. The API for Hyperlink therefore is equivalent to the Button class, with one small addition: a visited property to indicate whether the user has clicked on the link, as shown in Table 4-4. If visited is true, a developer may choose to style the Hyperlink differently. A typical usage of Hyperlink is shown in Listing 4-3.
Table 4-4

Properties of the Hyperlink class

Property

Type

Description

visited

BooleanProperty

Toggles to true when the hyperlink has been fired for the first time by the user.

var hyperlink = new Hyperlink("Click Me!");
hyperlink.setOnAction(event -> log("Hyperlink was clicked"));
Listing 4-3

Creating a Hyperlink instance and listening for it to be clicked

ToggleButton

ToggleButton is a Button (meaning that it can still fire action events), but generally this is not the best approach to take. This is because the intent of a ToggleButton is to toggle its selected property state between being selected and unselected, once per click. When a ToggleButton is selected, its visual appearance is different, appearing to be “pushed in.” ToggleButton instances may be added to a ToggleGroup to control selection.

What is a ToggleGroup?

ToggleGroup is a class that simply contains a list of Toggle instances for which it manages their selected state. ToggleGroup ensures that at most only one Toggle can be selected at a time.

Toggle is an interface with two properties – selected and toggleGroup. Classes that implement this interface include ToggleButton, RadioButton, and RadioMenuItem.

How do I use ToggleButton and ToggleGroup?

What this all boils down to then is that a ToggleButton may be associated with a ToggleGroup by instantiating a ToggleGroup instance, and multiple ToggleButton instances, and setting the toggleGroup property on each ToggleButton to be the single ToggleGroup instance. This is shown in Listing 4-4.

In doing this, the ToggleButton instances in this group have an additional constraint placed on them: there can only be one ToggleButton selected at any one time. Should a user select a new ToggleButton, the previously selected ToggleButton will become unselected. When ToggleButtons are placed in a ToggleGroup, it is valid for there to be no selected ToggleButton instances (i.e., the selected ToggleButton can be unselected). The main properties of ToggleButton are shown in Table 4-5.
Table 4-5

Properties of the ToggleButton class

Property

Type

Description

selected

BooleanProperty

Indicates whether the toggle is selected.

toggleGroup

ObjectProperty<ToggleGroup>

The ToggleGroup to which this ToggleButton belongs.

// create a few toggle buttons
ToggleButton tb1 = new ToggleButton("Toggle button 1");
ToggleButton tb2 = new ToggleButton("Toggle button 2");
ToggleButton tb3 = new ToggleButton("Toggle button 3");
// create a toggle group and add all the toggle buttons to it
ToggleGroup group = new ToggleGroup();
group.getToggles().addAll(tb1, tb2, tb3);
// it is possible to add an onAction listener for each button
tb1.setOnAction(e -> log("ToggleButton 1 was clicked on!"));
// but it is better to add a listener to the toggle group  selectedToggle property
group.selectedToggleProperty()
    .addListener(i -> log("Selected toggle is " + group.getSelectedToggle()));
Listing 4-4

Creating three ToggleButtons and adding them to a single ToggleGroup and listening to selection changes

RadioButton

RadioButton is a ToggleButton, with a different styling applied and a slightly different behavior when placed in a ToggleGroup. Whereas ToggleButtons in a ToggleGroup can be all unselected, in the case of RadioButtons in a ToggleGroup, there is no way for a user to unselect all RadioButtons. This is because, visually, a RadioButton can only be clicked to enter the selected state. Subsequent clicks have no effect (certainly it does not result in unselection). Therefore, the only way to unselect a RadioButton is to select a different RadioButton in the same ToggleGroup.

Because the API for RadioButton is essentially equivalent to ToggleButton, please refer to Listing 4-4 for the ToggleButton code sample. The only difference is to replace ToggleButton instances with RadioButton instances.

Text Input Controls

The next set of controls to cover, after the simple Labeled controls, are the three controls primarily used for text input, namely, TextArea, TextField, and PasswordField. TextField is designed to receive single-line input from users, whereas TextArea is designed to receive multiline input. PasswordField extends from TextField, enabling users to enter sensitive information by masking user input. In all three cases, these controls do not accept rich text input (see the HTMLEditor control later in this chapter for one option for rich text input).

TextArea and TextField extend from an abstract class called TextInputControl, which offers a base set of functionality, as well as a number of properties and methods that are applicable to both classes (the most important of which are shown in Table 4-6). For example, TextInputControl enables caret positioning (caret is the blinking cursor that indicates where text input will appear), text selection and formatting, and of course editing.
Table 4-6

Properties of the TextInputControl class

Property

Type

Description

anchor

ReadOnlyIntegerProperty

The anchor of the text selection. The range between anchor and caret represents the text selection range.

caretPosition

ReadOnlyIntegerProperty

The current position of the caret within the text.

editable

BooleanProperty

Whether the user can edit the text in the control.

font

ObjectProperty<Font>

The font to use to render the text.

length

ReadOnlyIntegerProperty

The number of characters entered in the control.

promptText

StringProperty

Text to display when there is no user input.

selectedText

ReadOnlyStringProperty

The text that has been selected in the control, via mouse or keyboard or programmatically.

textFormatter

ObjectProperty<TextFormatter<?>>

See section “TextFormatter.”

text

StringProperty

The textual content of this control.

TextFormatter

Before we dive into the concrete controls, we will first take a quick diversion to cover the TextFormatter API mentioned in the preceding text. A TextFormatter has two distinct mechanisms that enable it to influence what is accepted and displayed within text input controls:
  1. 1.

    A filter that can intercept and modify user input. This helps to keep the text in the desired format. A default text supplier can be used to provide the initial text.

     
  2. 2.

    A value converter and value can be used to provide special format that represents a value of type V. If the control is editable and the text is changed by the user, the value is then updated to correspond to the text.

     

It’s possible to have a formatter with just filter or value converter. If value converter is not provided however, setting a value will result in an IllegalStateException, and the value is always null.

TextField, PasswordField, and TextArea

As already noted, the TextField control is used to receive a single line of unformatted text from a user. This is ideal for forms requesting user name, email address, and so on. The two key properties are text and onAction. The text property has already been discussed as it is inherited from TextInputControl, and onAction functions as expected given we have already covered Button and similar classes: when the Enter key is pressed, an ActionEvent is fired from the TextField, alerting the developer that the user has chosen to “submit” their input. Listing 4-5 demonstrates the standard way to use a TextField control.
TextField textField = new TextField();
textField.setPromptText("Enter name here");
// this is fired when the user hits the Enter key
textField.setOnAction(e -> log("Entered text is: " + textField.getText()));
// we can also observe input in real time
textField.textProperty()
    .addListener((o, oldValue, newValue) -> log("current text input is " + newValue));
Listing 4-5

Creating and using a TextField control

PasswordField functions in exactly the same way as TextField, except that it obscures the user input so that there is some degree of security from prying eyes behind the user. In addition, for security reasons, the PasswordField does not support cut and copy operations (paste is still valid however). There are no additional properties or API on PasswordField.

The TextArea control is designed for multiple lines of user input, but again only supports unformatted text. The TextArea control is best used in situations where a single line of input is not desirable. For example, if you wish to ask your user for feedback (which may span multiple sentences or paragraphs), a TextArea is the best option. Because TextArea is designed for multiple lines of input, there are a few useful properties worth becoming acquainted with, as shown in Table 4-7.
Table 4-7

Properties of the TextArea class

Property

Type

Description

prefColumnCount

IntegerProperty

Preferred number of text columns.

prefRowCount

IntegerProperty

Preferred number of text rows.

wrapText

BooleanProperty

Whether to wrap text or let the TextArea scroll horizontally when a line exceeds the width available.

Other Simple Controls

Beyond the Labeled controls and the text input controls, there are three other controls that can be considered “simple”: ProgressBar, ProgressIndicator, and Slider.

ProgressBar and ProgressIndicator

JavaFX offers two UI controls for displaying progress to users: ProgressBar and ProgressIndicator . They are very closely related in terms of API, as ProgressBar extends ProgressIndicator and adds no additional API. The most important properties for ProgressIndicator are shown in Table 4-8.

Both controls can be used to display progress or can be set into indeterminate state to indicate to the user that work is proceeding, but that the progress is unknown at this point in time.

To show progress, developers should set the progress property to a value between 0.0 and 1.0. This at first may appear counterintuitive – why use a range between 0.0 and 1.0, instead of a range of 0–100? The answer isn’t clear that this was a conscious design choice throughout the entire JavaFX UI toolkit whenever dealing with percentages. To make the progress controls switch to their indeterminate form, simply set the progress property value to -1. When this is done, the indeterminate property will change from false to true.

A simple usage example is shown in Listing 4-6.
Table 4-8

Properties of the ProgressIndicator class

Property

Type

Description

indeterminate

ReadOnlyBooleanProperty

A boolean flag indicating if the indeterminate progress animation is playing.

progress

DoubleProperty

The actual progress (between 0.0 and 1.0), or can be set to -1 for indeterminate.

ProgressBar p2 = new ProgressBar();
p2.setProgress(0.25F);
Listing 4-6

Creating a ProgressBar that will show 25% progress

Slider

The Slider control is used to enable users to specify a value within a certain min/max range. This is made possible by displaying to the user a “track” and a “thumb.” The thumb can be dragged by the user to change the value. It is therefore no surprise that the three most critical properties of a Slider control are its min, max, and value properties, shown in Table 4-9. A simple example of using a Slider is shown in Listing 4-7.
Table 4-9

Properties of the Slider class

Property

Type

Description

blockIncrement

DoubleProperty

How much the Slider moves if the track is clicked.

max

DoubleProperty

The maximum value represented by the Slider.

min

DoubleProperty

The minimum value represented by the Slider.

orientation

ObjectProperty<Orientation>

Whether the Slider is horizontal or vertical.

value

DoubleProperty

The current value represented by the Slider.

Slider slider = new Slider(0.0f, 1.0f, 0.5f);
slider.valueProperty()
    .addListener((o, oldValue, newValue) -> log("Slider value is " + newValue));
Listing 4-7

Creating a Slider that will have a range between 0.0 and 1.0

Container Controls

Now that we have worked through the simple UI controls, we can move on to some more exciting controls. This section will be covering “container” controls, or controls that are used to contain and display other user interface elements. These container controls provide some additional functionality, be it the ability to collapse their content, offer a tabbed interface to change views, or something else.

Accordion and TitledPane

TitledPane is a container that displays a title area and a content area and has the ability to expand and collapse the content area by clicking on the title area. This is useful for side panels and the like in a user interface in that it allows for information to be displayed but optionally collapsed by users, such that they only see what they need to see.

TitledPane extends from Labeled, so as we discussed earlier, there is a large array of properties to customize the display. One should note however that these Labeled properties are applied only to the title area of the TitledPane and not the content area. The primary properties of TitledPane are shown in Table 4-10.
Table 4-10

Properties of the TitledPane class

Property

Type

Description

animated

BooleanProperty

Whether the TitledPane animates as it expands and collapses.

collapsible

BooleanProperty

Whether the TitledPane can be collapsed by the user.

content

ObjectProperty<Node>

The Node to display in the content area of the TitledPane.

expanded

BooleanProperty

Whether the TitledPane is currently expanded or not.

text

StringProperty

The text to show in the header area of the TitledPane.

With TitledPane introduced, we can move on to Accordion, which is a control that is simply a container of zero or more TitledPanes. When an Accordion is displayed to the user, it only allows for one TitledPane to be expanded at any time. Expanding a different TitledPane will result in the currently expanded TitledPane being collapsed.

There is only one notable property – expandedPane – which is an ObjectProperty<TitledPane> that represents the currently expanded TitledPane, as noted in Table 4-11.
Table 4-11

Properties of the Accordion class

Property

Type

Description

expandedPane

ObjectProperty<TitledPane>

The currently expanded TitledPane in the Accordion.

To add TitledPanes to the Accordion, we use the getPanes() method to retrieve the ObservableList of TitledPanes, and we add the applicable TitledPanes into this list. The result of doing this will be that the TitledPanes will be displayed stacked vertically in the order that they appear within this list. A code example of this is shown in Listing 4-8.

TitledPane t1 = new TitledPane("TitledPane 1", new Button("Button 1"));
TitledPane t2 = new TitledPane("TitledPane 2", new Button("Button 2"));
TitledPane t3 = new TitledPane("TitledPane 3", new Button("Button 3"));
Accordion accordion = new Accordion();
accordion.getPanes().addAll(t1, t2, t3);
Listing 4-8

Creating three TitledPanes and adding them all to a single Accordion

ButtonBar

The ButtonBar control was added in the JavaFX 8u40 release, so it is relatively new and relatively unknown. ButtonBar can be thought of as being essentially a HBox for Button controls (although it works with any Node), with the added functionality of placing the provided Buttons in the correct order for the operating system on which the user interface is running. This is extremely useful for dialogs, for example, as Windows, Mac OS, and Linux all have different button orderings. There is a small number of useful properties, as shown in Table 4-12, and Listing 4-9 demonstrates how to create and populate a ButtonBar instance.
Table 4-12

Properties of the ButtonBar class

Property

Type

Description

buttonMinWidth

DoubleProperty

The minimum width of all buttons placed in the ButtonBar.

buttonOrder

StringProperty

The ordering of buttons in the ButtonBar.

// Create the ButtonBar instance
ButtonBar buttonBar = new ButtonBar();
// Create the buttons to go into the ButtonBar
Button yesButton = new Button("Yes");
ButtonBar.setButtonData(yesButton, ButtonData.YES);
Button noButton = new Button("No");
ButtonBar.setButtonData(noButton, ButtonData.NO);
// Add buttons to the ButtonBar
buttonBar.getButtons().addAll(yesButton, noButton);
Listing 4-9

Creating a ButtonBar with “Yes” and “No” buttons. Ordering will depend on the operating system that this code is executed on

ScrollPane

ScrollPane is a control that is crucial to almost every user interface – the ability to scroll horizontally and vertically when content extends beyond the bounds of the user interface. For example, imagine an image manipulation program such as Adobe Photoshop. In this user interface, you may zoom in to work on a tiny section of your drawing, and the horizontal and vertical scrollbars allow for you to move this section around to see adjoining sections.

Unlike some other UI toolkits, it is not necessary to wrap UI controls such as ListView, TableView, and the like with a ScrollPane, as they have built-in scrolling and handle it for the developer. Therefore, a ScrollPane is typically used by developers in instances where they are doing something relatively custom. An example is shown in Listing 4-10, and the properties of ScrollPane are shown in Table 4-13.
Table 4-13

Properties of the ScrollPane class

Property

Type

Description

content

ObjectProperty<Node>

The Node to be displayed.

fitToHeight

BooleanProperty

Will attempt to keep content resized to match height of viewport.

fitToWidth

BooleanProperty

Will attempt to keep content resized to match width of viewport.

hbarPolicy

ObjectProperty<ScrollBarPolicy>

Sets policy for when to show horizontal scrollbars.

hmax

DoubleProperty

The maximum allowed hvalue.

hmin

DoubleProperty

The minimum allowed hvalue.

hvalue

DoubleProperty

The current horizontal position of the ScrollPane.

vbarPolicy

ObjectProperty<ScrollBarPolicy>

Sets policy for when to show vertical scrollbars. This can be one of the enum constants in ScrollPane.ScrollBarPolicy: ALWAYS, AS_NEEDED, or NEVER

vmax

DoubleProperty

The maximum allowed vvalue.

vmin

DoubleProperty

The minimum allowed vvalue.

vvalue

DoubleProperty

The current vertical position of the ScrollPane.

// in this sample we create a linear gradient to make the scrolling visible
Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.RED)};
LinearGradient gradient = new LinearGradient(0, 0, 1500, 1000, false, CycleMethod.NO_CYCLE, stops);
// we place the linear gradient inside a big rectangle
Rectangle rect = new Rectangle(2000, 2000, gradient);
// which is placed inside a scrollpane that is quite small in comparison
ScrollPane scrollPane = new ScrollPane();
scrollPane.setPrefSize(120, 120);
scrollPane.setContent(rect);
// and we then listen (and log) when the user is scrolling vertically or horizontally
ChangeListener<? super Number> o = (obs, oldValue, newValue) -> {
    log("x / y values are: (" + scrollPane.getHvalue() + ", " + scrollPane.getVvalue() + ")");
};
scrollPane.hvalueProperty().addListener(o);
scrollPane.vvalueProperty().addListener(o);
Listing 4-10

Creating a ScrollPane instance

SplitPane

The SplitPane control accepts two or more children and draws them with a draggable divider between them. The user is then able to use this divider to give more space to one child, at the cost of taking space away from the other child. A SplitPane control is great for user interfaces where there is a main content area, and then an area on the left/right/bottom of the content area is used to display more context-specific information. In this scenario, the user may give additional space to the main content area or the context-specific area as necessary.

Historically, UI toolkits have only supported two children (i.e., a “left” and a “right” or a “top” and a “bottom”), but JavaFX removed this restriction and allows for an unlimited number of children, with one restriction: all children must have the same divider orientation. This means that a SplitPane only has one orientation property for all dividers (as shown in Table 4-14). There is however a way around this: simply embed SplitPane instances inside each other, such that the end result consists of dividers operating both horizontally and vertically in the desired order.

The SplitPane control observes the minimum and maximum size properties of its children. It will never reduce the size of a node below its minimum size and will never give it more size than its maximum size. For this reason, it is recommended that all nodes added to a SplitPane be wrapped inside a separate layout container, such that the layout container may handle the sizing of the node, without impacting the SplitPane’s ability to function.

A divider’s position ranges from 0 to 1.0 (inclusive). A position of 0 will place the divider at the left-/topmost edge of the SplitPane plus the minimum size of the node. A position of 1.0 will place the divider at the right-/bottommost edge of the SplitPane minus the minimum size of the node. A divider position of 0.5 will place the divider in the middle of the SplitPane. Setting the divider position greater than the node’s maximum size position will result in the divider being set at the node’s maximum size position. Setting the divider position less than the node’s minimum size position will result in the divider being set at the node’s minimum size position.

An example of creating a SplitPane is shown in Listing 4-11.
Table 4-14

Properties of the SplitPane class

Property

Type

Description

orientation

ObjectProperty<Orientation>

The orientation of the SplitPane.

final StackPane sp1 = new StackPane();
sp1.getChildren().add(new Button("Button One"));
final StackPane sp2 = new StackPane();
sp2.getChildren().add(new Button("Button Two"));
final StackPane sp3 = new StackPane();
sp3.getChildren().add(new Button("Button Three"));
SplitPane splitPane = new SplitPane();
splitPane.getItems().addAll(sp1, sp2, sp3);
splitPane.setDividerPositions(0.3f, 0.6f, 0.9f);
Listing 4-11

Creating a SplitPane instance with three children (and therefore two dividers)

TabPane

TabPane is a UI control that enables for tabbed interfaces to be displayed to users. For example, most readers will be familiar with the tabbed interface in their preferred web browser, so that they do not need to have multiple windows open – one for each page that they wish to have open.

Table 4-15 outlines the most important properties, but the two most useful properties are the side property and the tabClosingPolicy property. The side property is for specifying on which side of the TabPane the tabs will be displayed (by default this is Side.TOP, which means that the tabs will be at the top of the TabPane). The tabClosingPolicy is for specifying whether tabs can be closed by the user – there is a TabClosingPolicy enum, with three valid values:
  1. 1.

    UNAVAILABLE: Tabs cannot be closed by the user.

     
  2. 2.

    SELECTED_TAB: The currently selected tab will have a small close button in the tab area (shown as a small “x”). When a different tab is selected, the close button will disappear from the previously selected tab and instead be shown on the newly selected tab.

     
  3. 3.

    ALL_TABS: All tabs visible in the TabPane will have the small close button visible.

     
The JavaFX TabPane functions by exposing an ObservableList of Tab instances. Each Tab instance consists of a title property and a content property. When a Tab is added to the tabs list, it will be displayed in the user interface in the order in which it appears in the list. This is demonstrated in Listing 4-12.
Table 4-15

Properties of the TabPane class

Property

Type

Description

rotateGraphic

BooleanProperty

Whether graphics should rotate to display appropriately when tabs are placed on the left/right side.

selectionModel

ObjectProperty<SingleSelectionModel>

The selection model being used in the TabPane.5

side

ObjectProperty<Side>

The location at which tabs will be displayed.

tabClosingPolicy

ObjectProperty<TabClosingPolicy>

Described in the preceding text.

TabPane tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
for (int i = 0; i < 5; i++) {
    Tab tab = new Tab("Tab " + I, new Rectangle(200, 200, randomColor()));
    tabPane.getTabs().add(tab);
}
Listing 4-12

How to instantiate and use a TabPane

ToolBar

The ToolBar control is a very simple UI control. In its most common permutation, it can be thought of as a stylized HBox – that is, it presents whatever nodes are added to it horizontally, with a background gradient. The most common elements to add to a ToolBar are other UI controls such as Button, ToggleButton, and Separator, but there is no restriction on what can be placed within a ToolBar, as long as it is a Node.

The ToolBar control does offer one useful piece of functionality – it supports the concept of overflow, so that if there are more elements to be displayed than there is space to display them all, it removes the “overflowing” elements from the ToolBar and instead shows an overflow button that when clicked pops up a menu containing all overflowing elements of the ToolBar.

As noted in Table 4-16, ToolBar offers a vertical orientation, so that it may be placed on the left- or right-hand side of an application user interface, although this is not as common as being placed at the top of the user interface, typically just below the menu bar.

An example of creating a ToolBar is shown in Listing 4-13.
Table 4-16

Properties of the ToolBar class

Property

Type

Description

orientation

ObjectProperty<Orientation>

Whether the ToolBar should be horizontal or vertical.

ToolBar toolBar = new ToolBar();
toolBar.getItems().addAll(
    new Button("New"),
    new Button("Open"),
    new Button("Save"),
    new Separator(),
    new Button("Clean"),
    new Button("Compile"),
    new Button("Run"),
    new Separator(),
    new Button("Debug"),
    new Button("Profile")
);
Listing 4-13

Instantiating a ToolBar with multiple Button and Separator instances

Other Controls

HTMLEditor

The HTMLEditor control enables users to create rich text input that is internally formatted as HTML content. The control provides a number of UI controls to specify font size, color, and type, as well as alignment and so on.

One point to note about the HTMLEditor control is that, because it is dependent on the JavaFX WebView component for rendering the user input, this control does not ship in the javafx.controls module, but rather the javafx.web module, and within that it can be found in the javafx.scene.web package.

Despite the large amount of functionality offered to the end user, there is surprisingly little API available to the developer using HTMLEditor. There are no relevant properties, and the only relevant methods are the getter and setter methods for htmlText. These methods operate with a String, and it is expected that this String consists of valid HTML.6

Pagination

The simplest way to understand the Pagination control is to think of the Google search results page, with the “Gooooooooogle” text at the bottom and the numbers “1, 2, 3,…10.” Each of these numbers represents a page of results, and the user may click these to be taken to that page. Of importance is the fact that Google doesn’t predetermine the elements to place on any page other than the current page – the rest of the pages are only determined when they are requested.

This is precisely the functionality that Pagination offers in JavaFX. The key properties of the pagination class are shown in Table 4-17. Pagination is an abstract way of representing multiple pages, where only the currently showing page actually exists in the scene graph and all other pages are only generated upon request.

This is the first time in this chapter we have encountered a situation where we make use of the “callback” functionality used in a number of JavaFX UI controls. This is made use of in the pageFactory, to allow for on-demand generation of pages, as requested by the user. As this chapter progresses, we will encounter this approach a number of more times, so it is worth your time to ensure you understand what happens in Listing 4-14, especially where the pageFactory is set.
Table 4-17

Properties of the Pagination class

Property

Type

Description

currentPageIndex

IntegerProperty

The current page index being displayed.

pageCount

IntegerProperty

The total number of pages available to be displayed.

pageFactory

ObjectProperty<Callback<Integer,Node>>

Callback function that returns the page corresponding to the given index.

Pagination pagination = new Pagination(10, 0);
pagination.setPageFactory(pageIndex -> {
    VBox box = new VBox(5);
    for (int i = 0; i < 10; i++) {
        int linkNumber = pageIndex * 10 + i;
        Hyperlink link = new Hyperlink("Hyperlink #" + linkNumber);
        link.setOnAction(e -> log("Hyperlink #" + linkNumber + " clicked!"));
        box.getChildren().add(link);
    }
    return box;
});
Listing 4-14

Instantiating a Pagination control with ten pages

ScrollBar

The ScrollBar control is essentially a Slider control with a different style. It consists of a track over which a thumb can be moved, as well as buttons at either end of incrementing and decrementing the value (and thus moving the thumb). ScrollBar typically is not used in the same circumstances as Slider however – instead, it is typically used as part of a more complex UI control. For example, it is used in the ScrollPane control to support vertical and horizontal scrolling and is used in the ListView, TableView, TreeView, and TreeTableView controls discussed later.

Table 4-18 introduces the most important properties of the ScrollBar control.
Table 4-18

Properties of the ScrollBar class

Property

Type

Description

blockIncrement

DoubleProperty

How much the thumb moves if the track is clicked.

max

DoubleProperty

The maximum allowed value.

min

DoubleProperty

The minimum allowed value.

orientation

ObjectProperty<Orientation>

Whether the ScrollBar is horizontal or vertical.

unitIncrement

DoubleProperty

The amount to adjust the value when increment/decrement methods are called.

value

DoubleProperty

The current value of the ScrollBar.

Separator

The Separator control is perhaps the simplest control in the entire JavaFX UI toolkit. It is a control that lacks any interactivity and is simply designed to draw a line in the relevant section of the user interface. This is commonly used in the ToolBar control to group buttons into subgroups, for example. A similar approach is used in popup menus, but as noted previously, in the case of menus, it is required to use SeparatorMenuItem, rather than the standard Separator control discussed here.

By default, a Separator is oriented vertically such that is draws appropriately when placed in a horizontal ToolBar. This can be controlled by modifying the orientation property.

Spinner

The Spinner control was introduced to JavaFX relatively recently, in JavaFX 8u40. A Spinner can be thought of as a single-line TextField that may or may not be editable, with the addition of increment and decrement arrows to step through some set of values. Table 4-19 introduces the most critical properties of this control.

Because a Spinner can be used to step through various types of value (integer, float, double, or even a List of some type), the Spinner defers to a SpinnerValueFactory to handle the actual process of stepping through the range of values (and precisely how to step). JavaFX ships with a number of built-in SpinnerValueFactory types (for doubles, integers, and Lists), and it is possible to write custom SpinnerValueFactory instances for custom needs. The code example in Listing 4-15 demonstrates the integer value factory, and the double and list value factories function in the same fashion.
Table 4-19

Properties of the Spinner class

Property

Type

Description

editable

BooleanProperty

Whether text input is able to be typed by the user.

editor

ReadOnlyObjectProperty<TextField>

The editor control used by the Spinner.

promptText

StringProperty

The prompt text to display when there is no user input.

valueFactory

ObjectProperty<SpinnerValueFactory<T>>

As discussed in the preceding text.

value

ReadOnlyObjectProperty<T>

The value selected by the user.

Spinner<Integer> spinner = new Spinner<>();
spinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(5, 10));
spinner.valueProperty().addListener((o, oldValue, newValue) -> {
        log("value changed: '" + oldValue + "' -> '" + newValue + "'");
});
Listing 4-15

Creating a Spinner with an integer value factory

Tooltip

Tooltips are common UI elements which are typically used for showing additional information about a Node in the scene graph when the Node is hovered over by the mouse. Any Node can show a tooltip. In most cases, a Tooltip is created, and its text property is modified to show plain text to the user. However, a Tooltip is able to show within it an arbitrary scene graph of nodes – this is done by creating the scene graph and setting it inside the Tooltip graphic property.

You can use the approach shown in Listing 4-16 to set a Tooltip on any node.
Rectangle rect = new Rectangle(0, 0, 100, 100);
Tooltip t = new Tooltip("A Square");
Tooltip.install(rect, t);
Listing 4-16

Adding a Tooltip to any Node in the JavaFX scene graph

This tooltip will then participate with the typical tooltip semantics (i.e., appearing on hover and so on). Note that the Tooltip does not have to be uninstalled: it will be garbage collected when it is not referenced by any Node. It is possible to manually uninstall the tooltip, however, in much the same way.

A single tooltip can be installed on multiple target nodes or multiple controls.

Because most Tooltips are shown on UI controls, there is special API for all controls to make installing a Tooltip less verbose. The example in Listing 4-17 shows how to create a tooltip for a Button control.
Button button = new Button("Hover Over Me");
button.setTooltip(new Tooltip("Tooltip for Button"));
Listing 4-17

Adding a Tooltip to a UI control using convenience API

The key properties of the Tooltip class are shown in Table 4-20.
Table 4-20

Properties of the Tooltip class

Property

Type

Description

graphic

ObjectProperty<Node>

An icon or arbitrarily complex scene graph to display within the Tooltip popup.

text

StringProperty

The text to display within the Tooltip popup.

wrapText

BooleanProperty

Whether to wrap text when it exceeds the Tooltip width.

Popup Controls

JavaFX ships with a comprehensive set of controls that “pop up.” What this means is that, behind the scenes, they are placed in their own window that is separate from the main stage of the user interface, and as such they may appear outside of the window in situations whether they are taller or wider than the window itself. Developers do not need to concern themselves with the placement, sizing, or any detail related to this, but it is a useful detail to understand.

This section covers all UI controls in JavaFX that make use of this popup functionality. For many of them, they also make use of the same APIs for building menus, so we will firstly cover this common functionality, before talking about each control in turn.

Menu-Based Controls

Menu and MenuItem

Building a menu in JavaFX starts with the Menu and MenuItem classes. Both classes are notable for not actually extending Control, which is because they are designed to represent a menu structure, but the implementation is handled behind the scenes by JavaFX.

MenuItem acts essentially in the same fashion as a Button does. It supports a similar set of properties – text, graphic, and onAction. On top of this, it adds support for specifying keyboard accelerators (e.g., Ctrl-c). These are detailed in Table 4-21.

Because MenuItem simply extends from Object, on its own it is useless and cannot be added to a JavaFX user interface in the standard way. The way in which MenuItem is used is via the Menu class, which acts as a container for MenuItem instances. The Menu class has a getItems() method that works in the standard way of most other JavaFX APIs – developers add MenuItem instances into the getItems() method, and these items will then be displayed in the Menu whenever it is displayed to the user.

This leads to a few important questions:
  1. 1.

    How does JavaFX support nested menus (i.e., where a menu contains a submenu, which itself may contain more submenus)? This is handled simply by the fact that the Menu class itself extends from MenuItem. This means that whenever the API allows for a MenuItem, it implicitly supports Menu as well.

     
  2. 2.

    How does JavaFX support menu items with checkboxes or radio states? JavaFX ships with two subclasses – CheckMenuItem and RadioMenuItem – that support this. CheckMenuItem has a selected property that will toggle between true and false every time the user clicks the menu item. RadioMenuItem functions in a similar fashion to RadioButton – it should be associated with a ToggleGroup, and then JavaFX will enforce that at most one RadioMenuItem will be selected at any one time.

     
  3. 3.

    How do I separate menu items into groups? The common way this is handled in user interfaces is with separators. As mentioned earlier in this chapter, it is not possible to add a Separator directly into a Menu (as it does not extend from MenuItem), and so for this reason JavaFX ships with SeparatorMenuItem, which places a Separator into the Menu at the position the SeparatorMenuItem is placed inside the Menu items list.

     
  4. 4.
    What about custom menu elements? What if I want to show a Slider or a TextField in a Menu, for example? JavaFX supports this with the CustomMenuItem class. By using this class, developers can embed any arbitrary Node into the content property.
    Table 4-21

    Properties of the MenuItem class

    Property

    Type

    Description

    accelerator

    ObjectProperty<KeyCombination>

    A keyboard shortcut to access this menu item.

    disable

    BooleanProperty

    Whether the menu item should be user interactive.

    graphic

    ObjectProperty<Node>

    The graphic to show to the left of the menu item text.

    onAction

    ObjectProperty<EventHandler<ActionEvent>>

    The event handler to be called when the menu item is clicked.

    text

    StringProperty

    The text to display in the menu item.

    visible

    BooleanProperty

    Whether the menu item is visible in the menu or not.

     

MenuBar

So far we have covered the API required to specify a Menu, but not how it may be displayed to the user. By far, the most common way in which menus are added to a JavaFX user interface is through the MenuBar control. This class is traditionally placed at the top of the user interface (e.g., if a BorderLayout is used, it is typically set to be the top node), and it is constructed simply by creating an instance and then adding Menu instances to the list returned by calling getMenus().

On some operating systems (most notably Mac OS), it is typically quite uncommon to see a menu bar at the top of an application window, as Mac OS instead has a “system menu bar” which runs across the very top of the screen. This system menu bar is application context specific, in that it changes its content whenever the focused application changes. JavaFX supports this, and the MenuBar class has a useSystemMenuBar property that, if set to true, will remove the MenuBar from the application window and instead render the menu bar natively using the system menu bar. This will happen automatically on platforms that have a system menu bar (Mac OS), but will have no effect on platforms that do not (and in which case, the MenuBar will be positioned in the user interface however it is specified to appear by the application developer).

Listing 4-18 shows how to create a MenuBar with menus and menu items.
// Firstly we create our menu instances (and populate with menu items)
final Menu fileMenu = new Menu("File");
final Menu helpMenu = new Menu("Help");
// we are creating a Menu here to add as a submenu to the File menu
Menu newMenu = new Menu("Create New...");
newMenu.getItems().addAll(
        makeMenuItem("Project", console),
        makeMenuItem("JavaFX class", console),
        makeMenuItem("FXML file", console)
);
// add menu items to each menu
fileMenu.getItems().addAll(
        newMenu,
        new SeparatorMenuItem(),
        makeMenuItem("Exit", console)
);
helpMenu.getItems().addAll(makeMenuItem("Help", console));
// then we create the MenuBar instance and add in the menus
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(fileMenu, helpMenu);
Listing 4-18

Creating a MenuBar with two menus (the first of which has a submenu)

MenuButton and SplitMenuButton

Another way menus are commonly shown to users in JavaFX applications is through the MenuButton and SplitMenuButton classes. These classes are quite closely related, but they do function in slightly different fashions, so we will cover them separately in the following.

MenuButton is a button-like control that, whenever clicked, will show a menu consisting of all MenuItem elements added to the items list. Because the MenuButton class extends from ButtonBase (which itself extends from Labeled), there is a significant amount of API overlap with the JavaFX Button control. For example, MenuButton has the same onAction event, as well as text and graphic properties and so on. Note however that for a MenuButton, setting onAction has no effect, as the MenuButton does not fire onAction events, because this is used instead to show a popup. Table 4-22 outlines the properties introduced by MenuItem, and Listing 4-19 demonstrates how to use MenuButton in code.

SplitMenuButton extends the MenuButton class, but unlike MenuButton, the visuals of SplitMenuButton split the button itself into two pieces – an “action” area and the “menu open” area. When a user clicks the “action” area, the SplitMenuButton essentially acts as if it were a Button – executing whatever code is associated with the onAction property. When the user clicks the “menu open” area, the popup menu is shown, and the user may interact with the menu as per usual. Listing 4-20 demonstrates how to use SplitMenuButton in code.
Table 4-22

Properties of the MenuButton class

Property

Type

Description

popupSide

ObjectProperty<Side>

The side the context menu should be shown relative to the button.

MenuButton menuButton = new MenuButton("Choose a meal...");
menuButton.getItems().addAll(
        makeMenuItem("Burgers", console),
        makeMenuItem("Pizza", console),
        makeMenuItem("Hot Dog", console));
// because the MenuButton does not have an 'action' area,
// onAction does nothing
menuButton.setOnAction(e -> log("MenuButton onAction event"));
Listing 4-19

An example of using MenuButton

SplitMenuButton splitMenuButton = new SplitMenuButton();
// this is the text in the 'action' area
splitMenuButton.setText("Perform action!");
// these are the menu items to display in the popup menu
splitMenuButton.getItems().addAll(
        makeMenuItem("Burgers", console),
        makeMenuItem("Pizza", console),
        makeMenuItem("Hot Dog", console));
// splitMenuButton does fire an onAction event,
// when the 'action' area is pressed
splitMenuButton.setOnAction(e -> log("SplitMenuButton onAction event"));
Listing 4-20

An example of using SplitMenuButton

ContextMenu

ContextMenu is a popup control that contains within it MenuItems. This means that it is never added to a scene graph and instead is called either directly (via the two show() methods) or as a consequence of a user requesting a context menu to show using common mouse or keyboard operations (most commonly via right-clicking the mouse).

To make specifying and displaying context menus as easy as possible, the root Control class has a contextMenu property on it. When certain events happen (e.g., mouse right-click), the UI control is configured to check if a context menu is specified and, if so, to automatically display it. For example, the Tab class has one such contextMenu property, and whenever a user right-clicks a Tab inside of a TabPane, they are presented with the context menu as specified by the developer. Listing 4-21 shows a Button that has a ContextMenu set against it, which displays whenever the right-mouse button clicks it.
// create a standard JavaFX Button
Button button = new Button("Right-click Me!");
button.setOnAction(event -> log("Button was clicked"));
// create a ContextMenu
ContextMenu contextMenu = new ContextMenu();
contextMenu.getItems().addAll(
        makeMenuItem("Hello", console),
        makeMenuItem("World!", console),
        new SeparatorMenuItem(),
        makeMenuItem("Goodbye Again!", console)
);
Listing 4-21

Specifying a ContextMenu and adding it to a Button instance

In some cases, we want to show a ContextMenu on a class that does not extend from Control. In these cases, we can simply make use of one of the two show() methods on ContextMenu to display it when relevant events come up. There are two show() methods available:
  1. 1.

    show(Node anchor, double screenX, double screenY): This method will show the context menu at the specified screen coordinates.

     
  2. 2.

    show(Node anchor, Side side, double dx, double dy): This method will show the context menu at the specified side (top, right, bottom, or left) of the specified anchor node, with the amount of x- and y-axis shifting specified by dx and dy, respectively (also note that dx and dy can be negative if desired, but most commonly these values can simply be zero).

     
By using one of these two show methods in conjunction with appropriate event handlers (in particular onContextMenuRequested and onMousePressed API available on all JavaFX Node subclasses), we can achieve the desired outcome. Listing 4-22 shows how to display a ContextMenu on a JavaFX Rectangle class (which lacks the setContextMenu API from Control subclasses).
Rectangle rectangle = new Rectangle(50, 50, Color.RED);
rectangle.setOnContextMenuRequested(e -> {
    // show the contextMenu to the right of the rectangle with zero
    // offset in x and y directions
    contextMenu.show(rectangle, Side.RIGHT, 0, 0);
});
Listing 4-22

Adding a ContextMenu to a JavaFX Rectangle by manually showing it when requested

ChoiceBox

ChoiceBox is a JavaFX UI control that displays a popup menu when clicked, but it is not constructed through MenuItem instances. Instead, ChoiceBox is a generic class (e.g., ChoiceBox<T>), where the type of the class is also the type used for the items list. In other words, rather than have users specify menu items, a ChoiceBox is constructed with zero or more objects of type T, and these are what are displayed to the user in the popup menu.

Because the default toString() method of the T class may not be appropriate or overly human readable, ChoiceBox supports the notion of a converter property (which is of type StringConverter<T>). If a converter is specified, the ChoiceBox will take each element from the items list (which is of type T) and pass it through the converter, which should return a more human-readable string to be displayed in the popup menu.

When a user makes a selection in the ChoiceBox, the value property will be updated to reflect this new selection. When the value property changes, the ChoiceBox control also fires an onAction ActionEvent, so developers can choose whether to observe the value property or to add an onAction event handler.

Due to the UI design of ChoiceBox, this control is best suited to relatively small lists of elements. If the number of elements to display is large, it is normally recommended that developers instead use the ComboBox control.

The primary properties of ChoiceBox are outlined in Table 4-23, and their use is demonstrated in Listing 4-23.
Table 4-23

Properties of the ChoiceBox class

Property

Type

Description

converter

ObjectProperty<StringConverter<T>>

Allows for a way to convert the visual presentation of the items list.

items

ObjectProperty<ObservableList<T>>

The items to display in the ChoiceBox.

selectionModel

ObjectProperty<SingleSelectionModel<T>>

The selection model for the ChoiceBox.

showing

ReadOnlyBooleanProperty

Indicates if the ChoiceBox popup is visible.

value

ObjectProperty<T>

The current selection in the ChoiceBox.

ChoiceBox<String> choiceBox = new ChoiceBox<>();
choiceBox.getItems().addAll(
    "Choice 1",
    "Choice 2",
    "Choice 3",
    "Choice 4"
);
choiceBox.getSelectionModel()
        .selectedItemProperty()
        .addListener((o, oldValue, newValue) -> log(newValue));
Listing 4-23

Creating a ChoiceBox with four choices and a listener

ComboBox-Based Controls

Beyond the menu-based controls, there are a lot of other controls that pop up based on user interaction. This section covers a set of controls that all can be classified as controls that are “combo boxes.” In JavaFX, the controls in this set all extend from the ComboBoxBase class (whose properties are shown in Table 4-24) and are known as ComboBox, ColorPicker, and DatePicker.

Because all ComboBoxBase subclasses share a common parent, their API is uniform, and there are a number of notable similarities:
  • They appear as a button that, when clicked, will pop up some UI that allows the user to make a selection.

  • There is a value property that represents the current value selected by the user.

  • They typically can be editable or not, by setting the editable property appropriately. When the control is editable, it displays with a TextField and a button on the side – the TextField allows for user input, and the button will show the popup. When the control is not editable, the entire control will appear as a button.

  • There are show() and hide() methods to programmatically cause the popup to show itself or, if it is already showing, to hide.
    Table 4-24

    Properties of the ComboBoxBase class

    Property

    Type

    Description

    editable

    BooleanProperty

    Whether the control shows a text input area to receive user input.

    onAction

    ObjectProperty<EventHandler<ActionEvent>>

    The event handler when the user sets a new value.

    promptText

    StringProperty

    The prompt text to display – whether it displays is dependent on the subclass.

    value

    ObjectProperty<T>

    The latest selection (or input if editable) by the user.

ComboBox

ComboBox is conceptually quite similar to the ChoiceBox control, but is more fully featured and more performant when large lists of elements are needing to be displayed. The properties added in ComboBox are shown in Table 4-25 and demonstrated in code in Listing 4-24.
Table 4-25

Properties of the ComboBox class

Property

Type

Description

cellFactory

ObjectProperty<Callback<ListView<T>,ListCell<T>>>

Used to customize rendering of items.

converter

ObjectProperty<StringConverter<T>>

Converts user-typed input (when editable) to an object of type T to set as value.

items

ObjectProperty<ObservableList<T>>

The elements to show in popup.

placeholder

ObjectProperty<Node>

What to show when the ComboBox has no items.

selectionModel

ObjectProperty<SingleSelectionModel<T>>

Selection model of ComboBox.

ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll(
        "Apple",
        "Carrot",
        "Orange",
        "Banana",
        "Mango",
        "Strawberry"
);
comboBox.getSelectionModel()
        .selectedItemProperty()
        .addListener((o, oldValue, newValue) -> log(newValue));
Listing 4-24

Creating a ComboBox with multiple choices and a listener

ColorPicker

The ColorPicker control is a specialized form of ComboBox, designed specifically to allow users to select a color value.7 The ColorPicker control does not add any additional functionality on top of ComboBoxBase, but of course the user interface is vastly different. Using a ColorPicker is very similar to the other controls, as shown in Listing 4-25.

The ColorPicker control provides a color palette with a predefined set of colors. If the user does not want to choose from the predefined set, they can create a custom color by interacting with a custom color dialog. This dialog provides RGB, HSB, and web modes of interaction, to create new colors. It also lets the opacity of the color to be modified.

Once a new color is defined, users can choose whether they want to save it or just use it. If the new color is saved, this color will then appear in the custom colors area on the color palette.
final ColorPicker colorPicker = new ColorPicker();
colorPicker.setOnAction(e -> {
    Color c = colorPicker.getValue();
    System.out.println("New Color RGB = "+c.getRed()+" "+c.getGreen()+" "+c.getBlue());
});
Listing 4-25

Creating a ColorPicker and listening for selection changes

DatePicker

Much as ColorPicker is a specialization of ComboBoxBase for selecting colors, the DatePicker is a specialization of ComboBoxBase for selecting dates – in this case a java.time.LocalDate value. The properties introduced in DatePicker are shown in Table 4-26, and its use in code is demonstrated in Listing 4-26.
Table 4-26

Properties of the DatePicker class

Property

Type

Description

chronology

ObjectProperty<Chronology>

Which calendar system to use.

converter

ObjectProperty<StringConverter<LocalDate>>

Converts text input into a LocalDate and vice versa.

dayCellFactory

ObjectProperty<Callback<DatePicker,DateCell>>

Cell factory to customize individual day cells in popup.

showWeekNumbers

BooleanProperty

Whether the popup should show week numbers.

final DatePicker datePicker = new DatePicker();
datePicker.setOnAction(e -> {
    LocalDate date = datePicker.getValue();
    System.err.println("Selected date: " + date);
});
Listing 4-26

Creating a DatePicker and listening for selection changes

JavaFX Dialogs

JavaFX, since the 8u40 release, has shipped with a comprehensive set of dialog APIs to alert, query, and inform users. There exists API for simply popping open an informational alert all the way through creating custom dialogs. At the simplest end of the spectrum, developers should use the Alert class to show pre-built dialogs. Developers who want to prompt a user for text input or to make a choice from a list of options would be better served by using TextInputDialog and ChoiceDialog, respectively. Completely custom dialogs can be created using the Dialog and DialogPane classes.

There are two terms when discussing dialogs that developers should become familiar with. These are “modal” and “blocking” and are often used interchangeably when there is a distinct difference between them. Despite this, the two terms are quite easily defined:
  • A modal dialog is one that appears atop another window and prevents the user from clicking that window until the dialog is dismissed.

  • A blocking dialog is one that causes code execution to stop at the very line that caused the dialog to appear. This means that, once the dialog is dismissed, execution continues from that line of code. This can be thought of as a synchronous dialog. Blocking dialogs are simpler to work with, as developers can retrieve a return value from the dialog and continue execution without needing to rely on listeners and callbacks.

In JavaFX, by default all dialogs are modal, but it is possible to be non-modal by using the initModality(Modality) method on Dialog. For blocking, this is up to the developer – they may choose to call showAndWait() for blocking and show() for non-blocking dialogs.

Alert

The Alert class is the simplest option for developers who simply want to display a dialog to the user. There are a number of pre-built options with varying icons and default buttons. Creating an Alert is simply a matter of calling the constructor with the desired AlertType specified. AlertType is used to configure which buttons and which graphic are shown by default. Here is a quick summary of the options:
  • Confirmation: Best used to confirm that a user is sure before performing some action. Shows with a blue question mark image and “Cancel” and “OK” buttons.

  • Error: Best used to inform the user that something has gone wrong. Shows a red “X” image and a single “OK” button.

  • Information: Best used to inform the user of some useful information. Shows a blue “I” image (to represent “information”) and a single “OK” button.

  • None: This will result in no image and no buttons being set. This should rarely be used unless a custom implementation is about to be provided.

  • Warning: Best used to warn user of some fact or pending problem. Shows a yellow exclamation mark image and a single “OK” button.

In most cases, developers should simply choose the appropriate alert type from the options outlined in the preceding text and then provide the text that they wish to display to the user. Once the alert is created, it can be shown as outlined in Listing 4-27.
alert.showAndWait()
      .filter(response -> response == ButtonType.OK)
      .ifPresent(response -> formatSystem());
Listing 4-27

Creating an alert, waiting to see if the user selects the OK button, and, if so, performing an action

ChoiceDialog

ChoiceDialog is a dialog that shows a list of choices to the user, from which they can pick one item at most. In other words, this dialog will use a control such as a ChoiceBox or ComboBox (it is left as an implementation detail; a developer cannot specify their preference) to enable a user to make a selection. This selection will subsequently be returned to the developer to act on as appropriate, as shown in Listing 4-28.

ChoiceDialog<String> dialog = new ChoiceDialog<>("Cat", "Dog", "Cat", "Mouse");
dialog.showAndWait()
    .ifPresent(result -> log("Result is " + result));
Listing 4-28

Creating a ChoiceDialog with default choice of “Cat” and three choices. Dialog is modal and blocking, and if the user clicks the “OK” button, output is printed to console

The key property of the ChoiceDialog class is shown in Table 4-27.
Table 4-27

Properties of the ChoiceDialog class

Property

Type

Description

selectedItem

ReadOnlyObjectProperty<T>

The item that was selected by the user in the dialog.

TextInputDialog

TextInputDialog is similar to ChoiceDialog, except rather than allow a user to make a selection from a popup list, it instead enables a user to provide a single line of text input.

The key method for the TextInputDialog class is shown in Table 4-28 along with an example of how to create a TextInputDialog in Listing 4-29.
Table 4-28

Methods on the TextInputDialog class

Method

Type

Description

getEditor()

TextField

The TextField the user is shown in the dialog.

TextInputDialog. dialog = new TextInputDialog ("Please enter your name");
dialog.showAndWait()
    .ifPresent(result -> log("Result is " + result));
Listing 4-29

Creating a TextInputDialog. Dialog is modal and blocking, and if the user clicks the “OK” button, their input is printed to console

Dialog and DialogPane

Dialog is the most flexible dialog option in JavaFX, enabling complete configuration of a dialog. This allows for creation of dialogs such as username/password prompts, complex forms, and similar.

When a Dialog is instantiated, developers can specify a single generic type, R, which represents the type of the result property. This is important, because it is what we as developers will receive back when the dialog is dismissed.

This may lead to the obvious question: What should the R type be? The answer is that it depends on what exactly is being asked of the user. In the case of a password prompt, it might be an instance of a UsernamePassword class, for example.

Because the Dialog class does not know about the content it is displaying and therefore does not know how to convert the values entered by the user into an instance of the R type, it is necessary for the developer to set the resultConverter property. This is required whenever the R type is not Void or ButtonType. If this is not heeded, developers will find that they get ClassCastException thrown in their code, for failure to convert from ButtonType via the result converter.

Once a Dialog is instantiated, the next step is to configure it. The most important properties related to creating a custom dialog are shown in Table 4-29.
Table 4-29

Properties of the Dialog class

Property

Type

Description

contentText

StringProperty

The main text to display.

dialogPane

ObjectProperty<DialogPane>

The root node in the Dialog. Contains most other properties displayed here.

graphic

ObjectProperty<Node>

The graphic to display.

headerText

StringProperty

The text to display in the header area (above the contentText)

result

ObjectProperty<R>

The value returned once the dialog is dismissed.

resultConverter

ObjectProperty<Callback<ButtonType, R>>

API to convert the user button click into a result.

title

StringProperty

The dialog title to display to the user.

Internally, Dialog defers all layout handling of the viewable area to an embedded DialogPane instance. In fact, many of the properties simply forward on to this DialogPane.8 The DialogPane API provides a lot of additional functionality that is not exposed at the Dialog level, and developers can retrieve the currently installed DialogPane by calling getDialogPane() on the dialog instance.

Advanced Controls

The last set of controls that need to be covered are the “advanced” controls: ListView, TreeView, TableView, and TreeTableView. These controls contain the most API and also the most functionality. All four controls share a lot of common concepts, so this section will dive deeply into ListView (being the simplest of the four) and then discuss the other three controls at a higher level.

ListView

A ListView control is used to show a list of elements to a user. ListView is a generic class, so a ListView<T> is able to contain items of type T. As with most UI controls, to populate a ListView with items is incredibly easy – simply add elements of type T into the items list. The order in which elements appear in the items list will correspond to the order in which they are displayed inside the ListView.

Because ListView (as with all “advanced” controls in this section) is “virtualized,” it does not pay a performance penalty as the number of elements in the list increases. This is because, behind the scenes, a ListView only creates enough “cells” to contain the elements in the visible area of the ListView. For example, if the ListView is tall enough to fit 20 rows, the ListView may choose to create 22 cells and reuse these as the user scrolls through the list.

The ListView control has selectionModel and focusModel properties, enabling developers to control precisely what is selected and focused on in the user interface. These concepts will be covered in more depth later in the “Selection and Focus Models” section.

Typically, a ListView scrolls vertically, but by changing the orientation property, it may also be configured to scroll horizontally. This property and other important properties are shown in Table 4-30.
Table 4-30

Properties of the ListView class

Property

Type

Description

cellFactory

ObjectProperty<Callback<ListView<T>, ListCell<T>>>

See “Cells and Cell Factories” section.

editable

BooleanProperty

Whether the ListView supports editing cells.

focusModel

ObjectProperty<FocusModel<T>>

Refer to the “Selection and Focus Models” section.

items

ObjectProperty<ObservableList<T>>

The elements to show within the ListView.

orientation

ObjectProperty<Orientation>

Whether the ListView is vertical or horizontal.

placeholder

ObjectProperty<Node>

Text to display within the ListView if the items list is empty.

selectionModel

ObjectProperty<MultipleSelectionModel<T>>

Refer to the “Selection and Focus Models” section.

Cells and Cell Factories

In the advanced controls in this section (ListView, TreeView, TableView, and TreeTableView), a common aspect to their API is that they all support the concept of a “cell factory.” This is similar in concept to other factories we’ve already covered in this chapter, such as the page factory in the Pagination control.

The purpose of a cell factory is to create cells when requested by the UI control (e.g., ListView), which leads to the question: What exactly is a cell? In the JavaFX sense, it is a class that extends from the javafx.scene.control.Cell class. The Cell class is Labeled, meaning it exposes all of the API discussed at the beginning of this chapter, and a Cell is used to render a single “row” inside the ListView and other controls. Cells are also used for each individual “cell” inside TableView and TreeTableView.

Every cell is associated with a single data item (represented by the Cell item property). The cell is solely responsible for rendering this item. Depending on the cell type being used, the item may be represented as a String or by using some other UI control such as a CheckBox or Slider.

Cells are “stacked” inside a UI control like ListView, and as noted earlier, cell factories are used to generate these based on the needs of the control. But how then are cells updated when they are reused? There is a critical method called updateItem that is called by the UI control (e.g., ListView) whenever it is about to reuse a cell. When developers provide custom cell factories, it is this method that they must override, as it provides a hook in which a developer may, at the moment a cell is updated to contain new content, also update the presentation of the cell to better represent this new content.

Because by far the most common use case for cells is to show text to a user, this use case is specially optimized for within Cell. This is done by Cell extending from Labeled. This means that subclasses of Cell need only set the text property, rather than create a separate Label and set that within the Cell. However, for situations where something more than just plain text is called for, it is possible to place any Node in the Cell graphic property. Despite the term, a graphic can be any Node and will be fully interactive. For example, a ListCell might be configured with a Button as its graphic. Table 4-31 outlines some of the more critical properties of the Cell class.
Table 4-31

Properties of the Cell class

Property

Type

Description

editable

BooleanProperty

Whether the Cell instance can enter an editing state.

editing

ReadOnlyBooleanProperty

Whether the Cell is currently in an editing state

empty

ReadyOnlyBooleanProperty

Whether the Cell has any item.

item

ObjectProperty<T>

The object the Cell is currently representing.

selected

ReadOnlyBooleanProperty

Whether the Cell has been selected by the user.

There are other use cases too. Supporting editing inside a ListView is easy – when a cell enters its “editing” state, the same updateItem method is called, and inside this code a developer can choose to check the cell’s editing state, and if it is true the developer can choose to remove, for example, the text and to replace it with a TextField, allowing for custom input directly from the user.

When working with a UI control, such as ListView, developers do not use Cell directly, but rather a control-specific subclass (in the case of ListView, this would be ListCell). For the TableView and TreeTableView controls, there are in fact two cell types – TableRow/TreeTableRow and TableCell/TreeTableCell – but we will discuss this distinction later in the chapter. Despite this additional complexity, developers can console themselves in the knowledge that by and large they must simply understand the basics of the Cell class, and they will be able to create cell factories for all UI controls in much the same way. Listing 4-30 demonstrates how a developer can create a custom ListCell class.
public class ColorRectCell extends ListCell<String> {
    private final Rectangle rect = new Rectangle(100, 20);
    @Override public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || item == null) {
            setGraphic(null);
        } else {
            rect.setFill(Color.web(item));
            setGraphic(rect);
        }
    }
}
Listing 4-30

Creating a custom ListCell subclass and overriding updateItem

Cell Editing

When a custom Cell is to be editable, we enable support for it simply by extending the updateItem method shown in Listing 4-31 to also add checks to see if the cell is being used to represent the current editing index in the control.

For many of the common cases, there already exist a number of pre-built cell factories that support editing shipping with the core JavaFX APIs, contained within the javafx.scene.control.cell package, and discussed in more detail in the next section.

In situations where a pre-built editable cell does not exist, follow the code shown in Listing 4-31 to toggle between editing and non-editing states with ease whenever the user performs the relevant interactions to do this (normally this is double-clicking within the cell, but there are keyboard shortcuts too). Note that to enable editing, not only must the cell support editing but the ListView editable property must be set to true by the developer.
public class EditableListCell extends ListCell<String> {
    private final TextField textField;
    public EditableListCell() {
        textField = new TextField();
        textField.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
            if (e.getCode() == KeyCode.ENTER) {
                commitEdit(textField.getText());
            } else if (e.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            }
        });
        setGraphic(textField);
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }
    @Override public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        setText(item);
        setContentDisplay(isEditing() ?
            ContentDisplay.GRAPHIC_ONLY : ContentDisplay.TEXT_ONLY);
    }
    @Override public void startEdit() {
        super.startEdit();
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        textField.requestFocus();
    }
    @Override public void commitEdit(String s) {
        super.commitEdit(s);
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }
    @Override public void cancelEdit() {
        super.cancelEdit();
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }
}
Listing 4-31

Creating a ListCell that supports editing state

Pre-built Cell Factories
As mentioned previously, JavaFX ships with a number of pre-built cell factories that make customizing ListView and others a very easy task. If you need to accept text input, there exists a TextFieldListCell class (for ListView). If you want to show progress in a TableColumn, there exists a ProgressBarTableCell (for TableView). Making use of these pre-built cell factories is a no-brainer, as they have been developed and tested in the widest array of configurations and have been developed to avoid common performance concerns. Table 4-32 summarizes all available pre-built cell factories, and Listing 4-32 demonstrates how to use one of these cell factories.
Table 4-32

Pre-built cell factories

Type

Supported UI Controls

CheckBox

ListView, TableView, TreeView, TreeTableView

ChoiceBox

ListView, TableView, TreeView, TreeTableView

ComboBox

ListView, TableView, TreeView, TreeTableView

ProgressBar

TableView, TreeTableView

TextField

ListView, TableView, TreeView, TreeTableView

ListView<String> listView = new ListView<>();
listView.setEditable(true);
listView.setCellFactory(param -> new TextFieldListCell<>());
Listing 4-32

Using a pre-built cell factory to customize the editing style of a ListView

TreeView

The TreeView control is the go-to control inside the JavaFX UI toolkit for displaying tree-like data structures to users, for example, for representing a file system or a corporate hierarchy. The TreeView control displays a hierarchical structure by showing “disclosure” nodes (i.e., arrows) on tree branches, allowing for them to be expanded and collapsed. When a tree branch is expanded, its children are displayed beneath the branch but with a certain amount of indentation to make it clear that the children belong to their parent.

Unlike the JavaFX ListView control, which simply exposes an items list, the TreeView control instead simply has a root property which the developer must specify. The root property is of type TreeItem<T> (where the T corresponds with the type of the TreeView instance itself, as TreeView has one generic type as well). The root property unsurprisingly represents the root element of the TreeView, from which all descendants derive. The primary properties for TreeView are shown in Table 4-33.
Table 4-33

Properties of the TreeView class

Property

Type

Description

cellFactory

ObjectProperty<Callback<TreeView<T>,TreeCell<T>>>

Cell factory used for creating all cells.

editable

BooleanProperty

Whether the TreeView is able to enter editing state.

editingItem

ReadOnlyObjectProperty<TreeItem<T>>

The TreeItem currently being edited.

expandedItemCount

ReadOnlyIntegerProperty

The total number of tree nodes able to be visible in the TreeView.

focusModel

ObjectProperty<FocusModel<TreeItem<T>>>

Refer to the “Selection and Focus Models” section.

root

ObjectProperty<TreeItem<T>>

The root tree item in the TreeView.

selectionModel

ObjectProperty<MultipleSelectionModel<TreeItem<T>>>

Refer to the “Selection and Focus Models” section.

showRoot

BooleanProperty

Whether the root is shown or not. If not, all children of the root will be shown as root elements.

TreeItem is a relatively simple class and behaves in a similar manner to MenuItem, discussed earlier, in that it is not a class that extends from Control or even Node. It is purely a model class used to represent the abstract concept of a tree item (either a branch, with children of its own, or a leaf, with no children). The properties of TreeItem are shown in Table 4-34.
Table 4-34

Properties of the TreeItem class

Property

Type

Description

expanded

BooleanProperty

Whether this TreeItem is expanded or collapsed.

graphic

ObjectProperty<Node>

The graphic to show beside any text or other representation.

leaf

ReadOnlyBooleanProperty

Whether this TreeItem is a leaf node or has children.

parent

ReadOnlyObjectProperty<TreeItem<T>>

The parent TreeItem of this TreeItem, or null if it is the root.

value

ObjectProperty<T>

The value of the TreeItem – this is what will be rendered in the cell of the TreeView/TreeTableView control.

TableView

TableView, as the name implies, enables developers to display tabular data to users. This control, therefore, can be thought of as a ListView with support for multiple columns of data, rather than the single column in a ListView. With this comes a vast array of additional functionality: columns may be sorted, reordered, resized, and nested, individual columns may have custom cell factories installed, resize policies can be set to control how columns have available space distributed to them, and so much more. The primary properties of TableView are shown in Table 4-35.

TableView has a single generic type, S, which is used to specify the value of the elements allowed in the items list. Each element in this list represents the backing object for one entire row in the TableView. For example, if the TableView was to show Person objects, then we would define a TableView<Person> and add all the relevant people into the items list.

The fact that TableView has an items list is sometimes surprising to developers, as it leads to the question: How are these items transformed into the values required to be displayed in each “cell” of the TableView (e.g., suppose our TableView had columns to display a person’s first name, last name, and email address)? The answer is that this is the responsibility of each TableColumn instance created by the developer, and therefore in this case we would expect a developer to create three TableColumn instances, one each for first name, last name, and email address.
Table 4-35

Properties of the TableView class

Property

Type

Description

columnResizePolicy

ObjectProperty<Callback<ResizeFeatures, Boolean>>

This handles redistributing column space when columns or the table are resized.

comparator

ReadOnlyObjectProperty<Comparator<S>>

The current comparator based on the table columns in the sortOrder list.

editable

BooleanProperty

Whether the TableView is able to enter editing state.

editingCell

ReadOnlyObjectProperty<TablePosition<S,?>>

The position of any cell that is currently being edited.

focusModel

ObjectProperty<TableViewFocusModel<S>>

Refer to the “Selection and Focus Models” section.

items

ObjectProperty<ObservableList<S>>

The elements to show within the TableView.

placeholder

ObjectProperty<Node>

Text to display within the ListView if the items list is empty.

rowFactory

ObjectProperty<Callback<TableView<S>,TableRow<S>>>

The rowFactory is responsible for creating an entire row of TableCells (for all columns).

selectionModel

ObjectProperty<TableViewSelectionModel<S>>

Refer to the “Selection and Focus Models” section.

sortPolicy

ObjectProperty<Callback<TableView<S>,Boolean>>

Specifies how sorting should be performed.

tableMenuButtonVisible

BooleanProperty

Specifies whether a menu button should show in the top right of TableView.

TableColumn and TreeTableColumn

TableColumn exists in the set of classes in JavaFX UI controls that do not extend from Control (previous examples we’ve discussed include MenuItem, Menu, and TreeItem). TableColumn extends from TableColumnBase, as TreeTableView has similar (but not fully identical) API, which therefore necessitated the creation of TreeTableColumn. Despite the need for different classes for TableView and TreeTableView, there is still significant overlap, which is why most API is on TableColumnBase. The key properties for TableColumnBase are shown in Table 4-36.
Table 4-36

Properties of the TableColumnBase class

Property

Type

Description

comparator

ObjectProperty<Comparator<T>>

The comparator to use when this column is part of the table sortOrder list.

editable

BooleanProperty

Specifies if this column supports editing.

graphic

ObjectProperty<Node>

Graphic to show in the column header area.

parentColumn

ReadOnlyObjectProperty<TableColumnBase<S,?>>

Refer to the “Nested Columns” section.

resizable

BooleanProperty

Whether the width of the column can be changed by the user.

sortable

BooleanProperty

Whether the column can be sorted by the user.

sortNode

ObjectProperty<Node>

The “sort arrow” to show when the column is part of the sort order list.

text

StringProperty

The text to display in the column header area.

visible

BooleanProperty

Whether the column shows to the user or not.

width

ReadOnlyDoubleProperty

The width of the column.

TableColumn is a generic class with two generic types, S and T, where S is the same type as the TableView generic type and T is the type for the values that will be used in the specific column that the TableColumn represents.

When creating a TableColumn instance, the two most important properties to set are the column text property (what to show in the column header area) and the column cellValueFactory property (which is used to populate individual cells in the column).9

TableColumn is obviously designed for use with TableView, but it may surprise some readers that it is not able to be used with TreeTableView (which is covered in more detail soon). This is because TableColumn makes some API assumptions that tie it directly to the TableView API. As such, another class, called TreeTableColumn, is used in conjunction with TreeTableView, to much the same effect. For the most part, the API that we care about is interchangeable, so Table 4-37 introduces these APIs for TableColumn, but rest assured that TreeTableView API exists in much the same form.
Table 4-37

Properties of the TableColumn and TreeTableColumn classes

Property

Type

Description

cellFactory

ObjectProperty<Callback<TableColumn<S,T>,TableCell<S,T>>>

Cell factory for all cells in this table column.

cellValueFactory

ObjectProperty<Callback<CellDataFeatures<S,T>,ObservableValue<T>>>

The cell value factory for all cells in this table column.

sortType

ObjectProperty<SortType>

Specifies, when this column is part of the sort, whether it should be ascending or descending.

ObservableList<Person> data = ...
TableView<Person> tableView = new TableView<Person>(data);
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
    public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
        // p.getValue() returns the Person instance for a particular TableView row
        return p.getValue().firstNameProperty();
    }
});
tableView.getColumns().add(firstNameCol);
Listing 4-33

Code required to create a TableColumn and specify a cell value factory

The approach in Listing 4-33 assumes that the object returned from p.getValue() has a JavaFX ObservableValue that can simply be returned. The benefit of this is that the TableView will internally create bindings to ensure that, should the returned ObservableValue change, the cell contents will be automatically refreshed.

There is a more succinct option available – it makes use of reflection to achieve the same effect, without the need to write the preceding code. This is demonstrated in Listing 4-34 where we use the PropertyValueFactory class and pass in the name of the property that we wish to observe (in this case “firstName”). Internally, JavaFX will try to find a property method titled firstNameProperty() and, if it finds it, will bind to it. If it does not find it, it will look for getFirstName() and display the returned value (without binding, obviously).
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
Listing 4-34

Example of using PropertyValueFactory

In situations where a TableColumn must interact with classes created before JavaFX or that generally do not wish to use JavaFX API for properties, it is possible to wrap the returned value in a ReadOnlyObjectWrapper instance. See Listing 4-35 for example.
firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
    public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
        return new ReadOnlyObjectWrapper(p.getValue().getFirstName());
    }
});
Listing 4-35

Wrapping non-property values for use in a JavaFX TableView

For the TreeTableView control, a class similar to the PropertyValueFactory exists, called TreeItemPropertyValueFactory. It performs the same function as the PropertyValueFactory class, but it is designed to work with the TreeItem class that is used as part of the data model of the TreeTableView class.

Nested Columns

The JavaFX TableView and TreeTableView controls both have built-in support for column nesting. This means that, for example, you may have a “Name” column that contains two sub-columns, for first name and last name. The “Name” column is for the most part decorative – it isn’t involved in providing a cell value factory or cell factory (this is the responsibility of the child columns), but it can be used by the user to reorder the column position and to resize all child columns.

To create nested columns is simple and demonstrated in Listing 4-36.
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn nameCol = new TableColumn("Name");
nameCol.getColumns().addAll(firstNameCol, lastNameCol);
Listing 4-36

Creating a “name” column with two nested child columns

Cell Factories in TableView

We have already covered cell factories in the context of ListView, but cell factories in TableView (and TreeTableView) are slightly more nuanced. This is because, unlike ListView and TreeView, in the TableView and TreeTableView classes, there are two possible places where a cell factory can be placed.

Firstly, a “row factory” can be specified on the TableView and TreeTableView controls. A row factory is responsible for displaying an entire row of information, and therefore a custom row factory must take care to carefully display all columns appropriately. For this reason, row factories are very rarely created by developers.

Instead, developers tend to specify a custom cell factory on a single TableColumn (or TreeTableColumn, for the TreeTableView case). When a cell factory is set on a TableColumn, it functions in much the same way as a cell factory functions when set on a ListView – it is focused solely on representing a single cell (i.e., a column/row intersection) and not an entire row. This works well, as in most cases it is a fact that we wish to display all cells in a given column the same way, and therefore by specifying a custom cell factory on a TableColumn, we can enable this without great difficulty. In fact, the approach to writing a custom cell factory for a TableColumn is essentially exactly the same as for writing one for a ListView.

TreeTableView

Now that we have covered TreeView and TableView, we are left with covering TreeTableView , which from an API point of view takes elements of both TreeView and TableView. Therefore, to simplify the discussion and avoid repetition, this section on TreeTableView will be largely spent detailing from which of the two controls TreeTableView inherits its API.

The high-level summary of TreeTableView is that it uses the same TreeItem API as TreeView, and therefore it is required for developers to set the root node in the TreeTableView. This also means that there is no items list, such as in ListView and TableView. Similarly, the TreeTableView control makes use of the same TableColumn-based approach that the TableView control uses, except instead of using the TableView-specific TableColumn class, developers will need to use the largely equivalent TreeTableColumn class instead.

In terms of functionality displayed to the end user, the TreeTableView is essentially equivalent to the TableView, with the addition of the ability to expand/collapse branches and indentation of branch children when they are showing. TreeTableView has quite a few properties, which are listed in Table 4-38.
Table 4-38

Properties of the TreeTableView class

Property

Type

Description

columnResizePolicy

ObjectProperty<Callback<ResizeFeatures, Boolean>>

This handles redistributing column space when columns or the table are resized.

comparator

ReadOnlyObjectProperty<Comparator<TreeItem<S>>>

The current comparator based on the table columns in the sortOrder list.

editable

BooleanProperty

Whether the TableView is able to enter editing state.

editingCell

ReadOnlyObjectProperty<TreeTablePosition<S,?>>

The position of any cell that is currently being edited.

expandedItemCount

ReadOnlyIntegerProperty

The total number of tree nodes able to be visible in the TreeTableView.

focusModel

ObjectProperty<TreeTTableViewFocusModel<S>>

Refer to the “Selection and Focus Models” section.

items

ObjectProperty<ObservableList<S>>

The elements to show within the TableView.

placeholder

ObjectProperty<Node>

Text to display within the ListView if the items list is empty.

root

ObjectProperty<TreeItem<S>>

The root tree item in the TreeTableView.

rowFactory

ObjectProperty<Callback<TreeTableView<S>,TreeTableRow<S>>>

The rowFactory is responsible for creating an entire row of TreeTableCells (for all columns).

selectionModel

ObjectProperty<TreeTableViewSelectionModel<S>>

Refer to the “Selection and Focus Models” section.

sortPolicy

ObjectProperty<Callback<TreeTableView<S>,Boolean>>

Specifies how sorting should be performed.

tableMenuButtonVisible

BooleanProperty

Specifies whether a menu button should show in the top right of TreeTableView.

treeColumn

ObjectProperty<TreeTableColumn<S,?>>

Which column should have the disclosure node drawn within it.

Selection and Focus Models

A number of the UI controls that ship with JavaFX consistently expose selection or focus models. This abstraction makes it simpler for developers to understand all UI controls, as they offer the same API for common scenarios. The SelectionModel API is far more widely used in a number of APIs, so we will cover it first.

SelectionModel

SelectionModel is an abstract class extended with a single generic type, T, that represents the type of the selected item in the related UI control. Because SelectionModel is abstract, most use cases are typically based on one of the two provided subclasses: SingleSelectionModel and MultipleSelectionModel. As their names imply, SingleSelectionModel is used in UI controls where only a single selection can be made at a time (e.g., in TabPane, it is only ever valid to select a single Tab at a time), whereas MultipleSelectionModel supports there being multiple selections existing at the same time (e.g., multiple rows in a ListView may be selected at the same moment).

Beyond the main two properties mentioned in Table 4-39, there are a number of methods for performing selection, clearing selection, and querying whether a given index or item is currently selected.
Table 4-39

Properties of the SelectionModel class

Property

Type

Description

selectedIndex

ReadOnlyIntegerProperty

The currently selected cell index in the UI control.

selectedItem

ReadOnlyObjectProperty<T>

The currently selected item in the UI control.

Moving further down the inheritance hierarchy, MultipleSelectionModel introduces additional API for allowing developers to select multiple rows at once and to observe on the selectedIndices and selectedItems lists for when state changes.

The final level of inheritance is table-specific selection models (TableViewSelectionModel and TreeTableViewSelectionModel). These classes add APIs to change selection mode between row and cell selection, and when in cell selection mode make it possible to select cells based on their row/column intersection points. They also make available a selectedCells list to listen for state changes.

FocusModel

The notion of focus in JavaFX can be a little odd when applied to UI controls, as there is an overloading of the term. In JavaFX, the more correct use of focus is related to what happens when the user “tabs” through a user interface – they are shifting focus between the various UI controls and nodes. Whichever of these elements has focus is then receptive to all other keyboard input.

Some UI controls have overloaded the term focus to also mean what could more precisely be referred to as “internal focus.” The ListView, TreeView, TableView, and TreeTableView controls all have focus models to allow for programmatic manipulation and observation of this internal focus. In this regard, the focus is not on a Node, but rather on an element inside the UI control, and we do not concern ourselves with the Node, but the value (of type T) in that row (as well as the index position of that row).

In many regards, a FocusModel can be considered quite similar to a SingleSelectionModel in that there can only ever be one focused element, and this is what makes the API for FocusModel simpler than for the more common MultipleSelectionModel we’ve already discussed. The two main properties of FocusModel are shown in Table 4-40.
Table 4-40

Properties of the FocusModel class

Property

Type

Description

focusedIndex

ReadOnlyIntegerProperty

The currently focused cell index in the UI control.

focusedItem

ReadOnlyObjectProperty<T>

The currently focused item in the UI control.

Summary

This chapter has methodically stepped through all UI controls that ship as part of JavaFX 11. Readers should now feel that they have sufficient knowledge to more easily create user interfaces consisting of appropriate UI controls.

As noted at the beginning of this chapter, there is a companion application available with the source code samples for this book that demonstrates all UI controls that are available as part of the core JavaFX distribution. Readers are encouraged to execute this application to become more familiar with how each UI control operates and to also better see how to use the UI control in their own development.

Acknowledgments

The companion application that was developed for this chapter could not have been fully realized without contributions from members of the JavaFX community. The author of this chapter would therefore like to take the opportunity to thank the following people: Abhinay Agarwal, Fouad Almalki, Almas Baimagambetov, Frank Delporte, Cyril Fischer, and Hossein Rimaz. Thanks!

Additionally, this chapter was expertly reviewed by Abhinay Agarwal. Thanks!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.40.189