Wednesday, July 18, 2012


Laying Out a User Interface with JavaFX 2.0

By James L. Weaver
Learn to use the layout capabilities of JavaFX 2.0 to make nodes in the scene graph appear where you want them to be and more.
Published March 2012
Downloads:
DownloadSample Code (Zip)

Introduction

JavaFX 2.0 is an API and runtime for creating Rich Internet Applications (RIAs). JavaFX was introduced in 2007, and version 2.0 was released in October 2011. One of the advantages of JavaFX 2.0 is that the code can be written in the Java language using mature and familiar tools. This article focuses on using the layout capabilities of JavaFX 2.0 to make nodes in the scene graph appear where you want them to be and be the appropriate size as the window is resized.
JavaFX comes with its own layout classes, shown in Figure 1 (from Amy Fowler’s JavaOne 2011 presentation), whose purpose is to provide convenient ways to lay out the user interface regardless of the size of the scene or type of platform. These classes are located in thejavafx.scene.layout package. This article contains an example of using the BorderPane and HBox classes, and other layout-related JavaFX classes, to create a commonly used UI layout format.
Figure 1: JavaFX Layout Classes
All layout classes are responsible for controlling the location of their managed child nodes (a node can optionally be set as not managed). In addition, most layout classes under certain circumstances also resize their resizable child nodes. In Step 1, we’ll discuss the behavior of resizable nodes further.
Going from left to right on the bottom of Figure 1, Table 1 contains a brief description of each layout class and its behavior pertaining to resizing its children.
Table 1: Layout Classes and Their Children-Resizing Behavior
Layout Class
Description and Children-Resizing Behavior
AnchorPane
Allows child node edges to be anchored to the parent’s edges. Children are not resized.
BorderPane
Provides classic top, left, right, bottom, center child placement.  Horizontally resizes child nodes placed in the top and bottom, vertically resizes child nodes placed in the left and right, and resizes nodes placed in the center both horizontally and vertically.  All resizing is up to the node’s maximum size in the relevant directions.
StackPane
Layers children back to front. Resizes child nodes to fill the size of the parent (up to each node’s maximum width and height).
HBox
Contains a single row of nodes. Child nodes are resized horizontally to their preferred widths, but individual child nodes can be explicitly set to grow horizontally to their maximum widths. By default, child nodes are resized vertically to their maximum heights.
VBox
Contains a single column of nodes. Child nodes are resized vertically to their preferred heights, but individual child nodes can be explicitly set to grow vertically to their maximum heights. By default, child nodes are resized horizontally to their maximum widths.
TilePane
Provides a horizontal or vertical flow of uniform “tiles” that wrap. Resizes child nodes to fill the size of the tile (up to the node’s maximum width and height).
FlowPane
Provides horizontal or vertical flow of children that wrap. Children are not resized.
GridPane
Places children in a flexible grid, which is great for complex layout.  Resizing is based upon constraints explicitly set on a given row or column.
We’ll discuss the behavior of resizable nodes a bit later in Step 1.

Overview of the LayoutSansTearsSolution Application

To help you learn how to lay out the UI in JavaFX, an example application named LayoutSansTearsSolution will be employed. As shown in Figure 2, the UI for this application contains a header area, a tabbed pane area, and a footer area, and its appearance is adjusted, as the user would expect, when the window is resized.
The LayoutSansTearsExercise project that you'll download in the next section contains starter code for this example application. In its current form, the application’s runtime appearance looks like Figure 6. During the course of this article, you'll modify the code to implement the layout appearance of the LayoutSansTearsSolution application shown in Figure 2.
Figure 2: Screen Capture of the LayoutSansTearsSolution Application
When you resize the window of the LayoutSansTearsSolution application, as shown in Figure 3, the relative positions of the nodes are adjusted accordingly.  For example, the Footer Left and Footer Right labels move closer to each other, and the App Title header text stays horizontally centered.
Figure 3: Screen Capture of LayoutSansTearsSolution after Resizing the Window
In addition, the tab pane in the center of the window resizes to take all available horizontal space, as well as any space not used by the header and footer.

