Chapter     6

Custom UIs

Wouldn’t it be nice if you could change the appearance of your application without altering its functionality? Many companies spend lots of money and time to design user interfaces (UIs). Often companies will mock up several designs before deciding how their UI should look and behave. Depending on the scope, many companies also build UIs that support different screen dimensions. Typically, tablets, smart phones, TVs, and desktops are among the main challenges UI designers face.

In this chapter you will learn about theming and how to customize applications by applying various themes (look and feels). Next, you will learn about the fundamentals of JavaFX CSS styling, which will enable you to change attributes of any Scene graph node, such as colors, backgrounds, margins, and so on. After learning about CSS styling, you will apply these same techniques while using the amazing Scene Builder application tool. Scene Builder is a free tool that enables you to create and define UI screens graphically and save them as a JavaFX FXML-formatted file. Last, you will learn about creating custom controls. Here you will learn how to build UI controls that aren’t part of the JavaFX 8 built-in controls. Some custom controls that might come to mind are number pads, gauges, clocks or futuristic context menus.

In this chapter you will be learning the following tools and concepts:

  • Theming
  • JavaFX CSS styling
  • Scene Builder
  • Custom controls

Theming

You have probably heard the terms skinning and look and feel in the context of designing user interfaces. These terms are often used interchangeably, and both reflect the basic concept of theming. Theming has the idea of transforming an entire application’s appearance without changing its underlying functionality. An example of theming would be to change an application’s UI layout and controls from a stylized business theme to a sports-like theme. Usually, companies will try to skin an application to resemble their logo. They’ll also use beautiful typefaces (typography) selected to personify their corporate culture or target audience.

In JavaFX you have the ability to create, modify, or use existing themes to skin your applications. Later, we will discuss how to create themes, but for now I’ll show you how to apply existing themes (skins) in JavaFX applications. To demonstrate applying themes to an application’s UI, I created the JavaFX 8 application depicted in Figure 6-1, which allows a user to choose and apply various UI look-and-feel themes.

9781430264606_Fig06-01.jpg

Figure 6-1. Setting an application's look and feel using JavaFX CSS styling. Moving counterclockwise from the upper-left are the Caspian, Modena, Sky and Flat Red look-and-feel themes

As you can see, there are four themes applied to the same application. At the upper left is the familiar Caspian theme from Java FX 2.X. Caspian has served well over the years and still has quite the professional look. At the lower left you see an accordion UI control expanded to show progress controls using JavaFX 8’s new default theme, Modena. The Modena theme seems brighter, cleaner, and more modern.

At the lower right, our next theme, called the Sky look and feel, was created by yours truly (Carl Dea). My intent was to create a theme for touch displays such that the controls would be slightly enlarged for adult-sized fingers. I wanted a friendly feel and so the cool blue sky came to mind.

Last but not least at the upper right is the Flat Red look and feel, created by Gerrit Grunwald. The Flat Red uses fonts to give text on controls a crisp and clean look. Another nice feature of the Flat Red theme is that it uses CSS effects based on pseudo-class state changes of UI controls, such as a mouse press on a slider control. Don’t worry if you don’t know yet what pseudo-classes are, because later in this chapter we will discuss pseudo-class selectors in detail. Mr. Grunwald’s look and feel is also an excellent candidate for touch displays.

Before jumping into code I want to give a shout-out to individuals in the JavaFX community who have crafted beautiful skins and themes that you will find most inspiring. For those interested in the OS X (Mac desktop) native look and feel, Claudine Zillmann (@etteClaudette) has created AquaFX (with Elements). AquaFX has an API that allows developers to style applications easily with the native OS X look, but also to theme the UI with colors other than the usual aqua blue.

If you prefer the Windows 8 Metro native look and feel, Pedro Duque Vieira (@P_Duke) has created JMetro. The JMetro look and feel can style applications that resemble many applications you’ll find on Microsoft Surface devices or Windows 8 desktops. JMetro also has the ability to switch from a dark or light theme.

The last skin I want to mention is a really nice look and feel for touch displays that has a Roku-like or Apple TV-like interface; it’s called Flatter, by Hendrick Ebbers (@hendrikEbbers). Flatter was designed for the BoxFX project. BoxFX is another project Mr. Ebbers has created by using a Raspberry Pi device running JavaFX 8 with the Flatter look and feel. The following are their respective websites:

Applying JavaFX CSS Themes

Now that you have seen a glimpse of some great-looking skins and themes, let’s see how to apply JavaFX CSS-based themes. JavaFX has the capability to apply Cascading Style Sheets to the Scene graph and its nodes in very much the way Web developers use CSS style sheets against HTML5 elements. These CSS style sheets are external files containing attributes and values to style JavaFX nodes. An example JavaFX CSS file would look something like Listing 6-1.

Listing 6-1. A JavaFX CSS File Containing Styling Attributes

/* sample.css */
.button {
    -fx-text-fill: brighter-sky-blue;
    -fx-border-color: rgba(255, 255, 255, .80);
    -fx-border-radius: 8;
    -fx-padding: 6 6 6 6;
    -fx-font: bold italic 20pt "LucidaBrightDemiBold";
}
/* ...more styling */

This code contains a CSS class style definition for JavaFX button controls. Button UI controls by default have a class style button, which maps to -fx- prefixed properties. You will learn about JavaFX CSS styling later in this chapter, but for now let’s discuss how to swap between the CSS style sheets that will dynamically skin an application.

In this section you will learn two ways to apply CSS style sheets as look-and-feel themes onto JavaFX applications. The first way to apply CSS style sheets is by invoking the static setUserAgentStylesheet() method on the JavaFX Application (javafx.application.Application) class. This static method styles every scene and all child nodes in a JavaFX application.

The second way of applying a look and feel is by invoking a Scene object’s getStylesheets().add() method. Unlike the first method of applying a look and feel, the getStylesheets().add() method is used to style a single scene and its child nodes.

Using the setUserAgentStylesheet(String URL) Method

The setUserAgentStylesheet(String URL) method accepts a valid URL string representing the JavaFX CSS file. Typically, CSS files are bundled inside a jar application; however. they can reside on the local file system or a remote web server. When the CSS file is in the classpath, the call to the getClass().getResource("path/some_file.css").toExternalForm() method will find the CSS file and produce a URL String for accessing the file.

