Getting Started with the JavaFX Script Language (for Swing Programmers)

The JavaFX Script™ (hereinafter referred to as JavaFX) language is a declarative and statically typed scripting language. It has first-class functions, declarative syntax, list-comprehensions, and incremental dependency-based evaluation. The JavaFX language makes intensive use of the Java2D Swing GUI components and allows for easy creation of graphical user interfaces or GUIs.

In this tutorial, you use the NetBeans™ IDE 5.5 (hereinafter referred to as the IDE) to learn how to use the JavaFX language to create a simple "Hello World" application. This document also discusses the different GUI components that are available in the JavaFX language, provides sample codes, and discusses how the JavaFX components compare to the Swing GUI components.

For more information about working with NetBeans IDE, see the Support and Docs page on the NetBeans web site.

Prerequisites

To use this tutorial, the NetBeans IDE must be installed on your system and you should be familiar with the basic parts of the IDE. For a basic understanding of the IDE, see the IDE Basics topics in the online help.

You should also have a basic familiarity with the Java programming language, specifically programming in Swing and Java 2D.

System Requirements

This tutorial assumes that your system meets the requirements specified in the Systems Requirements section of the NetBeans IDE 5.5 Release Notes in order to run the IDE.

Software Needed for the Tutorial

Before you begin, you must install the following software on your computer:

Contents

Setting Up a Project

First, you need to create the project to contain your first JavaFX project using the following steps.

  1. From the main menu of the IDE, select File > New Project.
  2. In the New Project wizard, select General in the Categories pane, and Java Application in the Projects pane.

    New project window
  3. Click Next.
  4. Type JavaFXapp for the Project Name field.
  5. In the Project Location , click Browse and specify the location in which to store the project.
  6. Uncheck both the Set as Main Project and Create Main Class checkboxes.

    Image for specifying name and location
  7. Click Finish.
    The JavaFXapp project is created and displayed in the Projects window, as shown in the following figure:

    Image with new JavaFXapp project displayed in Projects window

top

Creating Your First JavaFX Program

Now, create your first JavaFX HelloWorld program using the IDE.

  1. Right-click the JavaFXapp > Source Packages node and select New > File/Folder.
    The New File wizard appears.

    Creating a new JavaFX file for the first time.
  2. Choose Other in the Categories pane and JavaFX File in the File Types pane. Click Next.

    Note: After this initial creation of the JavaFX File, the JavaFX file type should appear in the list of available file types when you invoke New from the main project node or Source Packages node in the Projects window.

    Choosing JavaFX file type in New File dialog
  3. In the Name and Location page of the New JavaFX File wizard, type HelloWord in the File Name field and src in the Folder field.
    Notice that the Created File field is updated with the new file name.

    New JavaFX filename and location
  4. Click Finish.
    The HelloWorld.fx node is added in the Project window and the HelloWorld.fx file is opened in the Source editor.

    HelloWorld.fx created
  5. Copy and paste the following code into the body of the HelloWorld.fx file.
            
         import javafx.ui.*;
                    
         Frame {
                title: "Hello World JavaFX"
                width: 200
                height: 50
                content: Label {
                    text: "Hello World"
                }
                visible: true
         }
    

top

Running Your First JavaFX Program

Now let's run your first JavaFX program using the IDE.

  1. From the Projects window, right click the JavaFXapp node and select Properties

    edit project properties
  2. Select Run in the Categories pane.
  3. Enter HelloWorld in the Arguments field and click OK.

    Edit the Run properties of the JavaFXapp project
  4. Back in the Projects window, right-click the JavaFXapp node and select Run Project.

    Run the JavaFXapp project

    Running this program displays the following window:


You have now completed your first JavaFX program. Congratulations!

top

Comparing Declarative Versus Procedural Syntax

As you can see in the previous section, the JavaFX language provides a declarative syntax for expressing the structure and content of user interface components. To help you understand what's happening, let's rewrite the above program in a purely procedural fashion, similar to the way it's normally done in Swing:

        var win = new Frame();
        win.title = "Hello World JavaFX";
        win.width = 200;
        var label = new Label();
        label.text = "Hello World";
        win.content = label;
        win.visible = true;

The above source code is also a valid JavaFX program and it has exactly the same effect as the first one.

The following outlines what's actually happening in both the declarative and procedural methods:

  1. A Frame class constructor is called to create a new Frame
  2. Values are assigned to its title, width, visible, and content attributes.
  3. In the process of assigning the content attribute, the Label class constructor is called to create a new Label, and its text attribute is assigned a value.

However, even in an extremely simple example like this one, the meaning of the program in the declarative syntax is tangibly easier to grasp.

Thus declarative programming consists of creating a program from a single expression. As in the first example above, the root of that expression is often an object allocation expression (constructor) that generates the object graph that makes up your program.

top

Adding Dynamic Behavior

The "Hello World" program has no dynamic behavior. To create a graphical user interface with dynamic behavior in JavaFX, you create graphical user interface components whose properties depend on the attribute values of other objects. Those other objects can be whatever representation of the state of your application you feel is suitable. Since the GUI component's properties depend on the properties of another object, the GUI component will automatically reflect your changes whenever you modify that other object. In this context, the GUI component is typically referred to as the View and the "other" object as the Model. Below is a Model/View version of the "Hello World" program.

        class HelloWorldModel {
            attribute saying: String;
        }

        var model = HelloWorldModel {
            saying: "Hello World"
        };

        var win = Frame {
            title: "Hello World JavaFX"
            width: 200
                        
            content: Label {
                text: bind model.saying
            }
            visible: true
        };

Running this program displays the following:

If the model object's saying attribute is changed to the following,

        model.saying = "Goodbye Cruel World!";

then the view automatically changes, as shown in the following figure:

view changes to goodbye

Notice that this example initializes the text attribute of the label by applying the JavaFX bind operator to the saying attribute of model. In this context, the bind operator indicates incremental update. This means that whenever the value of model.saying changes, the text attribute of the label will be updated to the same value.

In the case of input widgets, such as Buttons, CheckBoxes, and TextFields, the linkage between model attributes and GUI component attributes can be bi-directional.

Consider this example:

        class HelloWorldModel {
            attribute saying: String;
        }

        var model = HelloWorldModel {
            saying: "Hello World"
        };

        var win = Frame {
            title: bind "{model.saying} JavaFX"
            width: 200
            content: TextField {
                value: bind model.saying
            }
            visible: true
        };

Running this program displays the following:

If you type something else into the Input text field and press Enter, the title of the window changes accordingly:

In this case, the text field's value attribute is updated as a result of user input (by the implementation of the TextField class). When that happens the saying attribute of model is updated to the same value. Since the expression assigned to the title attribute of the window depends on the saying attribute of model, the expression is re-evaluated and the window's title attribute is updated with the result.

Note: You can bind any property of any component to an incrementally evaluated expression. Such expressions can use conditional logic, iteration, selection, etc. to produce dynamic content of any complexity. Yet, the expression of such content remains declarative.

top

Learning More About JavaFX GUI Components