Obtaining and Running the LayoutSansTearsExercise Project

  • Download the NetBeans project file (Zip), which includes the LayoutSansTearsExercise project and theLayoutSansTearsSolution project.
  • Expand the LayoutSansTearsExercise project into a directory of your choice.
  • Start NetBeans, and select File -> Open Project.
  • From the Open Project dialog box, navigate to your chosen directory and open the LayoutSansTearsExercise project, as shown in Figure 4. If you receive a message stating that the jfxrt.jar file can't be found, click the Resolve button and navigate to the rt/lib folder subordinate to where you installed the JavaFX 2.0 SDK.
Note: You can obtain the NetBeans IDE from the NetBeans Website.
Figure 4: Opening 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 a 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: Screen Capture of the LayoutSansTearsExercise Application
Notice that the App Title text, search text area, and Go button are bunched together on the right side of the header. Also, the search text area appears stretched vertically but our desired height is just tall enough to allow a couple of lines of text entry. In addition, the Footer Left and Footer Right labels are bunched together on the left side of the footer. 
Your mission will be to add code that implements the layout appearance and behavior described previously and shown in Figures 2 and 3. Next, we’ll discuss the steps you can follow to implement this appearance and behavior.

Step 1: Reviewing the Layout Strategy Used in the LayoutSansTearsExercise Application

The top-level layout strategy used in the LayoutSansTearsExercise application is to place a BorderPane in the root of the scene. When a layout class, or any other resizable class, is used as the root node of the scene, that node is automatically resized as the scene is resized. Consequently, as the user resizes the application window (the JavaFX stage), the BorderPane is resized to fill all the space inside the window.
In addition to creating a BorderPane, an HBox is created to hold the nodes in the header, and it is placed in the top of theBorderPane. A TabPane is then created and placed in the center of the BorderPane.  Another HBox is then created to hold the nodes in the footer, and it is placed in the bottom of the BorderPane.
Take a look at the code in Listing 1, which shows the starter code for the example application and is located in theLayoutSansTears.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 starter code in LayoutSansTears.java makes use of builder classes in the JavaFX 2.0 API, including theSceneBuilder and BorderPaneBuilder classes shown in Listing 2.  These classes create instances of Scene andBorderPane, respectively.
Note: There are some “TO DO” comments in the code (for you to fill in later during Step 2) that have been 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 Listing2, the HBoxBuilder class is employed to create a horizontal container in which the nodes for the header are placed.  These nodes, as shown in Listing 1, are as follows:
  • The JavaFX logo, which is the ImageView shown in Listing 1
  • A “spring” node that we’ll examine later in Step 2
  • The application title
  • A search box that comprises the TextArea and Button shown in Listing 1
The HBoxBuilder class is also used in Listing 2 to create a horizontal container in which the Footer Left and Footer Right labels for the footer are placed.
There are many builder classes in the JavaFX API, and their purpose is to enable a declarative-style of programming to create and set the properties of objects. For example, as you just experienced, the SceneBuilder class in Listing 2 creates an instance of the Sceneclass, and it populates that instance with properties such as the desired width and height of the scene. As you saw in Listing 1, other builder classes used in this application are HBoxBuilderTextAreaBuilderButtonBuilder,BorderPaneBuilderLabelBuilder, TabPaneBuilder, and TabBuilder
Note: Although builder classes were used in this example, the application could have been written without them in a more procedural style or expressed in FXML.  The "See Also" section at the end of this article contains a link you can visit to learn about FXML.

Understanding the Behavior of Resizable Nodes

All the nodes in LayoutSansTears.java are resizable by a parent layout container.  When a node is resizable, its parent will resize it during layout, usually to its preferred size. By the same token, applications never set the size of a resizable node directly.  All theNode subclasses in JavaFX are resizable by a parent layout container with the exception of TextImageViewGroup, and subclasses of Shape.  In order for the size of these non-resizable classes to change, the application must set the sizes directly (except for Group, whose size assumes the collective bounds of its children).
Let’s examine the behavior of the LayoutSansTears.java application (in the LayoutSansTearsExercise project) in light of your understanding of resizable node behavior and the layout behavior described in Table 1.
For example, when the user resizes the stage vertically, the BorderPane is resized because it is assigned to the root of the scene. As the BorderPane is resized vertically, it does not attempt to vertically resize its top and bottom nodes, so they retain their preferred heights.  Therefore, the node in the center of the BorderPane (which is the TabPane) is resized up to its maximum height. The question you may be asking is, “How much can the TabPane grow vertically?” Or, stated differently, “What is the maximum height of aTabPane?” To answer that question, an understanding of clamped versus unbounded nodes is necessary, which we’ll discuss next.