The following code snippet loads the sample.css file as the current look and feel. The sample.css file in the code snippet is co-located where the current class is located. In other words, your Java class and your CSS file are in the same directory, so there is no need for a path in front of the file name.

Application.setUserAgentStylesheet(getClass().getResource("sample.css")
                                  .toExternalForm());

The setUserAgentStylesheet() method applies styling globally to all scenes owned by an application, such as context menus and child popup dialog windows. JavaFX 8 currently contains two style sheets, Caspian and Modena, which serve as default cross-platform look and feel skins. Because the two style sheets are predefined, you can easily switch between them using the setUserAgentStylesheet() method. The following code shows how to switch between the Caspian and Modena look and feel style sheets.

// Switch to JavaFX 2.x's CASPIAN Look and Feel.
Application.setUserAgentStylesheet(STYLESHEET_CASPIAN);
 
// Switch to JavaFX 8's Modena Look and Feel.
Application.setUserAgentStylesheet(STYLESHEET_MODENA);

A great way to learn how the pros do it (creating skins) is to look at good examples. You can extract the CSS files (caspian.css and modena.css) from the jfxrt.jar file or view the JavaFX source code, located at http://openjdk.java.net.

Note  When we invoke the setUserAgentStylesheet(null) method by passing in a null value, the default style sheet (Modena) will be automatically loaded and set as the current look and feel. However, if you are using JavaFX 2.x, the default style sheet will be Caspian.

Using Scene’s getStylesheets().add(String URL) Method

By calling the getStylesheets().add() method, you can style a single scene and its child nodes. As in the previous call to setUserAgentStylesheet(), you would pass in a URL string that represents a JavaFX CSS file. Later, you will see an example application that switches between different styles by using either the setUserAgentStylesheet() or the getStylesheets().add() method. You are probably wondering what the exact differences between the two methods are.

Because creating an entire look and feel would require hundreds if not thousands of lines of code in order to style every UI control in JavaFX, I basically created very small look-and-feel style sheet files to demonstrate the ability to swap themes. The simple example CSS files have just a handful of CSS styles for a small amount of UI controls that would be styled. In other words, the example’s small CSS files are loaded via the getStylesheets().add() method, which styles only some of the nodes and not every node in the application.

Because the default style sheet (Modena.css) is loaded from the prior call via the setUserAgentStylesheet(null) method, the smaller custom CSS styling file can then “piggyback” onto the default style sheet. What’s nice is that you wouldn’t have to start from scratch to create a brand new look and feel. The following snippet of code initially invokes the setUserAgentStylesheet() method with a null value, which loads Modena as the default look and feel and then sets the scene’s additional styling by invoking the getStylesheets().add() method.

Application.setUserAgentStylesheet(null); // defaults to Modena
 
// apply custom look and feel to the scene.
scene.getStylesheets()
     .add(getClass().getResource("my_cool_skin.css")
                    .toExternalForm());

An Example of Switching Themes

To show how to switch between various CSS style sheets (aka skins or themes), I’ve created an example application called the Look N Feel Chooser, which allows you to choose between different predefined look-and-feel themes. Figure 6-2 shows the Look and Feel Chooser example application initially displaying accordion UI control panes with common UI controls, beginning with the Modena look and feel.

9781430264606_Fig06-02.jpg

Figure 6-2. Predefined look-and-feel themes to choose from the Look ‘N’ Feel menu

In Figure 6-2 you’ll notice the menu options to select a look and feel (skin). Once a look and feel has been selected, the application’s appearance will change dynamically. Figure 6-3 shows the application switched to the FlatRed look and feel.

9781430264606_Fig06-03.jpg

Figure 6-3. The Look N Feel Chooser application using the Flat Red look and feel

Notice the accordion UI control’s title bar, reading “Scroll Bars & Sliders,” expanded with UI controls sporting the selected look and feel. This ability would be a great way to show off different look-and-feel themes to your users while their application continues to function as usual.

The next section shows the main source code of our Look N Feel Chooser application in Listing 6-2. For the sake of space, you will see only the start() method and not the entire source code. To see the entire source code listing of the Look N Feel Chooser application, please visit the book’s website to download the project.

The Look N Feel Chooser Example Application Code

By loading the Look N Feel Chooser project into the NetBeans IDE, you can see that the source code consists of five files: LookNFeelChooser.java, lnf_demo.fxml, controlStyle1.css, controlStyle2.css, flatred.css, and sky.css. The files with an extension of .ttf are TrueType fonts that are used in the FlatRed look and feel. Figure 6-4 shows what the project structure looks like in the NetBeans IDE.

9781430264606_Fig06-04.jpg

Figure 6-4. The LookNFeelChooser project structure in the NetBeans IDE

Looking at the project structure you’ll notice the LookNFeelChooser.java file as the main driver application class. The lnf_demo.fxml file is an FXML-formatted file representing the center content, which contains the accordion UI control with other UI control elements. The FXML file was created by the Scene Builder tool, discussed later in this chapter. The rest of the files are JavaFX CSS style sheets that represent different look-and-feel themes: controlStyle1.css, controlStyle2.css, flatred.css, and sky.css.

Listing 6-2 shows the init() and start() methods of the LookNFeelChooser.java Application class. Remember that all JavaFX applications are first initialized via the init() method and then begin with the start() method. After you have examined Listing 6-2, we will discuss how it all works.