This section discusses the different GUI components that are available in the JavaFX Language and gives examples of how they are used. Comparisons with the Swing GUI components are also discussed.

top

Borders and Layout Managers

In the JavaFX language, using Borders and Layout Managers is also declarative. Each Swing/AWT layout manager is encapsulated in a JavaFX class that instantiates a JPanel with the specified layout manager. Components are added to the (underlying) JPanel in a declarative way by using the attributes provided by the JavaFX class. Each Swing border type is also encapsulated by a JavaFX class that has attributes corresponding to the configuration options of that border. Here is a simple example using EmptyBorder and GridPanel. As you might expect, EmptyBorder corresponds to javax.swing.border.EmptyBorder and GridPanel corresponds to java.awt.GridLayout.

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        var win = Frame {
            width: 200
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Button {
                     text: "I'm a button!"
                     mnemonic: I
                     action: operation() {
                         model.numClicks++;
                    }
                },
                Label {
                    text: bind "Number of button clicks: {model.numClicks}"
                }]
            }
            visible: true
        };

Running this program displays the following:

After clicking the button a few times, the following is displayed:

Note: An explanation of the Button's action and mnemonic attributes is discussed below..

In this case, the GridPanel has been configured to have one column, two rows, and a vertical gap of 10 pixels between the rows by assigning values to its columns, rows, and vgap attributes. GridPanel also has an hgap attribute if you want to create a gap between its columns. An empty border of 30 pixels has also been set on all sides.

The button and label have been added by assigning them to the cells attribute. The GridPanel implementation responds to insertions and deletions to or from its cells attribute by adding or removing components from its underlying JPanel.

Other layout managers are supported in JavaFX in a similar fashion. The following table gives a summary of those layout managers:

JavaFX Widget Layout Manager
GridPanel GridLayout
GridBagPanel GridBagLayout
FlowPanel FlowLayout
BorderPanel BorderLayout
Box BoxLayout
StackPanel Romain Guy's StackLayout
CardPanel CardLayout
GroupPanel org.jdesktop.layout.GroupLayout

 

Below is a summary of the JavaFX Border classes and the Swing borders to which they correspond:

JavaFX Border Swing Border
EmptyBorder EmptyBorder
LineBorder LineBorder
BevelBorder BevelBorder
SoftBevelBorder SoftBevelBorder
MatteBorder MatteBorder
TitledBorder TitledBorder

top

Menus

Let's add a simple menubar to the above example. The new code is shown in bold below:

        import java.lang.System;

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        Frame {
            width: 200
             
       menubar: MenuBar {                  menus: Menu {                      text: "File"                      mnemonic: F                      items: MenuItem {                          text: "Exit"                          mnemonic: X                          accelerator: {                              modifier: ALT                              keyStroke: F4                          }                          action: operation() {                              System.exit(0);                          }                      }                  }             }
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Button {
                     text: "I'm a button!"
                     mnemonic: I
                     action: operation() {
                         model.numClicks++;
                    }
                },
                Label {
                    text: bind "Number of button clicks: {model.numClicks}"
                }]
            }
            visible: true
        }

    

After running this program and pressing ALT+F, the following is displayed:

As you can see, a menubar is createad by assigning a new instance of the MenuBar class to the menubar attribute of the window. You add menus to the menubar by assigning them to the menubar's menus attribute. In this case, only one menu is added, but any expression returning a list of Menu objects could be used.

To define the menu, values to its text, mnemonic, and items attributes can be assigned.

As you can probably tell, the type of the text attribute is String. The mnemonic attribute, however, is of type KeyStroke. Its value F is an enumerated instance of the KeyStroke class. In JavaFX, in the context of an attribute initializer the enumerated values of the attribute's static type (and static fields in the case of Java classes) can be accessed without being qualified by the type name (elsewhere you would have to refer to F as F:KeyStroke).

One menu item, a MenuItem whose text is "Exit" and whose mnemonic is X, has been created. A value has also been assigned to its accelerator attribute. Note that the type name Accelerator in the declaration of the value has been ommitted. This is permitted. If the type name isn't present, the static type of the attribute is used, in this case Accelerator. In addition, the accelerator's modifier and keyStroke attributes have been initialized using enumerations.

Finally, the action attribute of the MenuItem has a function type (i.e its value is a function, not an object). In this case, an inline operation that simply exits the application by calling some Java code has been written.

top

Labels

The JavaFX Label class supports HTML content. Using Labels you can create styled text and images with HTML and CSS, very much like you would in a typical Web application. In addition, by using JavaFX embedded expressions you can create dynamic HTML content in a Swing application as easily as Web page authors can do using tools like JSTL or Velocity.

Consider this example of a hypothetical shopping cart:

        
                class Item {
            attribute id: String;
            attribute productId: String;
            attribute description: String;
            attribute inStock: Boolean;
            attribute quantity: Number;
            attribute listPrice: Number;
            attribute totalCost: Number;
        }

        attribute Item.totalCost = bind quantity*listPrice;

        class Cart {
            attribute items: Item*;
            attribute subTotal: Number;
        }

        operation sumItems(itemList:Item*) {
            var result = 0.00;
            for (item in itemList) {
                result += item.totalCost;
            }
            return result;
        }

        attribute Cart.subTotal = bind sumItems(items);

        var cart = Cart {
            items:
            [Item {
                id: "UGLY"
                productId: "D100"
                description: "BullDog"
                inStock: true
                quantity: 1
                listPrice: 97.50
            },
            Item {
                id: "BITES"
                productId: "D101"
                description: "Pit Bull"
                inStock: true
                quantity: 1
                listPrice: 127.50
            }]
        };

        Frame {
             content: Label {
                text: bind

                   "<html>
                       <h2 align='center'>Shopping Cart</h2>
                       <table align='center' border='0' bgcolor='#008800' cellspacing='2' cellpadding='5'>
                          <tr bgcolor='#cccccc'>
                             <td><b>Item ID</b></td>
                             <td><b>Product ID</b></td>
                             <td><b>Description</b></td>
                             <td><b>In Stock?</b></td>
                             <td><b>Quantity</b></td>
                             <td><b>List Price</b></td>
                             <td><b>Total Cost</b></td>
                             <td> </td>
                           </tr>

                            
       {                              if (sizeof cart.items == 0)                              then "<tr bgcolor='#FFFF88'><td colspan='8'><b>Your cart is empty.</b></td></tr>"
                              
       else foreach (item in cart.items)
                                 "<tr bgcolor='#FFFF88'>
                                  <td>
       {item.id}</td>
                                  <td>
       {item.productId}</td>
                                  <td>
       {item.description}</td>
                                  <td>
       {if item.inStock then "Yes" else "No"}</td>
                                  <td>
       {item.quantity}</td>
                                  <td align='right'>
       {item.listPrice}</td>
                                  <td align='right'>
       {item.totalCost}</td>
                                  <td> </td>
                                  </tr>"
                            
       }

                           <tr bgcolor='#FFFF88'>
                                <td colspan='7' align='right'>
                                   <b>Sub Total: $
       {cart.subTotal}</b>
                               </td>
                               <td> </td>
                            </tr>
                         </table>
                      </html>"
                }
                visible: true
        }
    