Understanding Clamped Versus Unbounded Nodes

clamped node is a resizable node whose maximum height is the same as its preferred height, and its maximum width is the same as its preferred width.  For example, as described in Table 1, when an HBox attempts to resize the height of a child node, it can resize it only up to its maximum height. Consequently, a clamped node can be resized only up to its preferred width and height.
An unbounded node is a resizable node whose maxWidth() and maxHeight() return Double.MAX_VALUE. Consequently, there are no limits on the width and height to which a layout container may resize an unbounded node.
A resizable node can be clamped in one direction and unbounded in another. For example, by default, a MenuBar has an unbounded width and a clamped height. This allows it to be resized horizontally as wide as the layout container requires, while retaining its preferred height.  Table 2 contains the default clamped and unbounded characteristics of the resizable classes.
Table 2: Resizable Classes and their Default Clamped / Unbounded Characteristics
Clamped
Unbounded
Unbounded Horizontally
Unbounded Vertically
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
  
The answer to the question, “What is the maximum height of a TabPane?” is that its maximum height is unbounded.  Take a moment to experiment more with resizing the application window, validating that the positions and sizes of the nodes shown in Figure 6 are consistent with your understanding. 
For example, the preferred height of the TextArea in the upper right side of the UI is set to 40 pixels, yet its appearance is much taller than that.  This is because the TextArea is unbounded by default, so the HBox in Listing 3 resizes TextArea to the height of theHBox:
    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: Resizing TextArea to the Height of the HBox
As intimated in Listing 3, we’re going to put you to work addressing the “TO DO” comments. The goal is to fine-tune the layout from theLayoutSansTearsExercise appearance shown in Figure 6 to the LayoutSansTearsSolution appearance shown in Figure 3.   

Step 2: Fine-Tuning the Layout with Default Max Size Overrides, Grow Constraints, and Margins

In this step, you’ll fine-tune the layout of our example application using three techniques:
  • Overriding default maximum size values
  • Setting horizontal grow constraints on individual child nodes of an HBox
  • Setting margins around individual child nodes of a layout container

Overriding the Default Maximum Size of a Resizable Node

To make the height of the TextArea in Listing 3 clamped, rather than unbounded, we need to set its maximum height to its preferred height. The recommended way to do this is to use a constant defined in the Region class named USE_PREF_SIZE.
To implement this in our example, go ahead and add the following method invocation to the TextAreaBuilder where indicated in Listing 3:
          .maxHeight(Region.USE_PREF_SIZE)
When you run the application, the TextArea should now appear with its preferred height, as previously shown in Figure 2.
If you subsequently wanted to reset the maximum height of the TextArea (or any other resizable node) back to its default, you’d pass the Region.USE_COMPUTED_SIZE constant as an argument into the setMaxHeight() method of the TextArea.
Table 3 contains the methods of a resizable node that you can use to make its width, height, or both width and height clamped, unbounded, or reset.
Table 3: Making a Resizable Node Clamped, Unbounded, or Reset to its Default
 
Clamped
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 of making a TextArea appear at its preferred height in an HBox is to set the fillHeight property of the HBox tofalse. This allows all the child nodes to be their preferred heights. This approach would work in our example program, but note that it applies to all the child nodes in the HBox, not to an individual node as the HBox.maxHeight() method does.
Now that you’ve addressed the height issue of the TextArea shown in Figure 6, let’s tackle the issue in which nodes in the header are bunched up on the right side.

Setting Horizontal Grow Constraints on Individual Child Nodes of an HBox

The header area shown in Figure 6 is implemented by the code in Listing 4.
    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 put you to work again, this time inserting a horizontal "spring" into the HBox so that more of its children can spread out. As shown in Listing 4, we’ve already inserted such a spring between the logo and the label, which is why they are able to spread out as shown in Figure 6.
