Learn to use the layout features of JavaFX 2.0 to make nodes in your scene appear where you want them to appear

JavaFX 2.0 is an API and runtime for creating rich Internet applications (RIA). JavaFX was launched in 2007 and version 2.0 was released in October 2011. One advantage of JavaFX 2.0 is that you can use mature, familiar tools to write code in the Java language. This article focuses on how to use the layout features of JavaFX 2.0 to make the nodes in the scene appear where you want them to appear and adjust to the appropriate size when the window is resized.

JavaFX comes with its own layout classes, as shown in Figure 1 (from Amy Fowler's presentation at JavaOne 2011), designed to facilitate user interface layout for any type of platform and any size scene. These classes are located in the javafx.scene.layout package. This article contains an example that uses the BorderPane and HBox classes and other layout-related JavaFX classes to create a common UI layout format.

Figure 1: JavaFX layout class

All layout classes are responsible for controlling the position of the child nodes they manage  (nodes can optionally be set to unmanaged). Additionally, most layout classes can also resize their resizable child nodes under certain circumstances . In Step 1, we will further discuss the behavior of resizable points.

Table 1 contains a brief description of each layout class from left to right at the bottom of Figure 1 and its behavior related to resizing its child nodes.

Table 1: Layout classes and their child node resizing behavior

Layout class

Description and child node resizing behavior

AnchorPane

Allows positioning child node edges to the edges of its parent node. Child nodes are not resized.

BorderPane

Provides classic top, left, right, bottom, center child node placement. The top and bottom child nodes are resized horizontally, the left and right child nodes are resized vertically, and the center node is resized both horizontally and vertically. All resizing works up to the maximum size of the node in the relevant direction.

StackPane

Switch child nodes from background to foreground. Child nodes are resized to fill the parent node size (up to the maximum width and height of each node).

HBox

Contains a single row of nodes. Child nodes are sized horizontally to their preferred width, but each child node can be explicitly set to increase horizontally to its maximum width. By default, child nodes are sized vertically to their maximum height.

VBox

Contains a single column of nodes. Child nodes are sized vertically to their preferred height, but each child node can be explicitly set to increase vertically to its maximum height. By default, child nodes are sized horizontally to their maximum width.

TilePane

Provides a horizontally or vertically uniform "tiled" flow of line wraps. Child nodes are resized to fill the size of the tile (up to the node's maximum width and height).

FlowPane

Provides a wrapped horizontal or vertical stream of child nodes. Child nodes are not resized.

GridPane

Place child nodes in a flex grid, which is great for complex layouts. Sizing is based on explicitly set constraints in a given row or column.

 

We will discuss the behavior of resizable points later in step 1.

LayoutSansTearsSolution Application Overview

To help you understand how to lay out a UI in JavaFX, a sample application called LayoutSansTearsSolution will be used below. As shown in Figure 2, the application's UI consists of a header area, a tabbed pane area, and a footer area, and as the user would expect, its appearance changes as the window is resized.

The LayoutSansTearsExercise project you will download in the next section  contains the initial code for this sample application. The current runtime appearance of the application is shown in Figure 6. As this article progresses, you will modify its code to achieve the appearance of the LayoutSansTearsSolution application layout shown in Figure 2.

Figure 2: Screenshot of LayoutSansTearsSolution  application

When the LayoutSansTearsSolution application window is resized (as shown in Figure 3), the relative position of each node is adjusted accordingly. For example, the Footer Left and Footer Right labels will be close to each other, and the App Title header text will remain horizontally centered.

 Figure 3: Screenshot of LayoutSansTearsSolution after window resize 

Additionally, the tabbed pane in the center of the window resizes to take up all available horizontal space, plus any space unused by headers and footers.

Get and run the LayoutSansTearsExercise project

  • Download  the NetBeans project files (Zip) , which include the LayoutSansTearsExercise project and the LayoutSansTearsSolution project.
  • Extract the LayoutSansTearsExercise project into a directory of your choice.
  • Start NetBeans and select  File -> Open Project .
  • In the Open Project dialog box, go to the selected directory and open the LayoutSansTearsExercise project, as shown in Figure 4. If you receive a message stating that the jfxrt.jar file cannot be found, click  the Resolve  button and go to the rt/lib folder under the JavaFX 2.0 SDK installation directory.

Note : You can obtain NetBeans IDE from  the NetBeans website .

Figure 4: Open the LayoutSansTearsExercise  project in NetBeans 

  • To run the application, click  the Run Project  icon on the toolbar or press  the F6  key. The Run Project icon looks like the Play button on a media (for example, DVD) player, as shown in Figure 5.

Figure 5: Running the application in NetBeans

The LayoutSansTearsExercise application should appear in a window, as shown in Figure 6.

Figure 6:  Screenshot of the LayoutSansTearsExercise application

Notice that the App Title text, search text area, and Go button are crowded together to the right of the header. Additionally, the search text area appears stretched vertically, but we want it to be only tall enough to enter a few lines of text. Additionally, the Footer Left and Footer Right tags are crowded together on the left side of the footer.

Your task is to add some code to achieve the layout appearance and behavior described previously (shown in Figures 2 and 3). Let's discuss the steps you can take to achieve this look and behavior.

Step 1: Review the layout strategies used in the LayoutSansTearsExercise application

The top-level layout strategy used in the LayoutSansTearsExercise application is to place a BorderPane at the scene root. When you use a layout class or any other resizable class as the root node of a scene, the node will automatically resize as the scene is resized. Therefore, when the user resizes the application window (JavaFX Stage), the BorderPane will resize to take up the entire space within the window.

In addition to creating the BorderPane, create an HBox to hold the nodes in the header and place it on top of the BorderPane. Then create a TabPane and center it on the BorderPane. Create another HBox to hold the nodes in the footer and place it at the bottom of the BorderPane.

Let's take a look at the code in Listing 1, which shows the initial code for the sample application, located in the LayoutSansTears.java  file.

package javafxpert.layoutsanstears.ui;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class LayoutSansTears extends Application {

  public static void main(String[] args) {
    Application.launch(args);
  }

  @Override
  public void start(Stage primaryStage) {
    Region headerLeftSpring = new Region();

    // TO DO: Declare a variable of type Region, assigning a
    // new Region object to it for use as a "spring" in the
    // header between the title of the app and the search box

    // TO DO: Declare a variable of type Region, assigning a
    // new Region object to it for use as a "spring" in the
    // footer between the left and right labels

    ImageView logo = new ImageView(
      new Image(getClass().getResourceAsStream("images/javafx-logo.png"))
    );

    HBox searchBox = HBoxBuilder.create()
      .spacing(5)
      .children(
        TextAreaBuilder.create()
          .prefWidth(120)
          .prefHeight(40)

          // TO DO: Use a method of the TextAreaBuilder to set the maximum
          // height of the TextArea to its preferred size

          .build(),
        ButtonBuilder.create()
          .text("Go")
          .build()
      )
      .build();

    Scene scene = SceneBuilder.create()
      .stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
      .width(800)
      .height(500)
      .root(
        BorderPaneBuilder.create()
          .top(
            HBoxBuilder.create()
              .children(
                logo,
                headerLeftSpring,
                LabelBuilder.create()
                  .id("app-title")
                  .text("App Title")
                  .build(),

                // TO DO: Insert the Region object created to act as a "spring"

                searchBox
              )
              .build()
          )
          .center(
            TabPaneBuilder.create()
              .tabs(
                TabBuilder.create()
                  .text("Tab A")
                  .build(),
                TabBuilder.create()
                  .text("Tab B")
                  .build(),
                TabBuilder.create()
                  .text("Tab C")
                  .build()
              )
              .build()
          )
          .bottom(
            HBoxBuilder.create()
              .id("footer")
              .children(
                new Label("Footer Left"),

                // TO DO: Insert the Region object created to act as a "spring"

                new Label("Footer Right")
              )
              .build()
          )
          .build()
      )
      .build();

    HBox.setHgrow(headerLeftSpring, Priority.ALWAYS);

    // TO DO: Use a static method of HBox to allow the headerRightSpring
    // to compete for any extra horizontal space

    // TO DO: Use a static method of HBox to allow the footerCenterSpring
    // to compete for any extra horizontal space

    // TO DO: Use a static method of HBox to give the searchBox
    // a margin of 20 pixels on the top and 10 pixels on the right

    primaryStage.setTitle("Layout Sans Tears: Exercise");
    primaryStage.setScene(scene);
    primaryStage.show();
  }
}

Listing 1: LayoutSansTears.java

Using the SceneBuilder and BorderPaneBuilder classes

The initial code in LayoutSansTears.java takes advantage of the builder classes in the JavaFX 2.0 API , including the SceneBuilder  and BorderPaneBuilder  classes  shown in Listing 2 . These two classes create instances of Scene and BorderPane respectively.

Note : There are some "TO DO" ​​comments in the code (which you will fill in later in step 2) that are omitted from Listing 2.

Scene scene = SceneBuilder.create()
      .stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
      .width(800)
      .height(500)
      .root(
        BorderPaneBuilder.create()
          .top(
            HBoxBuilder.create()
              .children(
                logo,
                headerLeftSpring,
                LabelBuilder.create()
                  .id("app-title")
                  .text("App Title")
                  .build(),
                searchBox
              )
              .build()
          )
          .center(
            TabPaneBuilder.create()
              .tabs(
                TabBuilder.create()
                  .text("Tab A")
                  .build(),
                TabBuilder.create()
                  .text("Tab B")
                  .build(),
                TabBuilder.create()
                  .text("Tab C")
                  .build()
              )
              .build()
          )
          .bottom(
            HBoxBuilder.create()
              .id("footer")
              .children(
                new Label("Footer Left"),
                new Label("Footer Right")
              )
              .build()
          )
          .build()
      )
      .build();

 

Listing 2: Creating   instances of Scene  and  BorderPane

In Listing 2, you use the HBoxBuilder class to create a horizontal container in which the header's nodes are placed. These nodes (shown in Listing 1) are:

  • The JavaFX logo, the ImageView shown in Listing 1
  • A "spring" node, detailed later in step 2
  • Application title
  • A search box, including the TextArea and Button shown in Listing 1

Listing 2 also uses the HBoxBuilder class to create another horizontal container in which to place the Footer Left and Footer Right tags.

The JavaFX API provides a number of builder classes designed to support a declarative programming style for creating objects and setting object properties. For example, as you just experienced, the SceneBuilder class in Listing 2 creates an instance of the Scene class and fills it with properties such as the desired scene width and height. As you can see in Listing 1, this application also uses other builder classes: HBoxBuilder, TextAreaBuilder, ButtonBuilder, BorderPaneBuilder, LabelBuilder, TabPaneBuilder, and TabBuilder.

Note : Although builder classes are used in this example, the application could have been written in a more procedural style or expressed in FXML without using them. The "See Also" section at the end of this article contains a link you can visit to learn about FXML.

Understand the behavior of resizable points

All nodes in LayoutSansTears.java are resizable through the parent layout container . When a node is resizable, its parent layout container will resize it during layout, usually to its preferred size. For the same reason, applications never set the size of resizable points directly. Except for subclasses of Text, ImageView, Group, and Shape, all Node subclasses in JavaFX can be resized through the parent layout container. In order to change the size of these non-sizable  classes, the application must set the size directly (except for Groups, which are sized using the collection bounds of their children).

Now that you understand the sizing point behavior and layout behavior described in Table 1, let's take a look at the behavior of the LayoutSansTears.java application (in the LayoutSansTearsExercise project) based on your understanding.

For example, when the user resizes the Stage vertically, the BorderPane will also resize because we assigned it to the root of the scene. When a BorderPane is resized vertically, it does not attempt to resize its top and bottom nodes vertically, so these nodes maintain their preferred height. Therefore, the node at the center of the BorderPane (i.e. the TabPane) is sized up to its maximum height. You may ask, how much vertically can a TabPane grow? Or, put another way, what is the maximum height of a TabPane? To answer this question, you need to understand bounded  nodes and unbounded  nodes. Below we discuss these two types of nodes.

Understand bounded and unbounded nodes

A bounded  node is a resizable point whose maximum height is the same as the preferred height and whose maximum width is the same as the preferred width. For example, as described in Table 1, when an HBox attempts to resize a child node, it can only resize it to its maximum height. Therefore, bounded nodes can only be resized up to their preferred width and height.

Unbounded  nodes are resizable points whose maxWidth() and maxHeight() return Double.MAX_VALUE. Therefore, there are no width and height restrictions when the layout container resizes unbounded nodes.

Resizable points can be bounded in one direction and unbounded in another. For example, by default, a MenuBar has an unlimited width and a limited height. This allows the MenuBar to be resized horizontally to the width required by the layout container while maintaining its preferred height. Table 2 contains the default bounded and unbounded characteristics of resizable classes.

Table 2: Resizable classes and their default bounded/unbounded characteristics

Bounded

Unbounded

Horizontal unbounded

vertical unbounded

Button

Region (superclass of all layouts)

MenuBar

Separator (vertical)

Label

ListView

ToolBar

ScrollBar (vertical)

ChoiceBox

TreeView

Separator (horizontal)

Hyperlink

TableView

ScrollBar (horizontal)

ProgressBar

TabPane

TextField

Slider

SplitPane

ScrollPane

TextArea

To the previous question "What is the maximum height of a TabPane?", the answer is that its maximum height is unbounded. Take a moment to do some more experiments resizing the application window to verify that the node locations and sizes shown in Figure 6 are consistent with your understanding.

For example, the TextArea in the upper right side of the UI has its preferred height set to 40 pixels, but its appearance is much taller than that. This is because the TextArea is unbounded by default, so the HBox in Listing 3 resizes the TextArea to the height of the HBox:

Scene scene = SceneBuilder.create()
      .stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
      .width(800)
      .height(500)
      .root(
        BorderPaneBuilder.create()
          .top(
            HBoxBuilder.create()
              .children(
                logo,
                headerLeftSpring,
                LabelBuilder.create()
                  .id("app-title")
                  .text("App Title")
                  .build(),
                searchBox
              )
              .build()
          )
          .center(
            TabPaneBuilder.create()
              .tabs(
                TabBuilder.create()
                  .text("Tab A")
                  .build(),
                TabBuilder.create()
                  .text("Tab B")
                  .build(),
                TabBuilder.create()
                  .text("Tab C")
                  .build()
              )
              .build()
          )
          .bottom(
            HBoxBuilder.create()
              .id("footer")
              .children(
                new Label("Footer Left"),
                new Label("Footer Right")
              )
              .build()
          )
          .build()
      )
      .build();

Listing 3:  Resize TextArea  to  HBox  height

As hinted in Listing 3, we'll let you do the work of the "TO DO" ​​annotation. The goal is to fine-tune the layout from the LayoutSansTearsExercise look shown in Figure 6 to the LayoutSansTearsSolution look shown in Figure 3.

Step 2: Fine-tune the layout with default max size overrides, growth limits, and margins

In this step, you use three methods to fine-tune the layout of the sample application:

  • Override the default maximum size value
  • Set horizontal growth limits for each child node of HBox
  • Sets margins around individual child nodes in a layout container
Override the default maximum size of resizable points

To make the height of the TextArea in Listing 3 bounded rather than unbounded, its maximum height needs to be set to its preferred height. For this purpose, it is recommended to use a constant called USE_PREF_SIZE defined in the Region class.

To implement this in our example, we continue by adding the following method call to TextAreaBuilder as shown in Listing 3:

          .maxHeight(Region.USE_PREF_SIZE)

When you run the application, the TextArea should now appear at its preferred height, as shown previously in Figure 2.

If you later want to reset the maximum height of a TextArea (or any other resizable point) to its default value, you can pass the Region.USE_COMPUTED_SIZE constant as a parameter to TextArea's setMaxHeight() method.

Table 3 contains methods for resizable points that can be used to make the node's width, height, or both bounded, unbounded, or reset.

Table 3: Make resizable points bounded, unbounded, or reset to their default values

Bounded

Unbounded

reset to default

setMaxWidth()

Region.USE_PREF_SIZE

Double.MAX_VALUE

Region.USE_COMPUTED_SIZE

setMaxHeight()

Region.USE_PREF_SIZE

Double.MAX_VALUE

Region.USE_COMPUTED_SIZE

setMaxSize()

Region.USE_PREF_SIZE, Region.USE_PREF_SIZE

Double.MAX_VALUE, Double.MAX_VALUE

Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE

Another way to make the TextArea appear in an HBox at its preferred height is to set the HBox's fillHeight property to false. This allows all child nodes to be displayed at their preferred height. This method works for this sample program, but be aware that this will apply to all child nodes in the HBox  , not to a single node like the HBox.maxHeight() method.

Now that the height problem of the TextArea shown in Figure 6 is solved, let's solve the problem of nodes in the header being crowded together on the right side.

Set horizontal growth limits for each child node of HBox

The code in Listing 4 implements the header area shown in Figure 6

Scene scene = SceneBuilder.create()
      .stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
      .width(800)
      .height(500)
      .root(
        BorderPaneBuilder.create()
          .top(
            HBoxBuilder.create()
              .children(
                logo,
                headerLeftSpring,
                LabelBuilder.create()
                  .id("app-title")
                  .text("App Title")
                  .build(),
                searchBox
              )
              .build()
          )
          .center(
            TabPaneBuilder.create()
              .tabs(
                TabBuilder.create()
                  .text("Tab A")
                  .build(),
                TabBuilder.create()
                  .text("Tab B")
                  .build(),
                TabBuilder.create()
                  .text("Tab C")
                  .build()
              )
              .build()
          )
          .bottom(
            HBoxBuilder.create()
              .id("footer")
              .children(
                new Label("Footer Left"),
                new Label("Footer Right")
              )
              .build()
          )
          .build()
      )
      .build();

Listing 4: Implementing the header area

It looks like we're going to have to work for you again, this time inserting a horizontal "spring" into the HBox so that more child nodes can be spread out. As shown in Listing 4, we have inserted such a spring between the logo and the label, which is why the two of them are able to spread out (as shown in Figure 6).

To implement the spring, we first declare a variable of type Region called headerLeftSpring and assign it a new Region object. As shown in Listing 4, to enable the headerLeftSpring node to grow horizontally in the HBox, we use the HBox's static setHgrow() method. Passing the Priority.ALWAYS parameter means that we want the node to occupy all available horizontal space and share this space with other nodes with a horizontal growth limit of ALWAYS. The other constants in the Priority enumeration are SOMETIMES and NEVER, which can be used to further control the growth behavior.

Continue implementing a spring between the application title and the search box by inserting code at the location indicated in the previous listing. Then run the application and you can see that the App Title label is centered horizontally in the header because the two springs share all available horizontal space. The application should look similar to Figure 7.

Figure 7: LayoutSansTearsExercise with step 2 partially completed 

BTW, you can use the above method to insert a vertical spring into a VBox by using the VBox's static setVgrow() method. Additionally, the GridPane class also has static setHgrow() and setVgrow() methods for GridPane layout.

Now that you've resolved the horizontal white space issue in the header shown in Figure 6, let's set the margins for the search box so that it doesn't touch the upper right corner.

Sets margins around individual child nodes in a layout container

The code in Listing 5 implements the search box shown in Figure 6.

HBox searchBox = HBoxBuilder.create()
      .spacing(5)
      .children(
        TextAreaBuilder.create()
          .prefWidth(120)
          .prefHeight(40)
          .build(),
        ButtonBuilder.create()
          .text("Go")
          .build()
      )
      .build();

    ...code omitted...

            HBoxBuilder.create()
              .children(
                logo,
                headerLeftSpring,
                LabelBuilder.create()
                  .id("app-title")
                  .text("App Title")
                  .build(),
                searchBox
              )
              .build()

    ...code omitted...

    // TO DO: Use a static method of HBox to give the searchBox
    // a margin of 20 pixels on the top and 10 pixels on the right

 

Listing 5: Setting margins

There are several ways to achieve the desired top 20 pixels and right 10 pixels of padding in the search box. This is accomplished in our sample application by adding a padding() method call to the HBoxBuilder that creates the search box, as shown in the following code snippet:

    HBox searchBox = HBoxBuilder.create()
      .padding(new Insets(20, 10, 0, 0))
      ...code omitted...
      .build()

Another, more general approach to implementing margins around the children of a layout container is to use the static setMargin() method of its layout class. To implement this in the example, we add the following line of code at the location shown in Listing 5.

    HBox.setMargin(searchBox, new Insets(20, 10, 0, 0));

When you run the application, the search box should now be 20 pixels from the top and 10 pixels from the right, as shown previously in Figure 2.

Now that you've solved all the white space issues in the header, let's use JavaFX CSS to make the footer look like Figure 2.

Step 3: Modify the layout using JavaFX CSS

A very powerful aspect of JavaFX is the ability to dynamically style nodes (including layout container nodes) in a scene using CSS. The newly displayed footer area in Figure 7 is implemented with the code shown in Listing 6 (a snippet from Listing 1) and the CSS document shown in Listing 7.

Scene scene = SceneBuilder.create()
      .stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
      ...code omitted...

            HBoxBuilder.create()
              .id("footer")
              .children(
                new Label("Footer Left"),

                // TO DO: Insert the Region object created to act as a "spring"

                new Label("Footer Right")
              )
              .build()

Listing 6: Code snippet from Listing 1

/*
 * myStyles.css - style sheet for LayoutSansTears.java JavaFX layout example
 */

#footer {
  -fx-border-color: grey;
  -fx-border-width: 1;

  /* TO DO: Insert a property and value that will give the interior of
     the layout container a padding of 5 pixels on every side */

}

#app-title {
  -fx-font-size: 40pt;
  -fx-font-weight: normal;
  -fx-text-fill: grey;
}