Running the above program displays the following:

If you programmatically delete the cart items:

       delete cart.items;

you see the following:

In the above example, the embedded JavaFX expressions (shown in bold) dynamically create the rows of an HTML table and the content of the table cells. When there's a change in the objects on which those expressions depend, the HTML content of the Label is automatically updated.

The above example is also interesting because it demonstrates the use of expressions to define attribute values. The totalCost attribute of the Item class and the subTotal attribute of the Cart class are bound to expressions that compute their values. Whenever the objects those expressions depend on change, the attribute value is automatically recomputed and updated. Think of a spreadsheet where some cells contain formulas that refer to other cells. When you enter data into those other cells, the values of the cells containing formulas that depend on them are automatically updated.

top

Images in HTML

The JavaFX Label class actually encapsulates a specialized JEditorPane which uses a shared image cache that supports loading images from JAR files using the Java class loader. Thus, you can use HTML <img> elements that reference image resources packaged with your application as if they were normal file URL's.

Hyperlinking

The Label class also supports HTML hyperlinking by embedding a special URL as the href attribute of an HTML <a> element.

Such URL's are created with the JavaFX # operator. This operator generates a stringified object reference that refers to its operand which can later be dereferenced by the JavaFX ? operator. For example,

        var a = 20;
        var b = #a;
        assert b instanceof String; // passes
        var c = (Number) ?b;
        assert a == c;  // passes

The Label class's HTML renderer recognizes such URLs in HTML <a href= url> contexts and handles mouse clicks on such elements by deferencing the URL and if its value refers to a function or operation it calls that function or operation.

For example, here's the previous Button-Click example rewritten to use a Label with a hyperlink instead of a Button:

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        Frame {
            width: 200
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Label {
                     text: bind
                             "<html>
                                <a href='
       {#(operation() {model.numClicks++;})}'>
                                    I'm a hyperlink!
                                </a>
                             </html>"
                },
                Label {
                    text: bind "Number of clicks: {model.numClicks}"
                }]

            }
            visible: true
        };
    

The expression in bold in the above example creates a new operation that will increment the model's numClicks attribute. Applying the # operator to that generates a URL that refers to the operation which is then embedded into the HTML markup.

Running this program displays the following:

After clicking the hyperlink twice, the following displays:

top

GroupPanel, SimpleLabel, and TextField

This section will use a very simple example to introduce you to the JavaFX GroupPanel, SimpleLabel, and TextField classes.

The JavaFX GroupPanel class encapsulates the Java.net GroupLayout class. GroupLayout is a powerful layout manager that represents the contents of a panel as sets of parallel horizontal and vertical groups. In JavaFX, these parallel groups are simply referred to as Rows and Columns. When you declare a GroupPanel, you also declare Row and Column objects for each horizontal or vertical grouping of components. Then when you add components, you assign the appropriate Row and Column objects to the component's row and column attributes. GroupPanel automatically inserts gaps between components as defined by the current look-and-feel's style guidelines. By declaring values for the alignment and resizable attributes of a Row or Column object you can control the alignment of components within that row or column, and whether the row or column can be resized.

The JavaFX TextField class encapsulates the Swing JFormattedTextField class. It has a value attribute that is updated whenever the user presses Enter while the focus is on the text field or when the focus moves to another component. You can control its width by assigning a numeric value to its columns attribute, and its horizontal alignment by assigning LEADING, CENTER, or TRAILING to its horizontalAligment attribute. Finally, two function-valued attributes are provided that allow you to perform actions based on user interaction: action and onChange. If you assign a function or operation to the action attribute, your function will be called whenever the user presses the Enter key. If you assign a function or operation to the onChange attribute, your function or operation will be called whenever the text field's value changes.

The JavaFX SimpleLabel class encapsulates the Swing JLabel class. SimpleLabel differs from Label in that it doesn't support hyperlinking and its preferred size is calculated differently.

The following shows what the example looks like:

Below is the code used to create the above example:

        class Model {
            attribute firstName: String;
            attribute lastName: String;
        }

        var model = Model {
            firstName: "Joe"
            lastName: "Smith"
        };

        Frame {
            content: GroupPanel {
                 
       var firstNameRow = Row { alignment: BASELINE }                 var lastNameRow = Row { alignment: BASELINE }                 var labelsColumn = Column {                     alignment: TRAILING                 }                 var fieldsColumn = Column {                     alignment: LEADING                     resizable: true                 }                 rows: [firstNameRow, lastNameRow]                 columns: [labelsColumn, fieldsColumn]
                content:
                [SimpleLabel {
                     
       row: firstNameRow                     column: labelsColumn
                    text: "First Name:"
                },
                TextField {
                     
       row: firstNameRow                     column: fieldsColumn

                    columns: 25
                    value: bind model.firstName
                },
                SimpleLabel {
                     
       row: lastNameRow                     column: labelsColumn
                    text: "Last Name:"
                },
                TextField {
                     
       row: lastNameRow                     column: fieldsColumn
                    columns: 25
                    value: bind model.lastName
                }]
            }
            visible: true
        };
    

In the above example, the code related to layout is in blue. This example consists of two rows (one for the first name and one for the last name) and two columns (one for the labels and one for the text fields). In the declaration of the GroupPanel, four variables ( firstNameRow, lastNameRow, labelsColumn, and fieldsColumn) have been declared for the rows and columns. Two Row's were then assigned to the GroupPanel's rows attribute and the two Column's to its columns attribute. Finally, as you can see, the labels and text fields were added by assigning them to the GroupPanel's elements attribute. As the he labels and text fields are declared, their row and column attributes were assigned appropriately.

top

Buttons

The JavaFX Button class encapsulates the Swing JButton component. To introduce you to the use of Buttons, let's recreate a simple example from the Swing tutorial, which looks like this

        class ButtonDemoModel {
            attribute buttonEnabled: Boolean;
        }

        var model = ButtonDemoModel {
            buttonEnabled: true
        };

        Frame {
            title: "ButtonDemo"
            content: FlowPanel {
                content:
                [Button {
                    text: "Disable middle button"
                    verticalTextPosition: CENTER
                    horizontalTextPosition: LEADING
                    icon: Image {
                                                 url: "#"
                    }
                    mnemonic: D
                    toolTipText: "Click this button to disable the middle button"
                    enabled: bind model.buttonEnabled
                    action: operation() {
                         model.buttonEnabled = false;
                    }
                },
                Button {
                    text: "Middle button"
                    icon: Image {
                        url: "#"
                    }
                    verticalTextPosition: BOTTOM
                    horizontalTextPosition: CENTER
                    mnemonic: M
                    toolTipText: "This middle button does nothing when you click it."
                    enabled: bind model.buttonEnabled
                },
                Button {
                    text: "Enable middle button"
                    icon: Image {
                        url: "#"
                    }
                    mnemonic: E
                    toolTipText: "Click this button to enable the middle button"
                    action: operation() {
                         model.buttonEnabled = true;
                    }
                    enabled: bind not model.buttonEnabled
                }]
            }
            visible: true
        }