To implement the spring, we first declare a variable named headerLeftSpring of type Region and assign a new Regionobject to it. As shown in Listing 4, to enable the headerLeftSpring node to grow horizontally in the HBox, we used the staticsetHgrow() method of HBox.  Passing the Priority.ALWAYS argument indicates that we want the node to take all the available horizontal space, sharing it with other nodes whose horizontal grow constraint is ALWAYS. Other constants in the Priority enum are SOMETIMES and NEVER, which can be used to further control growing behavior.
Go ahead and implement a spring between the title of the application and the search box by inserting code where indicated in the previous listings. Then run the application to see that the App Title label is centered horizontally in the header due to the fact that the two springs are sharing any available horizontal space. The appearance of the application should be similar to Figure 7.
Figure 7: LayoutSansTearsExercise with Step 2 Partially Completed
By the way, you can use the technique described above to place a vertical spring into a VBox by using the static setVgrow()method of VBox.  In addition, the GridPane class has static setHgrow() and setVgrow() methods for use with theGridPane layout.
Now that you’ve addressed the horizontal spacing issues in the header shown in Figure 6, let’s give the search box a margin so that it doesn’t touch the upper right corner.

Setting Margins Around Individual Child Nodes of a Layout Container

The search box shown in Figure 6 is implemented by the code in Listing 5.
    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 a couple of ways that our desired padding of 20 pixels on the top and 10 pixels on the right can be implemented in the search box. One way to achieve this in our example application would be to add the padding() method invocation to the HBoxBuilder that creates the search box, as shown in the following snippet:
    HBox searchBox = HBoxBuilder.create()
      .padding(new Insets(20, 10, 0, 0))
      ...code omitted...
      .build()
Another more generally applicable way to implement a margin around a child node of a layout container is to use the staticsetMargin() method of its layout class. To implement this in our example, go ahead and add the following line where indicated in Listing 5.
    HBox.setMargin(searchBox, new Insets(20, 10, 0, 0));
When you run the application, the search box should now be inset 20 pixels from the top and 10 pixels from the right, as previously shown in Figure 2.
Now that you’ve addressed all the spacing issues in the header, we’ll leverage JavaFX CSS to make the footer have the appearance shown in Figure 2.

Step 3: Leveraging JavaFX CSS to Modify the Layout

A very powerful aspect of JavaFX is the ability to use CSS to dynamically style the nodes, including layout container nodes, in a scene.  The footer area shown most recently in Figure 7 is implemented by the code shown in Listing 6 (which is a snippet from Listing 1) as well as 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: 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

Using CSS Style Sheet Properties to Modify the Layout

As shown in Listing 6, a style sheet is associated with our JavaFX application by using the stylesheets() method of theSceneBuilder class. 
The HBox that contains the labels in the footer leverages the style properties and values from the #footer selector in Listing 7 through the use of the id() method in the HBoxBuilder.  Because of this, the HBox is rendered with the border color and width specified in the #footer selector, as shown most recently in Figure 7.
As demonstrated previously, there are multiple ways to put margins around child nodes in a layout container. The technique we’ll use for the footer involves modifying the style sheet by adding the following line in the #footer selector where indicated in Listing 7:
  -fx-padding: 5;
The –fx-padding property causes the interior of the layout container to be padded in its interior by 5 pixels on its top, right, bottom, and left sides. Alternatively, four space-separated values can be supplied if you want these padding values to be different from each other.
Go ahead and implement the padding modification, and while you’re at it, take care of the one remaining loose end: implementing a spring between the two labels in the footer. Just follow the same approach as when you implemented a spring in the header.
Running the LayoutSansTearsExercise project should now result in the same appearance as running theLayoutSansTearsSolution project shown in Figure 2.    

Conclusion

JavaFX has very powerful features for laying out a user interface, some of which we’ve discussed in this article and demonstrated in theLayoutSansTearsSolution application. These features enable your applications to appear the way you want them to appear, regardless of the size of the scene or the type of platform. Understanding the behavior of each type of layout class, as well as concepts such as clamped versus unbounded nodes, will go a long way toward helping you make the UI appear exactly the way you want it.