Listing 6-2. The LookNFeelChooser Project Source Code to Switch between Look and Feel (JavaFX CSS) Style Sheets

    @Override public void init() {
        Font.loadFont(LookNFeelChooser.class
                                      .getResourceAsStream("Roboto-Thin.ttf"), 10)
                                      .getName();
        Font.loadFont(LookNFeelChooser.class
                                      .getResourceAsStream("Roboto-Light.ttf"), 10)
                                      .getName();
    }
 
    @Override public void start(Stage primaryStage) throws IOException {
        BorderPane root    = new BorderPane();
        Parent     content = FXMLLoader.load(getClass().getResource("lnf_demo.fxml"));
        Scene      scene   = new Scene(root, 650, 550, Color.WHITE);
        root.setCenter(content);
 
        // Menu bar
        MenuBar menuBar = new MenuBar();
 
        // File menu
        Menu     fileMenu = new Menu("_File");
        MenuItem exitItem = new MenuItem("Exit");
        exitItem.setAccelerator(new KeyCodeCombination(KeyCode.X, KeyCombination.SHORTCUT_DOWN));
        exitItem.setOnAction(ae -> Platform.exit());
 
        fileMenu.getItems().add(exitItem);
        menuBar.getMenus().add(fileMenu);
 
        // Look and feel menu
        Menu lookNFeelMenu = new Menu("_Look 'N' Feel");
        lookNFeelMenu.setMnemonicParsing(true);
        menuBar.getMenus().add(lookNFeelMenu);
        root.setTop(menuBar);
 
        // Look and feel selection
        MenuItem caspianMenuItem = new MenuItem("Caspian");
        caspianMenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            setUserAgentStylesheet(STYLESHEET_CASPIAN);
        });
        MenuItem modenaMenuItem = new MenuItem("Modena");
        modenaMenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            setUserAgentStylesheet(STYLESHEET_MODENA);
        });
        MenuItem style1MenuItem = new MenuItem("Control Style 1");
        style1MenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            scene.getStylesheets()
                 .add(getClass()
                 .getResource("controlStyle1.css")
                 .toExternalForm());
        });
        MenuItem style2MenuItem = new MenuItem("Control Style 2");
        style2MenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            scene.getStylesheets()
                 .add(getClass()
                 .getResource("controlStyle2.css")
                 .toExternalForm());
        });
 
        MenuItem skyMenuItem = new MenuItem("Sky LnF");
        skyMenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            scene.getStylesheets()
                 .add(getClass()
                 .getResource("sky.css")
                 .toExternalForm());
        });

        MenuItem flatRedMenuItem = new MenuItem("FlatRed");
        flatRedMenuItem.setOnAction(ae -> {
            scene.getStylesheets().clear();
            setUserAgentStylesheet(null);
            scene.getStylesheets()
                 .add(getClass()
                 .getResource("flatred.css")
                 .toExternalForm());
        });
 
        lookNFeelMenu.getItems()
                     .addAll(caspianMenuItem,
                             modenaMenuItem,
                             style1MenuItem,
                             style2MenuItem,
                             skyMenuItem,
                             flatRedMenuItem);
 
        primaryStage.setTitle("Look N Feel Chooser");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

Note  To run this example, make sure the CSS and TTF (true type font) files are located in the compiled class’s area. Resource files can be loaded easily when placed in the same directory (package) as the compiled class file that is loading them. The CSS files are initially co-located with this source code example file. In NetBeans, you can select Clean and Build Project or you can copy files to your class’s build area.

How It Works

The Look N Feel Chooser application is first initialized with the overloaded method init(), which loads the required fonts used for the FlatRed look-and-feel theme. After the init() method is executed the JavaFX application life-cycle will invoke the start() method.

In the start() method the code begins by creating a border pane layout, which then loads an FXML file to be placed as the center content region. FXML is an XML-based language to express JavaFX UIs. This provides a way to separate the presentation layer from the application logic layer. Typically, FXML is generated by a GUI builder tool that allows a designer to drag and drop controls and create UIs graphically. The following line uses the FXMLLoader.load() method to unmarshall (deserialize) the center content. The center content pane is an AnchorPane layout containing an Accordion control holding other UI controls.

Parent content = FXMLLoader.load(getClass().getResource("lnf_demo.fxml"));

Later in this chapter you will learn about the Scene Builder tool, which allows you to create UIs to be saved as FXML-formatted files.

Continuing with our example code, you will notice the usual creation of a scene with a root node. Next, the code builds a menu bar with menu items. The first menu will be the File menu, to allow the user to exit the application. Notice that for a quick exit using a keyboard shortcut, the KeyCodeCombination instance allows the user to hit the Ctrl-X key combo. To learn about menus and keyboard shortcuts, please refer to Chapter 4.

The second menu is Look ‘N’ Feel, which allows the user to select predefined look-and-feel CSS style sheet files. The menu items are set to invoke handler code (lambda expressions) that is triggered based on the onAction event. The following code snippet is a menu item that is responsible for switching the application to use the Caspian look and feel:

MenuItem caspianMenuItem = new MenuItem("Caspian");
caspianMenuItem.setOnAction(ae -> {
     scene.getStylesheets().clear();
     setUserAgentStylesheet(STYLESHEET_CASPIAN);
});

Notice that the onAction handler code containing two statements. In the JavaFX API the STYLESHEET_CASPIAN and STYLESHEET_MODENA variables are enums representing the CSS files based on their location on the classpath.

Because the rest of the code is pretty similar, I’m going to fast-forward to the last look-and-feel menu option, which switches to the Flat Red look and feel. The other menu items are essentially identical and invoke the same methods to clear previously loaded CSS style sheet files. For the Flat Red look and feel I used the getStylesheets().add() method to style only the current scene-level nodes. The Flat Red look and feel styles a subset of UI controls that includes sliders, scroll bars, buttons, and progress controls. The code snippet shown next sets up a menu item to be selected that will skin the application with the Flat Red look and feel. After the style is loaded that clears previously loaded style sheets, it loads the default look and feel (null) and then loads the flatred.css file, which will style the given scene.

MenuItem flatRedMenuItem = new MenuItem("FlatRed");
flatRedMenuItem.setOnAction(ae -> {
    scene.getStylesheets().clear();
    setUserAgentStylesheet(null);
    scene.getStylesheets()
         .add(getClass()
         .getResource("flatred.css")
         .toExternalForm());
});

JavaFX CSS

Now that you know how to load CSS style sheet files, let’s discuss JavaFX CSS selectors and styling properties (rules). Similar to the way HTML5 uses CSS style sheets, there are selectors or style classes associated with Node objects on the Scene graph. All JavaFX Scene graph nodes have a setId(), a getStyleClass().add(), and a setStyle()method, respectively, to apply styling properties that could potentially change the node’s background color, border, stroke, and so on.

Before we discuss selectors, I would like to refer you to the JavaFX CSS Reference Guide at http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html. This invaluable reference guide will be very handy throughout this book and beyond.

What Are Selectors?

Similar to the W3C CSS (World Wide Web Consortium) standards for styling HTML elements, JavaFX CSS has the concept of selectors. Selectors are basically tags to help locate JavaFX nodes on the Scene graph to be styled using CSS style definitions. The two kinds of selector types are id and class. An id selector is a unique string name that is set on a scene node.