After clicking the left button, the example looks like this:

The enabled attribute of each of the three buttons is bound to the buttonEnabled attribute of the model object. When you modify this attribute in the actions of the left and right buttons, they all reflect the change.

Images to the buttons have been added by assigning Image objects to their icon attributes.

JavaFX Image objects have a url attribute to which you can assign a String containing a URL that points at the desired image resource. JavaFX has an internal image cache that supports loading images from JAR files using the Java class loader. As a result image resources packaged in JAR files accessible to the class loader can be accessed by simply using file: URLs.

top

TabbedPanes

To demonstrate the use of a TabbedPane, let's define the following Model class that has attributes corresponding to those of the TabbedPane widget.

        class Model {
            attribute tabPlacement: TabPlacement;
            attribute tabLayout: TabLayout;
            attribute tabCount: Integer;
            attribute selectedTab: Integer;
        }

Now, let's project an instance of the TabbedPane from an instance of the above model:

        var model = Model {
            tabPlacement: TOP
            tabLayout: WRAP
            selectedTab: 3
            tabCount: 5
        };

        Frame {
            height: 300
            width: 400
            content: TabbedPane {
                tabPlacement:  
       bind model.tabPlacement
                tabLayout:  
       bind model.tabLayout
                tabs:  
       bind foreach (i in [1..model.tabCount])
                    Tab {
                        title: "Tab {i}"
                        toolTipText: "Tooltip {i}"
                    }
                selectedIndex:  
       bind model.selectedTab

            }
            visible: true
        }

    

The code in bold shows the dependencies between the TabbedPane and the model. Having done this, the appearance of the TabbedPane can be changed by modifying the model.

Tabs are added to a TabbedPane by assigning a list of Tab objects to its tabs attribute. The TabPlacement and TabLayout classes define enumerations (TOP, LEFT, BOTTOM, RIGHT and WRAP or SCROLL) that allow you to control the location and layout of the tabs by assigning appropriate values to the TabbedPane's tabPlacement and tabLayout attributes. The TabbedPane's selectedIndex attribute determines which tab's content is visible.

Running the above program displays the following:

Notice that the fourth tab is selected. This is due to the fact that the model's selectedTab attribute has been initialized to the (zero-based) index 3. In this example, the TabbedPane's selectedIndex attribute is also updated because it is bound to the value of selectedTab and that tab is selected.

If the model is programmatically change to the following:

        model.tabPlacement = BOTTOM;

the tabs move to the bottom, as shown in the following figure:

Executing the following code:

        model.selectedTab = 0;

causes the first tab to become the selected tab:

Executing the following code:

        model.tabCount = 20;

causes fifteen new tabs to be created and added to the tabbed pane, as shown in the following figure.

Executing the code

        model.tabLayout = SCROLL;

results in the following:

Executing the following code:

        model.tabCount = 2;

causes all, but the first two tabs to be removed:

top

ListBoxes

The JavaFX ListBox class provides the functionality of the Swing JList component but with a declarative interface.

As a demonstration, let's recreate a simple example from the Swing tutorial ( ListDemo) that initially looks like this:

In this example, the ListBox contains a list of employee names. If the "Fire" button is clicked, the selected employee is removed from the list. If a new name is entered into the TextField below the list, the "Hire" button becomes enabled. If I press the Hire button, the new name is added to the list.

This example also demonstrates the use of BorderPanel and FlowPanel. A BorderPanel contains up to 5 components which can be placed at the top, left, bottom, right, or center of the panel. It stretches the left and right components vertically, the top and bottom components horizontally, and the center component in both directions. A FlowPanel contains a list of components which it lays out in a left to right flow, much like text in a paragraph. This example also shows the use of RigidArea, an invisible filler component which can be used to create space between other components.

        class EmployeeModel {
            attribute employees: String*;
            attribute selectedEmployee: Number;
            attribute newHireName: String;
        }

        var model = EmployeeModel {
            employees:
            ["Alan Sommerer",
             "Alison Huml",
             "Kathy Walrath",
             "Lisa Friendly",
             "Mary Campione",
             "Sharon Zakhour"]
        };

        Frame {
            title: "ListBox Example"
            content: BorderPanel {
                center:  
       ListBox {                     selection: bind model.selectedEmployee                     cells: bind foreach (emp in model.employees)                         ListCell {                            text: emp                         }                 }
                bottom: FlowPanel {
                    content:
                    [Button {
                       text: "Fire"
                       action: operation() {
                           delete model.employees[model.selectedEmployee];
                       }
                    },
                    RigidArea {
                        width: 5
                    },
                    TextField {
                        columns: 30
                        value: bind model.newHireName
                    },
                    RigidArea {
                        width: 5
                    },
                    Button {
                        text: "Hire"
                        enabled: bind model.newHireName.length() > 0
                        action: operation() {
                            insert model.newHireName
                               after model.employees[model.selectedEmployee];
                            model.newHireName = "";
                            if (sizeof model.employees == 1) {
                                model.selectedEmployee = 0;
                            } else {
                                model.selectedEmployee++;
                            }
                        }
                    }]
                }
            }
            visible: true
        }
    

The code to create the ListBox is in bold. As you can see, the ListBox has been created by assigning a list of ListCell objects to its cells attribute. In this case, the list of cells is projected from the list of employees in the model. So, as employees are added to or deleted from the model, the corresponding cells will be added to or deleted from the ListBox. Whatever value you assign to a ListCell's text attribute will be displayed when the cell is rendered. Although not required in this example, you can also assign HTML markup to a ListCell's text attribute to create styled text and/or images as the content of the cell.

The ListBox's selection attribute contains the indices of the list's selected cells. In this case, it has been bounded to the model's selectedEmployee attribute, so as the selection is moved in the list, the model's selectedEmployee will be updated. At the same time, if the selectedEmployee attribute is updated the list's selection will reflect the change. This is how it is done above in the "Hire" button's action.

After clicking "Fire" twice, the following is displayed:

If a new name is entered in the text field, as shown below,

and then the Hire button is clicked, the following is displayed.

top

SplitPanes

The JavaFX SplitPane is based on a custom Java component rather than the Swing JSplitPane class. Unlike JSplitPane, it can contain multiple components. Like JSplitPane you can control its orientation and the amount of space assigned to each of its contained components. Let's look at an example, which will look like this:

This example consists of a horizontal split pane with two components. The left component is a ListBox which occupies 30% of the available space. The right component, which occupies the remaining 70% of the space, is actually a ScrollPane containing a CenterPanel ( a panel that contains one component which it centers in its content area). The CenterPanel, in turn, contains a SimpleLabel, which displays the image associated with the selected item in the list box.

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }


        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                         "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                         "kathyCosmo.gif", "lainesTongue.gif",
                         "left.gif", "middle.gif", "right.gif",
                         "stickerface.gif"]

            selectedImageUrl: bind "#}"
        };

        Frame {
            title: "SplitPane Example"
            height: 400
            width: 500
            content:  
       SplitPane {                 orientation: HORIZONTAL
                 
       content:                 [SplitView {                     weight: 0.30
                     
       content: ListBox {
                         selection: bind model.selectedImageIndex
                         cells: bind foreach (file in model.imageFiles)
                            ListCell {
                               text: bind file
                           }
                    }
                 
       },                 SplitView {                     weight: 0.70
                     
       content: ScrollPane {
                        view: CenterPanel {
                            background: white
                            content: SimpleLabel {
                                icon: Image {url: bind model.selectedImageUrl}
                            }
                        }
                    }
                 
       }]             }

            visible: true
        }
    