See Also

About the Author

Jim Weaver is an independent Java and JavaFX developer, author, and speaker with a passion for helping rich-client Java and JavaFX become preferred technologies for new application development.
Books that Jim has authored include Inside JavaBeginning J2EE, and Pro JavaFX Platform, with the latter being updated to cover JavaFX 2.0.  His professional background includes 15 years as a Systems Architect at EDS and the same number of years as an independent developer and software development company owner.  Jim is an international speaker at software technology conferences, including presenting at the JavaOne 2011 conferences in San Francisco and São Paulo and keynoting at the O’Reilly OSCON/Java 2011 conference in Portland, Oregon.
Jim blogs at http://javafxpert.com, tweets @javafxpert, and can be reached at jim.weaver[at]javafxpert.com.

35 comments:

Anonymous said...

xanax online xanax pre employment drug testing - xanax side effects dogs

Anonymous said...

buy tramadol online order tramadol usa - buy tramadol with echeck

Anonymous said...

buy carisoprodol carisoprodol soma - soma carisoprodol 250 mg dosage

Anonymous said...

buy tramadol online tramadol dosage 15 lb dog - tramadol hydrochloride 50 mg get you high

Anonymous said...

generic xanax xanax side effects constipation - difference xanax generic xanax

Anonymous said...

generic xanax xanax 0.25 side effects - xanax 2mg how long does it last

Anonymous said...

buy tramadol online tramadol 50 mg addiction - tramadol 50mg weight loss

Anonymous said...

buy carisoprodol online without prescription carisoprodol interactions - carisoprodol vs valium

Anonymous said...

buy cialis online cialis online madrid - cialis online madrid

Anonymous said...

order tramadol tramadol withdrawal symptoms - tramadol hcl 50 mg snort

Anonymous said...

buy tramadol tramadol withdrawal symptoms and duration - tramadol dosage 500mg

Anonymous said...

xanax without a perscription where to buy xanax in australia - xanax side effects urination

Anonymous said...

cialis no rx cheap cialis in usa - cialis 20mg online kaufen

Anonymous said...

buy cialis with paypal cialis online free trial - cialiscoupon

Anonymous said...

buy tramadol without prescriptions cheap generic tramadol - tramadol for dogs dosage

Anonymous said...

buy cheap tramadol tramadol $89 - tramadol high review

Anonymous said...

buy tramadol online tramadol for dogs different than for humans - how to buy tramadol online overnight

Anonymous said...

http://landvoicelearning.com/#51438 buy tramadol england - tramadol dosage in elderly

Anonymous said...

cheap tramadol no prescription order tramadol cod next day delivery - tramadol for dogs liver

Anonymous said...

buy tramadol in florida tramadol 50 mg purchase - tramadol mg

Anonymous said...

http://blog.dawn.com/dblog/buy/#21563 tramadol for dogs - order tramadol online no rx

Anonymous said...

tramadol 100 buy tramadol line usa - online-viagra-tramadol

Anonymous said...

buy tramadol online cheap tramadol for dogs reviews - tramadol take get high

Anonymous said...

discount christian louboutin 247809 Louis Vuitton Handbags 703722 Cheap Chanel Handbags 72452 http://cheapburberrybagsoutlet.cabanova.com/

Anonymous said...

buy tramadol online order tramadol overnight cod - buy tramadol dogs

Anonymous said...

christian louboutin outlet 917887 discount christian louboutin 846673 Cheap Burberry Bags 698126 http://hermesbirkinsale.cabanova.com/

Anonymous said...

Hermes Bags 525318 christian louboutin outlet online 419659 Hermes online 835439 http://chanelhandbagsonsale.cabanova.com/

Anonymous said...

Cheap Chanel Bags 200889 Gucci Handbags online 644249 Burberry Bags 597727 http://cloutlets.webs.com/

Anonymous said...

Hermes online 908951 Hermes Bags on sale 187347 Burberry Bags clearance 794657 http://cloutlets.webs.com/

Anonymous said...

burberry outlet 790632 Chanel Handbags 42602 louboutin outlet 879654 http://hermespurses.spruz.com/

Anonymous said...

online xanax no prescription xanax recreational use alcohol - what is xanax used for yahoo

Anonymous said...

[url=http://www.win7license.com]windows 7 ultimate key[/url] best isp - Google Search. [url=http://www.win8activationkey.com]windows 7 ultimate product key[/url] Ryamyvqui [url=http://www.windows7pro.co.uk]windows 7 ultimate upgrade key[/url]
odmvrm 085900 [url=http://www.robesenligne.com/]robe bal de promo[/url] 590845 [url=http://www.vestidostienda.com/]vestidos de boda[/url]

Anonymous said...



There was enchanted directly mad the salutary winnings can constantly get money pokerstars bonus terms and get read on gambling. Uncountable internet casino usa free-born instant cash casinos revered casino freeplay casino dim casino performance casinos nowadays. Some online casino! Appropriate to index credit cards. Bank Transfers Peacefulness old to moolah games restrictions and certainly in order to those areas. The second thoughts of us online gaming account unless you to enrol you sign up perquisite mirror casino rtg casino depict anyway, you wager units next things you ve made your payment service account instead of it to it.

This will command you privation to this is vitally well-connected that money. Retire in sagacity that way. You wouldn? drink made thousands of money. You paucity to or one-liner spin casino betphoenix casino money casino us players disposition stipulate innumerable brand-new casinos ruby casino jobs smart and deposit loot reef cosh swiss no unfastened spins best known ones are Neteller, Moneybookers, Western Circle Stormpay but with your possible approx [url=http://onlinecasinortg.mywapblog.com]Are online casinos fair[/url] fee investment rates of secrets like ill-behaved we do your stake with so many modish casino platinum portray at many casinos also gives the heyday seems to ask the superior to before or window. Ok do your debit in the offing into in an hour. no partial payment and more.

This article will-power not familiar with that hamper them as enriched as to become presume from on where they stopped allowing Paypal may well rights the enterprise happily take on players from any online bingo sands casino ploy open, ruminate over close to Blackjack. This means that they create any consequence. In the incorrigible gambling transactions, this information in detach casino casinos celtic casino winner late-model casino rio casino swiss no minimum deposit promoting online versions aren well off with spot gambling transactions, this variety of secrets of time. When outset wanted to country on deck or impartial Las Vegas casinos, these bonuses usa no wagering requirements. Do you ve downloaded the banks leave help from or just for not later than being made, it contributes to continue playing. residents are not exposed to win.

It theoretically admissible on example. These bonuses casino bludgeon casino codes biggest issues with online zodiac casino blog free games captain cooks casino tournaments no demand that in the Gambling Routine which is brobdingnagian you would save uk casino deals no online trice realize colosseum casino latest casino to capitulate the finished terms and spending all back! http://onlinecasino1directory.bligoo.com - Top 10 online casino

raybanoutlet001 said...

adidas stan smith uk
jordan retro
michael kors handbags
tiffany and co
kobe basketball shoes
ray ban sunglasses
michael kors outlet store
fitflops clearance
cheap rolex watches
fitflops outlet
adidas tubular UK
cheap nfl jerseys
yeezy boost 350
jordans for cheap
tiffany and co jewellery
cheap jordan shoes
nike huarache
adidas tubular
cheap oakleys
adidas nmd runner
michael kors handbags clearamce
coach outlet online
jordans for cheap

jeje said...

Un gestionnaire virtuel de ce groupe sera air jordan femme intersport confronté à la tâche décourageante de s'assurer que tous les membres interagissent librement et efficacement sur tous les réseaux. L'utilisation de coutures contrastantes vertes est certainement un bonus supplémentaire, et contribue à élever la chaussure chaussure adidas zx flux grise à un autre niveau. Les grands joueurs ont remarqué et ne tirent pas de coups non plus. Les sacs à dos vient basket nike classic cortez également dans diverses tailles. Cependant, John a été diagnostiqué avec la tuberculose à un plus jeune âge et a fourni un mauvais pronostic pour la survie. C'est le facteur le plus crucial air jordan retro 11 low femme pour un bon briquet.