Listing 7: myStyles.css

Modify layout using CSS stylesheet properties

As shown in Listing 6, we associate a stylesheet with the JavaFX application by using the stylesheets() method of the SceneBuilder class.

The HBox containing the tags in the footer takes advantage of the style properties and values ​​of the #footer selector in Listing 7 by using the id() method in the HBoxBuilder. Therefore, the HBox will be rendered with the border color and width specified in the #footer selector, as shown in Figure 7.

As mentioned before, there are multiple ways to set margins around child nodes in a layout container. What we're doing here with the footer is modifying the style sheet by adding the following line of code at the location marked by the #footer selector in Listing 7:

-fx-padding: 5;

The –fx-padding property causes the interior of the layout container to be padded with 5 pixels of whitespace at its top, right, bottom, and left sides. Alternatively, if you want these blank-filled values ​​to be different from each other, you can provide four space-separated values.

Continue to implement the fill-in-the-blank modification. Once completed, note that there is one more thing left unfinished: implementing a spring between the two tags in the footer. You just do it the same way you did when implementing the spring in the header.

Running the LayoutSansTearsExercise project should now produce the same appearance as running the LayoutSansTearsSolution project shown in Figure 2.

Summarize

JavaFX has some very powerful features for laying out user interfaces, some of which we have discussed in this article and demonstrated in the LayoutSansTearsSolution application. With these features, you can make your application appear the way you want it to appear, regardless of scene size or platform type. It will be helpful to understand the behavior of each type of layout class and concepts such as bounded and unbounded nodes to help you make your UI look exactly the way you want it to.

See also

About the author

Jim Weaver is an independent Java and JavaFX developer, author, and speaker active in promoting rich-client Java and JavaFX as the technologies of choice for new application development.

Jim's authored books include "Inside Java," "Beginning J2EE," and "Pro JavaFX Platform," which has been updated to cover JavaFX 2.0. His professional background includes 15 years as an EDS systems architect and the same number of years as an independent developer and software development company owner. Jim has spoken at many international software technology conferences, including JavaOne 2011 in San Francisco and Sao Paulo and the keynote address at O'Reilly OSCON/Java 2011 in Portland, Oregon.

Jim blogs at  http://javafxpert.com , tweets at @javafxpert, and can be contacted by email at jim.weaver[at]javafxpert.com.

Category: JavaFX

Guess you like

Origin blog.csdn.net/weiweiqiao/article/details/133308692