The code related to the split pane is in bold. As you can see, this horizontal split pane is created by assigning HORIZONTAL to its orientation attribute. Components have been added to the SplitPane by assigning a list of SplitView objects to its content attribute. Each SplitView has two attributes, weight and content. The weight attribute determines how much of the available space is given to that panel when the overall split pane is resized. Whatever component you assign to its content attribute will be displayed inside that section of the split pane.

top

RadioButton, RadioButtonMenuItem, ToggleButton, and ButtonGroup

The JavaFX RadioButton class encapsulates the Swing JRadioButton component. The JavaFX RadioButtonMenuItem class encapsulates the Swing JRadioButtonMenuItem component. The JavaFX ToggleButton class encapsulates the Swing JToggleButton component.

These components have a strong similarity with each other, and with single selection ListBox's, ComboBox's, TabbedPane's, and CardPanel's, namely that all of these components allow you to "pick" one item from a list of items.

RadioButtons, RadioButtonMenuItems, and ToggleButtons are associated with a list of items by means of the JavaFX ButtonGroup class, which corresponds to the Swing ButtonGroup. Unlike the Swing class, however, the JavaFX ButtonGroup also provides a selection model similar to a (single-selection) ListBox. The ButtonGroup's selection attribute holds a numeric index that controls which button among the buttons it contains is the selected one. If you assign a value to this attribute, the button at that index will become selected, and any other buttons will be deselected. Likewise, if the user selects a particular button, the index of that button will be implicitly assigned to the ButtonGroup's selection.

To demonstrate this, let's extend the example from the previous section to include three button groups. The first will be associated with a list of RadioButtonMenuItems in a menu. The second will be associated with a list of RadioButtons placed into a four column GridPanel. Finally, the third will be associated with a list of ToggleButtons placed into a single column GridPanel. Each group of buttons will be projected from the same model objects as were the ListBox's cells in the original example, and the selection attribute of their ButtonGroup's will be bound to the same model attribute as the ListBox's selection was in the original. The result is that whatever selection you make in the ListBox, the menu, the radio buttons, or the toggle buttons will be reflected in all the others.

The following image and source code show what the example looks like initially:

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }

        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                         "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                         "kathyCosmo.gif", "lainesTongue.gif",
                         "left.gif", "middle.gif", "right.gif",
                         "stickerface.gif"]

            selectedImageUrl: bind
                "#"
        };

        Frame {
            menubar: MenuBar {
                menus: Menu {
                   text: "File"
                   mnemonic: F
                    
       var buttonGroup = ButtonGroup {                        selection: bind model.selectedImageIndex                    }
                   items: foreach (imageName in model.imageFiles)
                        RadioButtonMenuItem {
                            
       buttonGroup: buttonGroup
                           text: imageName
                        }
                }
            }
            title: "RadioButton/ToggleButton Example"
            height: 400
            width: 500
            content: BorderPanel {
                top: GridPanel {
                        rows:  sizeof model.imageFiles / 4
                        columns: sizeof model.imageFiles % 4
                         
       var buttonGroup = ButtonGroup {                             selection: bind model.selectedImageIndex                         }
                        cells: foreach (imageName in model.imageFiles)
                             RadioButton {
                                  
       buttonGroup: buttonGroup

                                 text: imageName
                             }
                }
                right: GridPanel {
                        rows: sizeof model.imageFiles
                        columns: 1
                         
       var buttonGroup = ButtonGroup {                              selection: bind model.selectedImageIndex                         }
                        cells: foreach (imageName in model.imageFiles)
                             ToggleButton {
                                  
       buttonGroup: buttonGroup
                                 text: imageName
                             }
                }
                center: SplitPane {
                    orientation: HORIZONTAL
                    content:
                    [SplitView {
                        weight: 0.30
                        content: ListBox {
                             selection: bind model.selectedImageIndex
                             cells: bind foreach (imageName in model.imageFiles)
                                 ListCell {
                                    text: bind imageName
                                 }
                         }
                    },
                    SplitView {
                         weight: 0.70
                         content: ScrollPane {
                             view: CenterPanel {
                                 background: white
                                 content: SimpleLabel {
                                     icon: Image {url: bind model.selectedImageUrl}
                                 }
                             }
                         }
                    }]
                }
            }
            visible: true
        }
    

The code related to the ButtonGroups is in orange. As you can see, you add buttons to a button group by setting the button's buttonGroup attribute to the desired ButtonGroup.

If the "Pig.gif" in the list box is clicked (or the "Pig.gif" radio button selected or the "Pig.gif" toggle button selected), the following is displayed:

If the menu is opened, you can see that it also reflects the same selection:

top

ComboBoxes