A class selector is also a string name that can be added as a tag to any JavaFX node. I should also point out that a class selector has no relation to the concepts of C++ or Java classes. Class selectors allow any number of nodes to contain the same class string name for the ability to style nodes with one CSS style definition. In the next sections you will learn more about how to define CSS selector types, and we will follow-up with an example.

CSS id Type Selectors

The id type selector is a unique string name assigned to a node. This means that no other node’s id can be the same. When using id type selectors, you will invoke the setId(StringID) method on a JavaFX node object. For example, to target a Button instance whose id is my-button, you would invoke the setId("my-button") method. To style the button with an id of my-button, you would create a CSS style definition block declared with an id selector #my-button, as shown here:

#my-button{
   -fx-text-fill: rgba(17, 145, 213);
   -fx-border-color: rgba(255, 255, 255, .80);
   -fx-border-radius: 8;
   -fx-padding: 6 6 6 6;
   -fx-font: bold italic 20pt "LucidaBrightDemiBold";
}

This CSS styling block will be applied to a button with the unique id of my-button. Thus no other node will be allowed to contain an id of my-button. You will also notice that when we use id selectors in styling blocks, the CSS selector name is prefixed with the # symbol, and when setting the id in Java code the # symbol is not used.

CSS class Type Selectors

When using class type selectors you will invoke the getStyleClass().add(String styleClass) method to add a selector to a node. The method allows you to have multiple style classes for styling a node. Since the getStyleClass() method returns an ObservableList you can add and remove style classes in an ad hoc fashion to update its appearance dynamically. For example, let’s target two buttons whose style classes (ObservableList) contain a num-button via the getStyleClass().add("num-button") method. The following is a CSS style definition block declared with a class selector .num-button:

.num-button{
   -fx-background-color: white, rgb(189,218,230), white;
   -fx-background-radius: 50%;
   -fx-background-insets: 0, 1, 2;
   -fx-font-family: "Helvetica";
   -fx-text-fill: black;
   -fx-font-size: 20px;
}

This CSS styling block will be applied to buttons with a style class num-button. You will notice that when using class selectors, the CSS selector name is prefixed with a dot (.), and when adding the selector in Java code the (.) symbol is not present.

Selector Patterns

Up until now you’ve seen only simple selectors; however, selectors can have patterns for the ability to traverse the Scene graph’s hierarchy of nodes from the root node to the child nodes. Of course, a full discussion of selector patterns is beyond the scope of this book, but I will briefly mention common selector patterns and introduce pseudo-classes.

Common Selector Patterns

Often you will want to style many nodes based on common selector patterns. A typical selector pattern you would want to perform would be to style child nodes whose parent is of a certain type. Another pattern would be to style two different kinds of nodes with a common property. The following example shows selector patterns for both of these use cases:

/* style all buttons who's parent is an HBox */
.hbox > .button { -fx-text-fill: black; }
 
/* style all labels and text nodes */
.label, .text { -fx-font-size: 20px; }

As you can see, the selector pattern (.hbox > .button) styles Button nodes that are descendants of an HBox and is pretty straightforward. The greater-than symbol between the two selectors lets the system know which nodes to style. Also, as described in the JavaFX CSS Reference Guide, the selectors for UI controls have a naming convention. In this convention, selectors for JavaFX nodes are all lowercase letters, and if a control has more than one word they are separated by hyphens. For instance, a GridPane’s class selector would be .grid-pane. Keep in mind that not all nodes have named class selectors by default, so please refer to the JavaFX CSS Reference guide.

The second use case has to do with setting a common property that is shared by different types of nodes. Our second example is a selector pattern that styles all Label nodes and Text nodes to have the same text font size. The comma denotes a list of selectors to style. In this scenario the font size will be styled to be 20 points.

Pseudo-Class Selectors

Pseudo-class selectors are used to style nodes that have different states. An example would be a Button node. A button has the following states: hover, selected, focused, and so on. To specify selectors with pseudo-classes you must append a colon and the state (type) to the main selector name. The following code snippet shows two selector patterns for a button that has a class selector num-button:

.num-button {
    -fx-background-color: white,
        bluish-gray,
        white;
    -fx-background-radius: 50%;
    -fx-background-insets: 0, 1, 2;
    -fx-font-family: "Helvetica";
    -fx-text-fill: black;
}
.num-button:hover {
    -fx-background-color: black,
        white,
        black;
    -fx-text-fill: white;
}

In this CSS code, the first (.num-button) selector merely styles a button in its normal state. However, the second selector with the appended colon and state (hover) will change a subset of properties, altering them slightly. In this scenario when a mouse cursor moves over (hovers) a button with the style class num-button:hover, the color of the button and the text’s fill color will be reversed. Figure 6-5 shows the pseudo-selector in action when the mouse is hovering over the number pad on button 3.

9781430264606_Fig06-05.jpg

Figure 6-5. A selector with a pseudo-class of hover is used when the mouse cursor is on top of any number pad button

A Selector Styling Example

To demonstrate selectors using id and class type selectors, I’ve created an application resembling a smart phone’s number pad, as shown in Figure 6-6.

9781430264606_Fig06-06.jpg

Figure 6-6. An application to mimic a number pad from a smart phone

In Figure 6-6 you will notice a 4 × 3 grid of round buttons for a number pad and a green rectangular button beneath it to make a phone call. Listing 6-3 is the main start() method containing the source code. Listing 6-4 then shows the contents of the CSS file mobile_buttons.css.

Listing 6-3. The JavaFX Source Code for Our Number Pad Application

@Override
public void start(Stage primaryStage) {
 
    BorderPane root = new BorderPane();
    Scene scene = new Scene(root, 180, 250);
    scene.getStylesheets()
         .add(getClass().getResource("mobile_buttons.css")
                        .toExternalForm());
    String[] keys = {"1", "2", "3",
                     "4", "5", "6",
                     "7", "8", "9",
                     "*", "0", "#"};
    GridPane numPad = new GridPane();
    numPad.getStyleClass().add("num-pad");
 
    for (int i=0; i < 12; i++) {
        Button button = new Button(keys[i]);
        button.getStyleClass().add("num-button");
        numPad.add(button, i % 3, (int) Math.ceil(i/3) );
    }
    // Call button
    Button call = new Button("Call");
    call.setId("call-button");
    call.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
    numPad.add(call, 0, 4);
    GridPane.setColumnSpan(call, 3);
 
    GridPane.setHgrow(call, Priority.ALWAYS);
    root.setCenter(numPad);
    primaryStage.setScene(scene);
    primaryStage.show();
}

Listing 6-4. The Contents of the CSS File mobile_bottons.css

.root {
    -fx-background-color: white;
    -fx-font-size: 20px;
    bright-green: rgb(59,223, 86);
    bluish-gray: rgb(189,218,230);
}
.num-pad {
    -fx-padding: 15px, 15px, 15px, 15px;
    -fx-hgap: 10px;
    -fx-vgap: 8px;
}
.num-button {
    -fx-background-color: white,
        bluish-gray,
        white;
    -fx-background-radius: 50%;
    -fx-background-insets: 0, 1, 2;
    -fx-font-family: "Helvetica";
    -fx-text-fill: black;
}
.num-button:hover {
    -fx-background-color: black,
        white,
        black;
    -fx-text-fill: white;
}
#call-button {
   -fx-background-color: white, bright-green;
   -fx-background-radius: 2;
   -fx-background-insets: 0, 1;
   -fx-font-family: "Helvetica";
   -fx-text-fill: white;
}
#call-button:hover {
   -fx-background-color: bright-green, white;
   -fx-background-radius: 2;
   -fx-background-insets: 0, 1;
   -fx-font-family: "Helvetica";
   -fx-text-fill: bright-green;
}

How It Works

The code starts by creating a scene that has a BorderPane as a root node. After the scene is created, the code loads the CSS style sheet file mobile_buttons.css to style the current scene’s nodes. Next, the code simply creates a grid using the GridPane class and generates 12 buttons to be placed in each cell. Notice in the for loop that each button is set with the style class named num-button via the getStyleClass().add() method.

Last, the green call button will be added to the last row of the grid pane. Because the call button is unique, its id selector is set with call-button, and it will be styled using the id selector, which means the selector named inside the CSS file will be prefixed with the # symbol. To see some amazing button styling, head over to the FXexperience.com’s blog entry on styling buttons at http://fxexperience.com/2011/12/styling-fx-buttons-with-css by Jasper Potts (Developer Experience Architect on Client Java at Oracle).

How to Define -fx- Based Styling Properties (Rules)

I’m sure by now you have noticed the many name-value pairs (properties) inside styling definition blocks having the prefix -fx-. These properties, often called rules, can be defined to have values that can set a region’s border width, background fill colors, and so on. In this section you will learn how to style a node using selector styling blocks and the ability to override properties by using inline styling.

Styling a Node with a Selector Style Definition Block

A selector style definition block always begins with a selector name prefixed with a . or # symbol. The symbol determines the selector type as mentioned earlier. A selector will start a block with an open curly brace, followed by property or rule definitions. Each JavaFX theme property or rule will be prefixed with -fx- and its appropriate property name. A property name and value are separated by a : symbol, and the pair ends with a semicolon. To finish the styling block, you simply end the block with a closing curly brace. The syntax for a selector style definition block looks like this:

. or #<selector-name> <pattern>{
   -fx-<some-property> : <some-value>;
}

One last thing to mention is the ability to add comments to CSS styling definitions. To add comments you would use an opening slash asterisk /* and a closing asterisk slash */, the same as adding comments in C/C++ and Java. Shown next is an example of using comments in a selector style block definition:

.num-button{
   -fx-background-color: white, rgb(189,218,230), white;
   /*
     This is a comment.
     -fx-background-radius: 50%;
   */
   -fx-background-insets: 0, 1, 2;
   -fx-font-family: "Helvetica";
   -fx-text-fill: black;
   -fx-font-size: 20px;
}

Styling a Node by Inlining JavaFX CSS Styling Properties

While CSS selector blocks are the recommended way to style your JavaFX nodes, there might be situations where you will want to override styling properties. For example you may want to enlarge a button and change its text color temporarily as a mouse cursor hovers (OnMouseEntered) over the specified button. When the mouse cursor is not hovering (OnMouseExited) the button’s inline style is removed which then reverts the styling to the parent class or id selector styling blocks.  To override a node’s styling property set from its ancestor (parent) id or class selector, you can invoke the node’s method setStyle(). To implement the prior example just mentioned the code snippet below implements a button with handler code that responds to OnMouseEntered and OnMouseExited events which toggles the button’s text size and color.

Button button = new Button("Press Me");
button.getStyleClass().add("my-default-style");
button.setOnMouseEntered(actionEvent -> button.setStyle("-fx-font-size: 30px; -fx-text-fill: green;"));
button.setOnMouseExited(actionEvent -> button.setStyle(""));

Styling Properties (rules) Limitations

Because all graph nodes extend from the Node class, derived classes will be able to inherit styling properties from their ancestors. Knowing the inheritance hierarchy of node types is very important because the type of node will help determine the types of styling properties you can control. For instance, a Rectangle class extends from Shape, which extends from Node. Having said this, you will later see that some properties that you assumed would be on a node don’t exist. So how do you know what properties exist? The solution is to refer to the JavaFX CSS Reference Guide. Again, based on the type of node there are limitations to the styles you are able to set.

Obeying the JavaFX CSS Rules

Did you know that styling properties (rules) can be overridden? To override properties you must learn about the order of precedence when rules are defined. Figure 6-7 depicts a typical Scene graph with a root (parent) node with child nodes. A root node such as a BorderPane layout node will contain a class selector of root, which is meant for top-level properties. These are basically properties common to many nodes, such as font size for text or background color. This is a great feature for theming, because many nodes that share a property will inherit that change. As mentioned earlier, some nodes will contain default selectors; for example,JavaFX Button instances will have a .button class selector.

9781430264606_Fig06-07.jpg

Figure 6-7. A scene with a root node which has buttons as child nodes

In Figure 6-7 the class selector root could have a property that is common across many nodes:

.root {
   -fx-font-size: 12px;
}
 
.button {
   -fx-font-size: 20px;
}

When you want to style a button’s text font size you have an opportunity to override the root’s styling definition. In order to override the -fx-font-size property, the button style class definition block will override the 12 point font from the parent with a 20 point font.

Believe it or not, there is an additional way to override the style definition block of the class selector .button  in addition to the method just shown. Similar to HTML5 CSS, each element has a style attribute. JavaFX’s graph nodes also have a style attribute (property). Following is an example of how to set the style property programmatically in Java code:

Button button = new Button("press it!");
button.setStyle("-fx-font-size: 30px;");

This code will override any root-, id-, or class-level selector style definition block. Basically, the invocation to the setStyle() method allows you to add any number of property-value pairs as long they are separated by the semicolon.

Scene Builder

Up to this point you’ve learned how to code and style UI elements programmatically. You are probably wondering if there is a better way to build UIs. Well, I am happy to inform you that the JavaFX team has built the free tool Scene Builder 2.0, which allows you to build UIs graphically. The Scene Builder tool has also been open-sourced and will begin to show up in your favorite IDE. This section provides a quick glimpse of the Scene Builder tool. To download and try Scene Builder, please visit Oracle’s JavaFX tools site, at http://www.oracle.com/technetwork/java/javase/downloads/javafxscenebuilder-info-2157684.html.

Launching Scene Builder

After downloading and installing it, you will launch the Scene Builder tool. Figure 6-8 shows the Scene Builder tool.

9781430264606_Fig06-08.jpg

Figure 6-8. The Scene Builder tool

Here you can see the earlier Look N Feel Chooser project example with an AnchorPane layout node and an Accordion UI control in the center canvas area. On the left you see a split divider with UI elements that can be dragged onto the center canvas area.

Also on the left beneath the UI elements is the Document section. It has two sub-sections, Hierarchy and Controller. The Hierarchy panel allows you to view the current node hierarchy (better known as the document model). Finally, below the Hierarchy is the Controller panel. The Controller lets you create action code or presentation logic in a Java class. This allows a developer to have separate controller code bind to the UI elements.

On the right you see the Properties, Layout, and Code panels. The Properties panel allows you to style UI elements using JavaFX CSS properties, the Layout panel allows you to specify dimensions, transforms, and padding of a node’s properties, and the Code panel allows you to map event methods to UI elements such as OnMousePressed.

FXML

The Scene Builder allows you to save UI projects as a single file in an XML format called FXML (FX markup language). The FXML file is saved with an extension of .fxml. FXML is a language that represents a JavaFX-based UI. The FXML language is beyond the scope of this book; however I will briefly describe how to load the files to be displayed.

Loading FXML into a Scene

FXML provides interesting advantages over the programmatic approach. One of the main advantages is the ability to decouple the GUI (view) layer from the code logic (controller) layer. As you learned in our discussion of UI patterns in Chapter 4, it is a good idea to separate GUI code from action code. Shown here is code to load an FXML file:

BorderPane root = new BorderPane();
Parent content = FXMLLoader.load(getClass().getResource("lnf_demo.fxml"));
root.setCenter(content);

As you can see, above the root pane is a BorderPane, and we use the static method load on the javafx.fxml.FXMLLoader class The load() method will load (deserialize) the FXML file that was created by the Scene Builder tool.

Custom Controls

“Use the force, Luke.”

—Obi wan Kenobi

With all of your new-found abilities, there is yet another powerful API you will want to harness. Imagine a customer whose requirements call for specialized UI controls such as gauges, knobs, or light indicators. Surely you could attempt to use the existing UI controls to be styled, but it is rather difficult, if not impossible. So, what is a young padwan to do? (Now, say the quote). Welcome to the world of custom controls (the Controls API)!

As an example of creating a custom control, in this section you will learn how to create a light-emitting diode (LED) control. We’ll begin with a description of the LED control. Then you will see how the code is structured as we look at the classes involved in the creation of custom controls. After that, you will get a chance to look at the code before we walk through it to discuss the code details.

The LED Custom Control

The light-emitting diode (LED) is something that you will find in many electronic devices to indicate status (such as on or off). So why not using an LED as a custom control in your code as a visual indicator? Typically, an LED contains two wires and a body made from clear plastic, as shown in Figure 6-9.

9781430264606_Fig06-09.jpg

Figure 6-9. A typical light-emitting diode

Often the LED is mounted in a socket. To create a custom JavaFX control to emulate a physical object you first have to decide what materials and components you will portray.

In the case of the LED custom JavaFX control, we will approximate this appearance:

  • Metal socket
  • Plastic body
  • Light effect on top of the plastic body due to the curved surface

One thing that Java developers often don’t like, but which is really helpful for designing a custom control, is a drawing program—to be precise, a vector drawing program. When the appearance of your control is important, it makes sense to use a tool that is good for visualizing things; a drawing program, as used to create Figure 6-10. So the first thing you should do is to create a vector drawing of the control you plan to create.

9781430264606_Fig06-10.jpg

Figure 6-10. A vector drawing of the LED control

You can see the metal socket around the red plastic body and the white highlight. The red body contains an inner- and outer-shadow to create a more realistic look. If we look at the parts of the vector drawing, we will find three circles filled with gradients, as shown in Figure 6-11.

9781430264606_Fig06-11.jpg

Figure 6-11. The three parts of the vector LED

You start coding in the drawing program because here you define the size, colors, gradients and positions of your control. The big advantage of using a drawing program is the direct visual feedback that you get by changing parameters like color, size, and position.

In JavaFX there’s not only one way of creating a custom control but many. Here is a list of valid approaches:

  • Customize the CSS of an existing control
  • Extend an existing control
  • Extend the Region node
  • Create a control, a skin, and a CSS file
  • Use the Canvas node

In this book we will focus on the approach that extends a Region and uses CSS. To get an idea of how to create the same LED control by using the Canvas node or by using a separate Control. class, Skin. class, and a CSS file, look at the GitHub repository at http://github.com/HanSolo/JFX8CustomControls.

Structure of the LED Custom Control Example Code

Usually I structure the code in my controls in the following way:

  • Constructor
  • Initialization:
    • An init() method defines the initial size.
    • An initGraphics() method sets up the Scene graph of the control.
    • A registerListeners() method hooks up listeners to properties.
  • A Methods block contains the get-, set- and property-methods.
  • A Resizing block contains methods to resize and redraw (if needed) the control.

The Properties of the LED Control

The LED control will contain the logic of the control (its properties) and the visualization code. Because the LED is a very simple control we do not need many properties. For this example we will use five properties:

  • On (Boolean property for current state)
  • Blinking (Boolean property to switch on/off blinking)
  • Interval (long property to define the blink interval)
  • FrameVisible (Boolean property to switch on/off the metal socket)
  • LedColor (object property of type Color to define the color of the LED)

We will write code for get-, set- and property methods for these properties as shown in Listing 6-5.

Listing 6-5. Property Manipulation Code

public final boolean isOn() {
    return null == on ? false : on.get();
}
public final void setOn(final boolean ON) {
    onProperty().set(ON);
}
public final BooleanProperty onProperty() {
    if (null == on) {
         on = new BooleanPropertyBase(false) {
            @Override protected void invalidated() {
                pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
            }
            @Override public Object getBean() { return this; }
            @Override public String getName() { return "on"; }
        };
    }
    return on;
}
 
public final boolean isBlinking() {
    return null == blinking ? false : blinking.get();
}
public final void setBlinking(final boolean BLINKING) {
    blinkingProperty.set(BLINKING);
}
public final BooleanProperty blinkingProperty() {
    if (null == blinking) {
        blinking = new BooleanPropertyBase() {
            @Override public void set(final boolean BLINKING) {
                super.set(BLINKING);
                if (BLINKING) {
                    timer.start();
                } else {
                    timer.stop();
                    setOn(false);
                }
            }
            @Override public Object getBean() {
                return Led.this;
            }
            @Override public String getName() {
                return "blinking";
            }
        };
    }
    return blinking;
}

public final long getInterval() {
    return null == interval ? 500_000_000l : interval.get();
}
public final void setInterval(final long INTERVAL) {
    intervalProperty().set(INTERVAL);
}
public final LongProperty intervalProperty() {
    if (null == interval) {
        interval = new LongPropertyBase() {
            @Override public void set(final long INTERVAL) {
                super.set(clamp(SHORTEST_INTERVAL, LONGEST_INTERVAL, INTERVAL));
            }
            @Override public Object getBean() {
                return Led.this;
            }
            @Override public String getName() {
                return "interval";
            }
        };
    }
    return interval;
}
 
public final boolean isFrameVisible() {
    return null == frameVisible ? true : frameVisible.get();
}
public final void setFrameVisible(final boolean FRAME_VISIBLE) {
    frameVisibleProperty().set(FRAME_VISIBLE);
}
public final BooleanProperty frameVisibleProperty() {
    if (null == frameVisible) {
        frameVisible = new SimpleBooleanProperty(this, "frameVisible", _frameVisible);
    }
    return frameVisible;
}
 
public final Color getLedColor() {
    return null == ledColor ? Color.RED : ledColor.get();
}
public final void setLedColor(final Color LED_COLOR) {
    ledColorProperty().set(LED_COLOR);
}
public final ObjectProperty<Color> ledColorProperty() {
    if (null == ledColor) {
        ledColor = new SimpleObjectProperty<>(this, "ledColor", Color.RED);
    }
    return ledColor;
}

The Initialization Code of the LED Control

The first thing we have to do in the constructor of the LED control is  load the corresponding CSS file and add the main style class as follows:

public Led() {
    getStylesheets().add(getClass().getResource("led.css").toExternalForm());
    getStyleClass().add("led");

Then we simply initialize the AnimationTimer that we will use to make the LED control blink:

lastTimerCall = System.nanoTime();
timer         = new AnimationTimer() {
    @Override public void handle(final long NOW) {
        if (NOW > lastTimerCall + getInterval()) {
            setOn(!isOn());
            lastTimerCall = NOW;
        }
    }
};

The last thing we have to do in the constructor is to call these methods to initialize the size of the control, initialize the Scene graph, and register some listeners:

init();
initGraphics();
registerListeners();

To make sure that our LED control will be correctly sized during initialization we set the minimum, preferred, and maximum sizes in the init() method, which looks as follows:

private void init() {
    if (Double.compare(getWidth(), 0) <= 0 || Double.compare(getHeight(), 0) <= 0 ||
        Double.compare(getPrefWidth(), 0) <= 0 || Double.compare(getPrefHeight(), 0) <= 0) {
        setPrefSize(PREFERRED_SIZE, PREFERRED_SIZE);
    }
    if (Double.compare(getMinWidth(), 0) <= 0 || Double.compare(getMinHeight(), 0) <= 0) {
        setMinSize(MINIMUM_SIZE, MINIMUM_SIZE);
    }
    if (Double.compare(getMaxWidth(), 0) <= 0 || Double.compare(getMaxHeight(), 0) <= 0) {
        setMaxSize(MAXIMUM_SIZE, MAXIMUM_SIZE);
    }
}

Visualization Code

The Region node is a lightweight JavaFX container that can contain other nodes and be styled by CSS. By extending the Region node our custom control will contain the logic of the control and also the visualization code. In the initGraphics() method shown in Listing 6-6, the code sets up the Scene graph for the control by creating the nodes that are needed and applying the appropriate CSS styles to them.

Listing 6-6. Setting Up the LED Control Scene Graph

private void initGraphics() {
    // Create the node for the metal socket
    frame = new Region();
    frame.getStyleClass().setAll("frame");
    frame.setOpacity(isFrameVisible() ? 1 : 0);
 
    // Create the node for the main LED plastic body
    led = new Region();
    led.getStyleClass().setAll("main");
    led.setStyle("-led-color: " + (getLedColor()).toString().replace("0x", "#") + ";");
 
    // Create the inner shadow effect for the main LED body
    innerShadow = new InnerShadow(BlurType.TWO_PASS_BOX,
                                  Color.rgb(0, 0, 0, 0.65),
                                  8, 0d, 0d, 0d);
 
    // Create the drop shadow effect for the main LED body (the glow effect)
    glow = new DropShadow(BlurType.TWO_PASS_BOX,
                          getLedColor(),
                          20, 0d, 0d, 0d);
    glow.setInput(innerShadow);
 
    // Create the node for the highlight effect on the main LED body
    highlight = new Region();
    highlight.getStyleClass().setAll("highlight");
 
    // Add all nodes to the Scene graph of this control
    getChildren().addAll(frame, led, highlight);
}

So we have created each node that we need for our LED control and applied the appropriate style from the CSS file.

The LED Control CSS File

Each node that is created in the initGraphics() method gets its own CSS style class, which can be found in the led.css file. It looks like this:

/* The main led style class where the -led-color variable is defined */
.led {
    -led-color  : red;
    -frame-color: linear-gradient(from 14% 14% to 84% 84%,
                                  rgba(20, 20, 20, 0.64706) 0%,
                                  rgba(20, 20, 20, 0.64706) 15%,
                                  rgba(41, 41, 41, 0.64706) 26%,
                                  rgba(200, 200, 200, 0.40631) 85%,
                                  rgba(200, 200, 200, 0.3451) 100%);
}
 
/* The .frame sub-class, which defines the fill for the metal socket */
.led .frame {
    -fx-background-color : -frame-color;
    -fx-background-radius: 1024;
}
 
/* The .main sub-class, which defines the fill for the LED plastic body when it's off */
.led .main {
    -fx-background-color : linear-gradient(from 15% 15% to 83% 83%,
                                           derive(-led-color, -80%) 0%,
                                           derive(-led-color, -87%) 49%,
                                           derive(-led-color, -80) 100%);
    -fx-background-radius: 1024;
}
 
/* The .main sub class with pseudo-class :on that defines the fill for the LED plastic
   body when it's on
 */
.led:on .main {
    -fx-background-color: linear-gradient(from 15% 15% to 83% 83%,
                                          derive(-led-color, -23%) 0%,
                                          derive(-led-color, -50%) 49%,
                                          -led-color 100%);
}
 
/* The .highlight sub-class that defines the fill of the highlight effect */
.led .highlight {
    -fx-background-color : radial-gradient(center 15% 15%, radius 50%,
                                           white 0%,
                                           transparent 100%);
    -fx-background-radius: 1024;
}

Because in CSS we can use percentage to define positions in gradients, we don’t have to take care about the real size of the control to calculate the start and stop positions of the gradients. That is a huge advantage compared to Java Swing, where we had to calculate all these values every time the size of the control changed. So all these calculations will be done automatically by JavaFX. That reduces our resizing code a lot as you will see.

Resizing the LED Control

In JavaFX the size of the layout container determines the size of its children, so we have to ensure that our control is resized by its layout container. That means if we put our LED control in a StackPane (which resizes its children relative to its own size), the LED will be sized the same as the StackPane. Therefore we hook up listeners to the widthProperty() and heightProperty() of our control in the registerListeners() method.

private void registerListeners() {
    widthProperty().addListener(observable -> resize());
    heightProperty().addListener(observable -> resize());
    frameVisibleProperty().addListener(observable ->
        frame.setOpacity(isFrameVisible() ? 1 : 0));
    onProperty().addListener(observable -> led.setEffect(isOn() ? glow : innerShadow));
    ledColorProperty().addListener(observable -> {
        led.setStyle("-led-color: " + (getLedColor()).toString().replace("0x", "#") + ";");
        resize();
    });
}

In this method we hooked up listeners to all properties that might have an effect on either visualization or size of the LED control. When the listeners for the width and height are triggered, they will call the resize() method, and that will take care of sizing all nodes in our control.

private void resize() {
    double width  = getWidth();
    double height = getHeight();
    
    // Determine the smallest dimension of our control
    size = width < height ? width : height;
 
    // Resize only if the current width and height > 0
    if (width > 0 && height > 0) {

        // Center the control if the parents dimensions are not square
        if (width > height) {
            setTranslateX(0.5 * (getWidth() - size));
        } else if (getHeight() > getWidth()) {
            setTranslateY(0.5 * (getHeight() - size));
        }
 
        // Set the radius of the inner shadow to 7% of the current size
        innerShadow.setRadius(0.07 * size);
    
        // Set the radius of the drop shadow to 36% of the current size and set the glow color
        glow.setRadius(0.36 * size);
        glow.setColor(getLedColor());
 
        // Make the metal frame as big as the current size
        frame.setPrefSize(size, size);
    
        // Set the size of the LED plastic body to 72% of the current size and center it
        led.setPrefSize(0.72 * size, 0.72 * size);
        led.relocate(0.14 * size, 0.14 * size);
        
        // Set the right effect dependent on the current state of the LED
        led.setEffect(isOn() ? glow : innerShadow);
 
        // Set the size of the highlight to 58% of the current size and center it
        highlight.setPrefSize(0.58 * size, 0.58 * size);
        highlight.relocate(0.21 * size, 0.21 * size);
    }
}

As you can see, in the resize() method the only thing we have to do for each node is the calculation of its size and the relocation of the node. So first we calculate the minimum dimension of our LED (because it’s square, we take the width if it’s less than the height or vice versa). In addition we make sure that the resizing will be done only if the current size is larger than 0.

How It Works

Now that we have all things in place we can simply use our control like any other control, as shown here:

@Override public void start(Stage stage) {
    Led control = new Led();
 
    StackPane pane = new StackPane();
    pane.getChildren().add(control);
 
    Scene scene = new Scene(pane);
 
    stage.setTitle("JavaFX Led Control");
    stage.setScene(scene);
    stage.show();
 
    control.setBlinking(true);
}

Figure 6-12 shows the result.

9781430264606_Fig06-12.jpg

Figure 6-12. The Region-based custom LED Control

Other Ways to Create a Custom Control

As I mentioned earlier, there are different ways to create a custom control in JavaFX. The “extend Region” approach that was shown here is the most common one. The drawback that comes with this approach is that we mixed up the model/controller with the view, which is acceptable for small controls like our LED, but it might be a bad solution for a bigger control that relies on a data model or controls that are part of a controls library.

For those more complex controls, you should use a custom Control.class in combination with a custom Skin.class and your CSS file. The main difference here is that the properties with their get, set, and property methods will be placed in the Control.class and the visualization code will be placed in the Skin.class. This approach provides a proper separation between the controller logic and the view logic. The last approach to creating a custom control that I would like to mention is to use the Canvas node. The Canvas node represents a single node that in principle behaves like an image which you can draw on. The API that is used to draw on the Canvas node is taken from the HTML5 Canvas, which means you won’t have JavaFX nodes  but you will simply draw on the Canvas node. (That is, you’ll be working in immediate mode rather than retained mode). You have to take care of clearing and redrawing the Canvas node surface. This approach might be useful for controls that contain very complex drawings to calculate and draw, as you would only need to do so once.

Summary

In this chapter you learned how to style your application with custom CSS files by using the setUserAgentStylesheet(String URL) and getStylesheets().add(String URL) methods. Then you saw how CSS is used in JavaFX by using the default CSS selectors or creating your own. You learned about the id type selectors, class type selectors, and CSS pseudo-classes. In addition, you learned how to use the selectors in different selector patterns. Next you took a short look at the Scene Builder and how to load FXML into a scene. Finally, you learned about custom controls in JavaFX and how create them by extending a Region node.

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

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