The JavaFX ComboBox corresponds to the Swing JComboBox component. To demonstrate the use of ComboBoxes, let's add two of them to the previous example. The example will now look like this:

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }


        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                     "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                     "kathyCosmo.gif", "lainesTongue.gif",
                     "left.gif", "middle.gif", "right.gif",
                     "stickerface.gif"]
            selectedImageUrl: bind "#"
        };

        Frame {
            menubar: MenuBar {
                menus: Menu {
                   text: "File"
                   mnemonic: F
                   var buttonGroup = ButtonGroup {
                       selection: bind model.selectedImageIndex
                   }
                   function makeRadioButton(buttonGroup, imageName) {
                        return RadioButtonMenuItem {
                           buttonGroup: buttonGroup
                           text: imageName
                        };
                   }
                   items: foreach (imageName in model.imageFiles)
                        makeRadioButton(buttonGroup, imageName)

                }
            }
            title: "RadioButton/ToggleButton/ComboBox Example"
            height: 400
            width: 500
            content: BorderPanel {
                top: GridPanel {
                        rows:  sizeof model.imageFiles / 4
                        columns: sizeof model.imageFiles % 4
                        var buttonGroup = ButtonGroup {
                            selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                            RadioButton {
                                buttonGroup: buttonGroup
                                text: imageName
                            }
                }
                right: GridPanel {
                        rows: sizeof model.imageFiles
                        columns: 1
                        var buttonGroup = ButtonGroup {
                             selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                             ToggleButton {
                                 buttonGroup: buttonGroup
                                 text: imageName
                             }
                }
                center: SplitPane {
                    orientation: HORIZONTAL
                    content:
                    [SplitView {
                        weight: 0.30
                        content: ListBox {
                            selection: bind model.selectedImageIndex
                            cells: bind foreach (imageName in model.imageFiles)
                                ListCell {
                                    text: bind imageName
                                }
                        }
                    },
                    SplitView {
                         weight: 0.70
                         content: BorderPanel {
                            top:  
       ComboBox {                                 selection: bind model.selectedImageIndex                                 cells: bind foreach (imageName in model.imageFiles)                                     ComboBoxCell {                                         text: bind imageName                                     }                             } 
                            center: ScrollPane {
                                view: CenterPanel {
                                    background: white
                                    content: SimpleLabel {
                                        icon: Image {url: bind model.selectedImageUrl}
                                    }
                                }
                            }
                        }
                    }]
                }
                bottom: FlowPanel {
                     alignment: LEADING
                     content:  
       ComboBox {                          selection: bind model.selectedImageIndex                          cells: bind foreach (imageName in model.imageFiles)                               ComboBoxCell {                                  text: bind "<html>                                            <table>                                               <tr>                                                 <td>                                                    <img src='#' height='32' width='32'></img>                                                 </td>                                                 <td>                                                     {imageName}                                                 </td>                                               </tr>                                            </table>                                          </html>"                               }                     }
                }
            }
            visible: true
        }
    

The code related to the ComboBoxes is in bold. You specify the content of a ComboBox's drop-down list by assigning a list of ComboBoxCell's to its cells attribute. The ComboBoxCell's text attribute determines the appearance of the cell. You can create styled text and/or images as the content of a cell by assigning HTML content to its text attribute (as in the ComboBox in the lower left corner of the window in this example). The ComboBox's numeric selection attribute determines the selected cell. Programmatically assigning a (zero-based) integer index to this attribute causes the cell at that position to be selected. At the same time when the user selects a cell, the index of the cell will be implicitly assigned to selection. In this example, the selection attribute of both ComboBoxes is bound to the same model attribute as the button groups and listbox in the original example. The list of ComboBox cells is also projected from the same model attribute as the button groups and the listbox. As a result, you can select the image to be displayed through either ComboBox as well as through the listbox or button groups.

If the second ComboBox is opened, the example looks like the following:

top

Trees

The JavaFX Tree class provides a declarative interface encapsulating the Swing JTree component. To introduce you to the use of Trees, let's first create a very simple example tree with no dynamic behavior:

        Frame {
            height: 400
            width: 300
            content: Tree {
                root: TreeCell {
                    text: "Tree"
                    cells:
                    [TreeCell {
                        text: "colors"
                        cells:
                        [TreeCell {
                            text: "<html><font color='blue'>blue</font></html>"
                        },
                        TreeCell {
                            text: "<html><font color='red'>red</font></html>"
                        },
                        TreeCell {
                            text: "<html><font color='green'>green</font></html>"
                        }]
                    },
                    TreeCell {
                        text: "food"
                        cells:
                        [TreeCell {
                            text: "hot dogs"
                        },
                        TreeCell {
                            text: "pizza"
                        },
                        TreeCell {
                            text: "ravioli"
                        }]
                    }]
                }
            }
            visible: true
        }

Following is what it looks like when the above code is run:

To populate a Tree, an expression returning a TreeCell object is assigned to its root attribute. A TreeCell represents one row in the tree. You specify the child cells of a TreeCell by assigning a list of TreeCell objects to its cells attribute. In addition, a TreeCell has a text attribute which determines its visual appearance. As you can see, you can assign HTML content to text to create styled text and/or images as the content of a tree cell.

Next, let's recreate one of the Swing tutorial demos (called GenealogyExample) that displays the descendants or ancestors of a particular person.

When you run that demo, the example progam initially looks like the following:

If a person is selected in the tree and one of the radio buttons is clicked, the selected person becomes the root of the tree. Depending on the selection made, the ancestors or descendants of the person are shown as sub-nodes in the tree.

Below is the code for this example. The code related to the tree itself is in bold. TreeCell has a Boolean selected attribute that is set by the tree implementation as the user selects cells in the tree. At the same time if you programatically assign a value to this attribute, the corresponding tree cell will be selected or unselected depending on the value.

In this case, the geneology of a person is a recursive data structure, so the TreeCell's cells attribute has been defined using an expression that makes recursive function calls. Notice that the bind lazy operator is used rather than just the bind operator in the initialization of the cells attribute, which indicates lazy evaluation. This means that its right hand side is not evaluated until its left hand side is accessed the first time. As a result, the recursive calls to the descendantTree() and ancestorTree() functions are not actually performed until you expand a node in the tree and the tree requires access to the node's child cells.

        class GeneologyModel {
            attribute people: Person*;
            attribute selectedPerson: Person;
            attribute showDescendants: Boolean;
        }

        class Person {
            attribute selected: Boolean;
            attribute father: Person;
            attribute mother: Person;
            attribute children: Person*;
            attribute name: String;
        }

        // By defining these triggers I can populate the model
        // by just assigning the mother and father attributes of a Person

        trigger on Person.father = father {
            insert this into father.children;
        }

        trigger on Person.mother = mother {
            insert this into mother.children;
        }

        // Create and populate the model
        var model = GeneologyModel {

            var jack = Person {
                selected: true
                name: "Jack (great-granddaddy)"
            }
            var jean = Person {
                name: "Jean (great-granny)"
            }
            var albert = Person {
                name: "Albert (great-granddaddy)"
            }
            var rae = Person {
                name: "Rae (great-granny)"
            }
            var paul = Person {
                name: "Paul (great-granddaddy)"
            }
            var josie = Person {
                name: "Josie (great-granny)"
            }
            var peter = Person {
                father: jack
                mother: jean
                name: "Peter (grandpa)"
            }
            var zoe = Person {
                father: jack
                mother: jean
                name: "Zoe (grandma)"
            }
            var simon = Person {
                father: jack
                mother: jean
                name: "Simon (grandpa)"
            }
            var james = Person {
                father: jack
                mother: jean
                name: "James (grandpa)"
            }
            var bertha = Person {
                father: albert
                mother: rae
                name: "Bertha (grandma)"
            }
            var veronica = Person {
                father: albert
                mother: rae
                name: "Veronica (grandma)"
            }
            var anne = Person {
                father: albert
                mother: rae
                name: "Anne (grandma)"
            }
            var renee = Person {
                father: albert
                mother: rae
                name: "Renee (grandma)"
            }
            var joseph = Person {
                father: paul
                mother: josie
                name: "Joseph (grandpa)"
            }
            var isabelle = Person {
                father: simon
                mother: veronica
                name: "Isabelle (mom)"
            }
            var frank = Person {
                father: simon
                mother: veronica
                name: "Frank (dad)"
            }
            var louis = Person {
                father: simon
                mother: veronica
                name: "Louis (dad)"
            }
            var laurence = Person {
                father: james
                mother: bertha
                name: "Laurence (dad)"
            }
            var valerie = Person {
                father: james
                mother: bertha
                name: "Valerie (mom)"
            }
            var marie = Person {
                father: james
                mother: bertha
                name: "Marie (mom)"
            }
            var helen = Person {
                father: joseph
                mother: renee
                name: "Helen (mom)"
            }
            var mark = Person {
                father: joseph
                mother: renee
                name: "Mark (dad)"
            }
            var oliver = Person {
                father: joseph
                mother: renee
                name: "Oliver (dad)"
            }
            var clement = Person {
                father: laurence
                mother: helen
                name: "Clement (boy)"
            }
            var colin = Person {
                father: laurence
                mother: helen
                name: "Colin (boy)"
            }

            people: [jack, jean, albert, rae, paul, josie,
                  peter, zoe, simon, james, bertha, anne,
                  renee, joseph, frank, louis, laurence,
                  valerie, marie, helen, mark, oliver,
                  clement, colin]

            selectedPerson: jack
            showDescendants: true
        };

        // Tree generation functions:
        operation geneologyTree(p:Person, showDescendants:Boolean) {
            if (showDescendants) {
                return descendantTree(p);
            } else {
                return ancestorTree(p);
            }
        }

        function descendantTree(p:Person) {
            return  
       TreeCell {                 selected: bind p.selected                 text: bind p.name                 cells:                     bind lazy                         foreach (c in p.children)                             descendantTree(c)             };
        }

        function ancestorTree(p:Person) {
            return  
       TreeCell {                 selected: bind p.selected                 text: bind p.name                 cells:                     bind lazy                         foreach (a in [p.father, p.mother])                             ancestorTree(a)             };
        }

        Frame {
            title: "Genology Example"
            height: 300
            width: 300
            content: BorderPanel {
                top: FlowPanel {
                    var buttonGroup = new ButtonGroup()
                    content:
                    [RadioButton {
                        buttonGroup: buttonGroup
                        text: "Show Descendants"
                        selected: model.showDescendants
                        onChange: operation(newValue:Boolean) {
                             if (newValue) {
                                 var selectedPerson = model.people[selected];
                                 if (selectedPerson <> null) {
                                     model.selectedPerson = selectedPerson;
                                 }
                                 model.showDescendants = true;
                             }
                        }
                    },
                    RadioButton {
                        buttonGroup: buttonGroup
                        text: "Show Ancestors"
                        onChange: operation(newValue:Boolean) {
                             if (newValue) {
                                 var selectedPerson = model.people[selected];
                                 if (selectedPerson <> null) {
                                     model.selectedPerson = selectedPerson;
                                 }
                                 model.showDescendants = false;
                             }
                        }
                    }]
                }
                center:  
       Tree {                         showRootHandles: true                         root: bind geneologyTree(model.selectedPerson,                                                    model.showDescendants)                 }
            }
            visible: true
        }

    

If all the nodes are expanded and "Clement" is selected, the tree looks like the following figure:

After clicking on "Show Ancestors", Clement becomes the root, and his parents are shown beneath him:

top

Tables

The JavaFX Table class encapsulates the Swing JTable component. To demonstrate the use of Tables, let's create a slight variation of one of the Swing tutorial examples (SimpleTableDemo) that looks like this:

Following is the code to create the above table:

        class Person {
            attribute firstName: String;
            attribute lastName: String;
            attribute sport: String;
            attribute numYears: Number;
            attribute vegetarian: Boolean;
            attribute selected: Boolean;
        }

        class TableDemoModel {
            attribute people: Person*;
        }

        var model = TableDemoModel {
            people:
            [Person {
                firstName: "Mary"
                lastName: "Campione"
                sport: "Snowboarding"
                numYears: 5
                vegetarian: false
            },
            Person {
                firstName: "Alison"
                lastName: "Huml"
                sport: "Rowing"
                numYears: 3
                vegetarian: true
            },
            Person {
                firstName: "Kathy"
                lastName: "Walrath"
                sport: "Knitting"
                numYears: 2
                vegetarian: false
            },
            Person {
                firstName: "Sharon"
                lastName: "Zakhour"
                sport: "Speed reading"
                numYears: 20
                vegetarian: true
            },
            Person {
                firstName: "Philip"
                lastName: "Milne"
                sport: "Pool"
                numYears: 10
                vegetarian: false
            }]
        };

        Frame {
            height: 120
            width: 500
            title: "SimpleTableDemo"
            content:  
       Table {                 columns:                 [TableColumn {                     text: "First Name"  
                      },                 TableColumn {                     text: "Last Name"  
                      },                 TableColumn {                     text: "Sport"  
                          width: 100
                        },                 TableColumn {                     text: "# of Years"  
                          alignment: TRAILING  
                      },                 TableColumn {                     text: "Vegetarian"  
                          alignment: CENTER  
                      }]                  cells: bind foreach (p in model.people)
                            [TableCell {                         text:bind p.firstName
                         
       selected: bind p.selected  
                          },                     TableCell {                         text:bind p.lastName  
                          },                     TableCell {                         text: bind p.sport
                            },                     TableCell {                         text: bind "{p.numYears}"  
                          },                     TableCell {                         text: bind if p.vegetarian then "Yes" else "No"  
                              toolTipText: bind "{p.firstName} {p.lastName} {if not p.vegetarian then "eats" else "does not eat"} meat"  
                          }]
            }
            visible: true
        }
    

The code related to the table is in bold. To create the Table, a list of TableColumn objects has been assigned to its columns attribute and a list of TableCell objects has been assigned to its cells attribute. In this case, since five TableColumn objects have been assigned, this table will have five columns. Also, since five TableCell objects have been assigned for each person, there will be one row for each person. The TableColumn's text attribute determines the content of the column's header cell. Its width and alignment attributes determine the column's preferred width and horizontal alignment.

The JavaFX Table is a ScrollableWidget, so you don't have to explicitly add it to a scroll pane.

top

Text Components

To demonstrate the use of text components in JavaFX, let's create a slight variation of the text sampler demo from the Swing tutorial, which looks like this:

The JavaFX text components in this example correspond to Swing components as follows:

JavaFX Widget Swing Component
TextField JFormattedTextField
PasswordField JPasswordField
TextArea JTextArea
EditorPane JEditorPane
TextPane JTextPane

 

        
class TextSamplerModel {
            attribute textFieldInput: String?;
        }

        var model = TextSamplerModel {
        };

        Frame {
            title: "Text Sampler"
            visible: true
            content: SplitPane {
                orientation: HORIZONTAL
                content:
                [SplitView {
                    weight: 0.5
                    content:
                    BorderPanel {
                        top: GridBagPanel {
                             border: CompoundBorder {
                                 borders:
                                 [TitledBorder {
                                     title: "Text Fields"
                                 },
                                 EmptyBorder {
                                     top: 5
                                     left: 5
                                     bottom: 5
                                     right: 5
                                 }]
                             }
                             cells:
                             [GridCell {
                                  anchor: EAST
                                  gridx: 0
                                  gridy: 0
                                  content: SimpleLabel {
                                       text: "TextField: "
                                  }
                              },
                              GridCell {
                                  anchor: WEST
                                  fill: HORIZONTAL
                                  weightx: 1
                                  gridx: 1
                                  gridy: 0
                                  content:  
       TextField {                                       action: operation(value:String) {                                           model.textFieldInput = value;                                       }                                   }
                              },
                              GridCell {
                                  anchor: EAST
                                  gridx: 0
                                  gridy: 1
                                  insets: {top: 2}
                                  content: SimpleLabel {
                                       text: "PasswordField: "
                                  }
                              },
                              GridCell {
                                  gridx: 1
                                  gridy: 1
                                  fill: HORIZONTAL
                                  weightx: 1
                                  insets: {top: 2}
                                  content:  
       PasswordField {                                       action: operation(value:String) {                                           model.textFieldInput = value;                                       }                                   }
                              },
                              GridCell {
                                  anchor: WEST
                                  weightx: 1.0
                                  gridx: 0
                                  gridy: 2
                                  gridwidth: 2
                                  fill: HORIZONTAL
                                  content: SimpleLabel {
                                      border: EmptyBorder {
                                          top: 10
                                      }
                                      text: bind if model.textFieldInput == null then  "Type text and then Return in a field" else "You typed \"{model.textFieldInput}\""

                                  }
                              }]
                         }
                   center: BorderPanel {
                             border: CompoundBorder {
                                 borders:
                                 [TitledBorder {
                                     title: "Plain Text"
                                  },
                                  EmptyBorder {
                                      top: 5
                                      left: 5
                                      bottom: 5
                                      right: 5
                                  }]
                             }
                             center:  
       TextArea {                                  font: new Font("Serif", Font.ITALIC, 16)                                  lineWrap: true                                  wrapStyleWord: true                                  text: "This is an editable TextArea that has been initialized with its text attribute. A text area is a \"plain\" text component, which means that although it can display text in any font, all of the text is in the same font"                              }
                        }
                    }
                },
                SplitView {
                    weight: 0.5
                    content: SplitPane {
                       border: CompoundBorder {
                           borders:
                           [TitledBorder {
                               title: "Styled Text"
                            },
                            EmptyBorder {
                                top: 5
                                left: 5
                                bottom: 5
                                right: 5
                            }]
                       }
                       orientation: VERTICAL
                       content:
                       [SplitView {
                           weight: 0.5
                           content:  
       EditorPane {                                 opaque: true                                 preferredSize: {height: 250 width: 250}                                 contentType: HTML                                 editable: false                                 text: "<html>         <body>         <img src='#' width='64' height='64'>         This is an uneditable <code>EditorPane</code>,         which was <em>initialized</em>         with <strong>HTML</strong> text <font size='-2'>but not from</font> a         <font size='+2'>URL</font>.          <p>         An editor pane uses specialized editor kits         to read, write, display, and edit text of         different formats.         </p>         <p>         The Swing text package includes editor kits         for plain text, HTML, and RTF.         </p>         <p>         You can also develop         custom editor kits for other formats.         </p>         </body></html>"                            }
                       },
                       SplitView {
                           weight: 0.5
                           content:  
       TextPane {                                preferredSize: {height: 250 width: 250}                                editable: true                                content:                                ["This is an editable TextPane, another styled text component, which supports embedded icons...\n",                                 Image {url: "#"},                                 "\n...and embedded components...\n",                                 Button {                                     contentAreaFilled: false                                     icon: Image {url: "#"}                                 },                                 "\nTextPane is a subclass of EditorPane that uses a StyledEditorKit and StyledDocument,\n and provides cover methods for interacting with those objects."]                           }
                       }]
                  }
                }]
           }
        }
    

top

Spinners and Sliders

The JavaFX Spinner and Slider classes correspond to Swing components as follows:

JavaFX Widget Swing Component
Spinner JSpinner
 

 

To demonstrate their use, let's create a simple application that shows the temperature on both celsius and farenheit scales.

When the example is initially run, it looks like this:

        class Temp {
            attribute celsius: Number;
            attribute farenheit: Number;
            attribute showCelsius: Boolean;
            attribute showFarenheit: Boolean;
        }


        trigger on Temp.celsius = value {
            farenheit = (9/5 * celsius + 32);
        }

        trigger on Temp.farenheit = value {
            celsius = ((farenheit - 32) * 5/9);
        }


        Frame {

            var temp = Temp {
                farenheit: 32
                showFarenheit: true
                showCelsius: true
            }

            height: 300

            width: 400


            title: "Temperature"

            content: Box {
                orientation: VERTICAL
                content:
                [FlowPanel {
                    content:
                    [CheckBox {
                        text: "Show Celsius"
                        selected: bind temp.showCelsius
                    },
                    RigidArea {
                        width: 20
                    },
                    CheckBox {
                        text: "Show Farenheit"
                        selected: bind temp.showFarenheit
                    }]
                },
                 
       Slider {                     visible: bind temp.showCelsius                     min: -100                     max: 100                     border: TitledBorder {title: "Celsius"}                     value: bind temp.celsius                     minorTickSpacing: 5                     majorTickSpacing: 10                     paintTicks: true                     paintLabels: true                     labels:                     [SliderLabel {                          value: 0                          label: SimpleLabel {                              text: "0"                          }                     },                     SliderLabel {                          value: 100                          label: SimpleLabel {                              text: "100"                          }                     }]                 },
                 
       Slider {                     visible: bind temp.showFarenheit                     border: TitledBorder {title: "Farenheit"}                     min: -148                     max: 212                     paintTicks: true                     minorTickSpacing: 5                     majorTickSpacing: 10                     value: bind temp.farenheit                     paintLabels: true                     labels:                     [SliderLabel {                          value: 0                          label: SimpleLabel {                             text: "0"                          }                     },                     SliderLabel {                          value: 32                          label: SimpleLabel {                              text: "32"                          }                     },                     SliderLabel {                          value: 212                          label: SimpleLabel {                              text: "212"                          }                     }]                 },
                FlowPanel {
                     alignment: LEADING
                     content:
                     [SimpleLabel {
                          visible: bind temp.showCelsius
                          alignmentX: 1
                          text: "Celsius:"
                      },
                       
       Spinner {                           visible: bind temp.showCelsius                           min: -100                           max: 100                           value: bind temp.celsius                       },
                      RigidArea {
                          width: 20
                      },
                      SimpleLabel {
                          visible: bind temp.showFarenheit
                          alignmentX: 1
                          text: "Farenheit:"
                      },
                       
       Spinner {                           visible: bind temp.showFarenheit                           min: -148                           max: 212                           value: bind temp.farenheit                       }]
                 }]
            }

            visible: true

        }
    

The code related to the Spinners and Sliders is in bold. Both Spinners and Sliders have min and max attributes that determine their ranges and a value attribute that determines the current value.

In this example, the value attributes of the celsius Spinner and Slider are bound to the model's celsius attribute. The value attributes of the farenheit Spinner and Slider are bound to the model's farenheit attribute. Triggers have also been defined on the model's celsius and farenheit attributes to update the other attribute's value whenever one of them changes. As a result, if either slider is moved or either spinner is chnaged, all of the others will reflect this change.

For example, here's what it looks like if the temperature is set to 88 degrees farenheit:

Sliders also have several attributes that determine if and how tick lines should be displayed. In addition, by assigning a list of SliderLabels to a Slider's labels attribute you can attach labels to particular values. In this example, that has been done for the freezing and boiling points and for 0 degrees farenheit.

top

Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve