Animations with JavaFX: So Easy a Chimp Can Do It

Ok, well maybe its slightly more complicated than that, but not much. Things such as animations which were do-able with Swing, but were so painful, are baked into the JavaFX API and are relatively straightforward to implement.

In last month’s blog post, I demoed a JavaFX application which had 2 logos slowly scrolling across the app’s background. The code which was needed in order to acheive this effect was suprisingly minimal. The relevant code snippet from the application’s controller class is below.

public class AppController {

@FXML
protected ImageView logoImage;

@FXML
protected AnchorPane rootPane;

public void animateLogo() {
    TranslateTransition tt = 
    new TranslateTransition(Duration.seconds(30), logoImage);

    tt.setFromX( -(logoImage.getFitWidth()) );
    tt.setToX( rootPane.getPrefWidth() );
    tt.setCycleCount( Timeline.INDEFINITE );
    tt.play();
}
}

In the snippet above, we use a TranslationTransition which will move a node along a specified path over a specified time interval. The easiest way to do this is to give it the start point, end point, and the amount of time to run the animation for.

The TranslationTransition is created with a duration of 30 seconds, using the logoImage ImageView object that was injected by JavaFX into this controller class. Next, the starting point is set by specifying the fromX value on the transition. In this case, we want the animation to start the logo just off of the screen, so we specify a negative X value that is the width of the logo node. The toX value is set to the width of the entire UI so that the logo will scroll completely off the screen.

Finally, before playing the animation, the cycle count is set to INDEFINITE which means that as soon as the logo animation is complete, it will be automatically started again from the beginning, and keep doing so indefinitely.

That’s it, other transitions are equally as easy such as fading and scaling. Animations can also be chained together to run in sequence or set up to be run in parallel with other transitions.

Once again, from last month’s post, here’s a video showing animations in action. In particular, there are 2 logos which are animated on the Scene’s background that are scrolling in opposite directions across the screen.

 

Have fun!

twitter: @RobTerpilowski

Written with StackEdit.

Adding a Custom JavaFX Component to Scene Builder 2.0 (Part 2)

In a previous post I demonstrated how to add a custom component to Scene Builder 2.0 which was basically a short cut for copying & pasting FXML from the custom component into the FXML of the UI control which contained the component.

In this post I will demonstrate how to add a component as what I will call a ‘customized’ FXML component. Where the component will be encapsulated in its own custom FXML tag.

So to start off with, let’s say we have a label which contains a commodity name, image, and change in price. We’d like to have a collection of these commodity image labels on the main UI. Scene Builder is used to design the custom label (below):

enter image description here

Once the component is laid out, be sure that the “Use fxroot:construct” option is selected under the “Document” -> “Controller” accordion item on the left pane of Scene Builder.

enter image description here

In the project, for the custom component you should have the CommodityImageLabel.fxml file (the component just created in Scene Builder), and then create a CommodityImageLabel.java class to act as the component’s controller. (More on this in just a second)

enter image description here

First, a look below at the FXML code that was generated by Scene Builder

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.effect.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>

<fx:root styleClass="glass-pane" type="AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="110.0" prefWidth="675.0" styleClass="glass-pane" stylesheets="@zoi.css">
          <children>
              <Label fx:id="percentChangeLabel" layoutX="480.0" layoutY="14.0" text="- 0.23%" AnchorPane.rightAnchor="14.0">
                  <styleClass>
                      <String fx:value="label-text" />
                      <String fx:value="header" />
                  </styleClass>
              </Label>
              <Label fx:id="commodityLabel" layoutX="90.0" layoutY="28.0" text="Wheat">
                  <font>
                      <Font name="System Bold" size="50.0" />
                  </font>
                  <padding>
                      <Insets left="25.0" right="25.0" />
                  </padding>
                  <styleClass>
                      <String fx:value="label-text" />
                      <String fx:value="header" />
                  </styleClass>
              </Label>
              <Label fx:id="directionLabel" layoutX="120.0" layoutY="3.0" styleClass="label-direction-text" text="Long">
                  <opaqueInsets>
                      <Insets bottom="20.0" />
                  </opaqueInsets>
              </Label>
                <ImageView fx:id="commodityImageView" fitHeight="75.0" fitWidth="75.0" layoutX="14.0" layoutY="18.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="15.0" AnchorPane.topAnchor="15.0">
               <image>
                  <Image url="@../images/ZW.png" />
               </image></ImageView>
          </children>
      </AnchorPane>
   </children>
</fx:root>

Ok, on to the contorller class for the component.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.zoicapital.commoditylabelwidget;

import java.io.IOException;
import java.text.DecimalFormat;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;

/**
 *
 * @author robbob
 */
public class CommodityImageLabel extends AnchorPane {

    @FXML
    protected Label directionLabel;

    @FXML
    protected Label commodityLabel;

    @FXML
    protected Label percentChangeLabel;

    @FXML
    private ImageView commodityImageView;

    protected DecimalFormat decimalFormat = new DecimalFormat("#0.00");
    protected String currentStyle = "";

    public CommodityImageLabel() {
        FXMLLoader fxmlLoader = new FXMLLoader(
                getClass().getResource("/fxml/CommodityImageLabel.fxml"));

        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

    }

    public void setPercentChange(double percentChange) {

        StringBuilder builder = new StringBuilder();
        String percentChangeString = decimalFormat.format(Math.abs(percentChange) * 100.0);
        String style;
        if ("0.00".equals(percentChangeString)) {
            style = Style.LABEL_PLAIN;
        } else {
            if (percentChange < 0) {
                builder.append("-");
                style = Style.LABEL_RED;
            } else {
                builder.append("+");
                style = Style.LABEL_GREEN;
            }
        }

        builder.append(percentChangeString);
        builder.append("%");

        percentChangeLabel.setText(builder.toString());

        if (!style.equals(currentStyle)) {
            getStyleClass().remove(currentStyle);
            currentStyle = style;
            getStyleClass().add(currentStyle);
        }

    }

}

 

First off, notice that it extends AnchorPane, and also that the constructor loads the component’s FXML and then tells the loader that ‘this’ will be acting as the component’s controller.
That’s it for creating the custom component. Build it, jar it up, and its ready to be imported into Scene Builder and used in an application.


Using the Custom Component in Scene Builder

The new application will need to include the previously created .jar file as a dependency, in this case the “CommodityLabelWidget.jar” file.

enter image description here

The DemoScreen.fxml component for this application currently looks as follows.

enter image description here

In order to get the CommodityImageLabel into Scene Builder’s pallete, select the small “gear” icon in the Library header of Scene Builder, and select the “Import JAR/FXML File…” item.
enter image description here

Next, browse to the .jar file that contains your custom component.
enter image description here

Once the .jar is selected, the import dialog should display a list of all the custom components in the .jar file. In this case the CommodityImageLabel is the only component available.

enter image description here

At this point the custom component is added to the Scene Builder pallette, and can be dragged on to the main UI.

enter image description here

Screen shot showing 2 CommodityImageLabels placed on the main application UI.

enter image description here

Now, in order to be able to access these custom component’s from your main UI’s controller class, enter an fx:id for the components in Scene Builder’s “Code” section for each component. Scene Builder may complain that it isn’t able to find an injectable field with the name you just entered, but don’t worry, you’ll be adding it to the UI’s controller shortly.
Save the .FXML file in Scene Builder and then head back over to the IDE.

enter image description here

Below is the FXML file generated by Scene Builder. Take note of the CommodityImageLabel “custom” FXML tags, also note that the controller for this class is “DemoScreenController” which is shown next.

<?xml version="1.0" encoding="UTF-8"?>

<?import com.zoicapital.commoditylabelwidget.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>


<AnchorPane id="AnchorPane" prefHeight="632.0" prefWidth="889.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.zoicapital.demolabelproject.DemoScreenController">
   <children>
      <Label layoutX="72.0" layoutY="48.0" text="My Commodities">
         <font>
            <Font name="System Bold" size="36.0" />
         </font>
      </Label>
      <Separator layoutX="72.0" layoutY="109.0" prefHeight="4.0" prefWidth="289.0" />
      <CommodityImageLabel fx:id="commodityLabel1" layoutX="72.0" layoutY="144.0" />
      <CommodityImageLabel fx:id="commodityLabel2" layoutX="72.0" layoutY="268.0" />
   </children>
</AnchorPane>

In order to access the custom label components, be sure to name the fields the same as the fx:id controller name that you specified in Scene Builder, and to annotate the fields with the @FXML annotation.
Once this is complete you will have access to the CommodityImageLabel’s underlying controller classes, and can manipulate the custom component.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package com.zoicapital.demolabelproject;

import com.zoicapital.commoditylabelwidget.CommodityImageLabel;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

/**
 * FXML Controller class
 *
 * @author robbob
 */
public class DemoScreenController implements Initializable {


    @FXML
    protected CommodityImageLabel commodityLabel1;

    @FXML
    protected CommodityImageLabel commodityLabel2;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        commodityLabel1.setPercentChange(50.0);
        commodityLabel2.setPercentChange(-4.55);
    }    

}

The unfortunate wrinkle to this is that Scene Builder does not automatically pick up changes to the custom components. Meaning if you were to make a modification to the CommodityImageLabel component, you would need to manually re-import the .jar file in to Scene Builder in order to see the changes. This should hopefully be something that is addressed in a future version of Scene Builder.

twitter: @RobTerpilowski

Adding Custom JavaFX Components to Scene Builder 2.0

With the release of Scene Builder 2.0 it is now much easier to add custom and 3rd party JavaFX controls to the component palette.

There are a couple of different strategies to add a custom component to Scene Builder, and this blog post will illustrate creating a custom component with FXML and importing the FXML file into Scene Builder so the custom component can be used within Scene Builder itself.

To start with, I’ll create a simple custom component in Scene Builder which will consist of a Button and a Label, which I have cleverly named a ButtonLabel. A screenshot of Scene Builder with the fancy new custom component is below.

enter image description here

Below is the FXML that SceneBuilder generates. As you can see I have an action defined on the button, but no controller defined for the component. This is because the button will end up pointing to whatever controller the Scene that this component gets bundled into. The other thing to note is that this FXML file contains no references to stylesheets or outside images.

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="70" fx:id="label" />
    </children>
</AnchorPane>

Now to add the new component to Scene Builder so it can be added to an application, you must import the FXML file into Scene Builder by selecting the icon next to the search box at the top of the Library Pane.

enter image description here

Browse the the FXML file of the custom component.
enter image description here

The ButtonLabel now appears in the Custom section of the library and can be dragged onto the new Scene.
enter image description here

Below is the code that is generated by Scene Builder for the new Scene with the LabelButton. As can be viewed below, Scene Builder simply imports the custom FXML code into the new scene, and also adds the #handleButtonAction to the com.lynden.myapp.FXMLController class.

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="379.0" prefWidth="442.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="com.lynden.myapp.FXMLController">
    <children>
        <TableView layoutX="40.0" layoutY="28.0" prefHeight="200.0" prefWidth="356.0">
            <columns>
                <TableColumn prefWidth="75.0" text="C1" />
                <TableColumn prefWidth="75.0" text="C2" />
            </columns>
        </TableView>
        <AnchorPane id="AnchorPane" layoutX="108.0" layoutY="256.0" prefHeight="109.0" prefWidth="220.0">
            <children>
                <Button fx:id="button" layoutX="172.0" layoutY="43.0" onAction="#handleButtonAction" text="Click Me!" AnchorPane.rightAnchor="10.0" />
                <Label fx:id="label" layoutX="14.0" layoutY="31.0" minHeight="16" minWidth="69" prefHeight="47.0" prefWidth="100.0" text="Custom Text" />
            </children>
        </AnchorPane>
    </children>
</AnchorPane>

twitter: @RobTerp

GMapsFX Version 1.1.0

GMapsFX 1.1.0 has been released and supports the following new features and bug fixes.

We are at also looking at breaking the Java-to-JavaScript abstraction layer out of this project and creating its own project for it, as this layer could prove useful for interacting with other JavaScript applications within a JavaFX WebView.

We will have project artifacts published to Maven Central in the very near future.

The latest GMapsFX.jar file can be found here

Project documentation can be found here and with Javadocs located here

My Interview with TechTarget.com on JavaFX, Swing, and the NetBeans Rich Client Platform

I sat down with Jan Stafford of Tech Target at JavaOne in September and discussed our experiences with beginning to migrate to JavaFX from Swing while continuing to use the NetBeans Rich Client Platform (RCP) as the foundation for our application.

I discuss some of the pros of using JavaFX and the NetBeans RCP, such as having the ability to develop very polished looking components using effects such as drop shadows, reflections, and gradients  which may have been do-able in Swing, but were extremely painful and time consuming.  These effects however are baked into the JavaFX framework.

The largest negative at this point has been the learning curve with getting up to speed on Cascading Style Sheets.  Since much of the look and feel of the UI can be applied via CSS, it took a bit of work to learn how to use the style sheets to properly style the UI components.  However, the benefit to using CSS is that the work can be given to a UX/UI developer rather than a Java developer, thus truly decoupling the UI development from the business logic development.

A link to the interview on Tech Target’s website is below.

Interview with Tech Target: JavaFX beats Swing: Changing RIA development platform

techTargetInterview

twitter: @RobTerp

Using a Custom TableCell Factory to Format a JavaFX table in FXML

We are in the process of designing a table within our JavaFX UI which is being developed using Scene Builder and FXML with CSS for the UI styling.  The table contains information about shipping containers with a requirement that the table row be color coded based on the status of the particular container.  The row will need to be red if the container’s arrival has not yet been planned for, green if its arrival has been planned, but it has not yet arrived, and the default background if the container has been planned for and it has arrived.

I could not find an online tutorial which covered this type of conditional formatting with the UI written in FXML and CSS, so I wanted to document a solution for this problem.

The first step is to define the various formats for the table rows in the application’s CSS file

/* Table text should always be white */

.table-cell { -fx-text-fill: white; -fx-font-size: 14px; } /* Style for default table rows */ .defaultTableStyle { -fx-font-size: 12px; -fx-base: rgb(0,0,0,0); -fx-background-color: linear-gradient( to top, derive(-fx-color, 35.77966101694915%) 0%, derive(-fx-color, 10.05084745762713%) 50%, derive(-fx-color, -8.559322033898283%) 50.5%, derive(-fx-color, -30.0%) 100%); -fx-background-image: url('low_contrast_linen.png'); -fx-background-color: rgba(0,0,0,.75); -fx-border-color: gray; -fx-border-width: 1 1 0 0; }

 

 


/* Format for table rows where container's arrival

has been planned */

.planAssigned { -fx-font-size: 12px; -fx-base: rgb(0,0,0,0); -fx-background-color: #347b34; -fx-border-color: gray; -fx-border-width: 1 1 0 0; } /* Format for row where a plan has not yet been assigned */ .planNotAssigned { -fx-font-size: 12px; -fx-base: rgb(0,0,0,0); -fx-background-color: #983737; -fx-border-color: gray; -fx-border-width: 1 1 0 0; }

/* Format for table row where container has arrived */ .vfcRecovered { -fx-font-size: 12px; -fx-base: rgb(0,0,0,0); -fx-background-color: linear-gradient( to top, derive(-fx-color, 35.77966101694915%) 0%, derive(-fx-color, 10.05084745762713%) 50%, derive(-fx-color, -8.559322033898283%) 50.5%, derive(-fx-color, -30.0%) 100%); -fx-background-image: url('low_contrast_linen.png'); -fx-background-color: rgba(0,0,0,.75); -fx-border-color: gray; -fx-border-width: 1 1 0 0; }

The next step is to create a new custom table cell factory which will be used to select the correct CSS style for the table row based on the container’s status.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.lynden.fx.table;

import com.lynden.fx.InboundBean;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;

/**
 *
 * @author ROBT
 */
public class FormattedTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {

    public FormattedTableCellFactory() {
    }

    @Override
    public TableCell<S, T> call(TableColumn<S, T> p) {
        TableCell<S, T> cell = new TableCell<S, T>() {
            @Override
            protected void updateItem(Object item, boolean empty) {
                // CSS Styles
                String planNotAssignedStyle = "planNotAssigned";
                String planAssignedStyle = "planAssigned";
                String vfcRecoveredStyle = "vfcRecovered";
                String defaultTableStyle = "defaultTableStyle";
                String cssStyle = "";

                InboundBean inboundBean = null;
                if( getTableRow() != null ) {
                    inboundBean = (InboundBean) getTableRow().getItem();
                }

                //Remove all previously assigned CSS styles from the cell.
                getStyleClass().remove(planAssignedStyle);
                getStyleClass().remove(planNotAssignedStyle);
                getStyleClass().remove(vfcRecoveredStyle);
                getStyleClass().remove(defaultTableStyle);

                super.updateItem((T) item, empty);

                //Determine how to format the cell based on the status of the container.
                if( inboundBean == null ) {
                    cssStyle = defaultTableStyle;
                } else if( inboundBean.isRecovered() ) {
                    cssStyle = vfcRecoveredStyle;
                } else if( inboundBean.getVfcPlan() != null && inboundBean.getVfcPlan().length() > 0 ) {
                    cssStyle = planAssignedStyle;
                } else {
                    cssStyle = planNotAssignedStyle;
                }

                //Set the CSS style on the cell and set the cell's text.
                getStyleClass().add(cssStyle);
                if( item != null ) {
                    setText( item.toString()  );
                } else {
                    setText( "" );
                }
            }
        };
        return cell;
    }
}

The final step in the process is to specify the custom factory in the TableView section of the FXML file.  .

First, in the FXML file, define the packages that contain the custom factory classes.

<?xml version="1.0" encoding="UTF-8"?>

<?import com.lynden.fx.*?>
<?import com.lynden.fx.table.*?>

 

The next step is to then specify the FormattedTableCellFactory as the cellFactory (highlighted in red) for the columns in the TableView.

<TableView fx:id="table" prefHeight="451.0" prefWidth="727.0" styleClass="main-back"> <columns> <TableColumn text=""> <cellFactory> <FormattedTableCellFactory /> </cellFactory> </TableColumn> <TableColumn prefWidth="120.0" text="Plan"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="vfcPlan" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="55.0" text="VFC #"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="vfcNumber" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="100.0" text="Location"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="location" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="100.0" text="Arrival"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="arrivalTime" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="35.0" text="Org"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="origin" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="35.0" text="Dst"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="destination" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="65.0" text="Shipments"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="shipments" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="125.0" text="Consignee"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="consignee" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="35.0" text="Haz"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="hazMat" /> </cellValueFactory> </TableColumn> <TableColumn prefWidth="45.0" text="Temp"> <cellFactory> <FormattedTableCellFactory /> </cellFactory> <cellValueFactory> <PropertyValueFactory property="temperature" /> </cellValueFactory> </TableColumn> </columns> <items>

<!—Sample data to illustrate cell formatting –-> <FXCollections fx:factory="observableArrayList"> <InboundBean arrivalTime="03-11-13 15:00" consignee="Fred Meyer" destination="ANC" hazMat="N" location="Consignee" origin="TAC" shipments="1" temperature="KFF" vfcNumber="345440" vfcPlan="" /> <InboundBean arrivalTime="03-11-13 14:00" consignee="Fred Meyer" destination="ANC" hazMat="N" location="Yard" origin="TAC" shipments="1" temperature="KFF" vfcNumber="123456" vfcPlan="" /> <InboundBean arrivalTime="03-11-13 19:00" consignee="Fred Meyer" destination="ANC" hazMat="N" location="Trip 12543" origin="TAC" shipments="1" temperature="KFF" vfcNumber="235555" vfcPlan="Fred Meyer" /> <InboundBean recovered="true" arrivalTime="03-12-13 10:00" consignee="Costco" destination="KNA" hazMat="N" location="Trip 551332" origin="TAC" shipments="1" temperature="KFF" vfcNumber="244000" vfcPlan="KNA" /> <InboundBean arrivalTime="05-23-13 15:00" consignee="Lowes" destination="ANC" eta="05-23-13 15:00" hazMat="N" location="Yard" origin="TAC" shipments="5" temperature="KFF" vfcNumber="123456" vfcPlan="Fred Meyer" /> <InboundBean arrivalTime="05-23-13 15:00" consignee="Lowes" destination="ANC" eta="05-23-13 15:00" hazMat="N" location="Yard" origin="TAC" shipments="5" temperature="KFF" vfcNumber="123456" vfcPlan="Fred Meyer" /> <InboundBean arrivalTime="05-23-13 15:00" consignee="Lowes" destination="ANC" eta="05-23-13 15:00" hazMat="N" location="Yard" origin="TAC" shipments="5" temperature="KFF" vfcNumber="123456" vfcPlan="Fred Meyer" /> </FXCollections> </items> </TableView>

That’s it. By utilizing this strategy, cells in the JavaFX TableView can be conditionally formatted using custom cell factories and CSS.  The table below is the product of the strategy that was implemented in the above code.

image

twitter: @RobTerp

Add a JavaFX Component Built with Scene Builder and FXML to a NetBeans RCP App

Most of the tutorials and demos I have seen up to this point for integrating JavaFX with NetBeans Rich Client Platform (RCP) applications have created and configured JavaFX components by using the JavaFX Java APIs that are now shipped as part of the JDK as opposed to using the FXML markup language.  This seems logical since if one is already coding a panel or container in Java, it makes sense to instantiate and configure JavaFX components for that container in Java as well.  The disadvantage to this is that you are not able to leverage the benefits of Scene Builder to design your JavaFX components.  Scene Builder is a WYSIWYG design tool for creating components and UIs in JavaFX.  Behind the scenes (pun intended) the application generates an FXML representation of the UI, and does *not* generate Java code.

In the following example I will create a JavaFX component with Scene Builder and add it to a NetBeans RCP TopComponent window alongside a plain vanilla Swing JLabel.


Creating the Application

The first step in the process is to create an ordinary NetBeans  RCP application.  We use maven, so I create a new NetBeans application from the Maven Category

image

In NetBeans IDE there are now 3 projects.

image


Creating a JavaFX Runtime Wrapper Module

The next step is not create a wrapper module for the JavaFX runtime library.  If you are running Java 1.7.0_06 or higher (and you should be if you are developing a JavaFX app), you don’t need to include any of the native libraries in the wrapper module, only the jfxrt.jar itself needs to be included.

I created a new module called jfxrt (JavaFX runtime), and include the jfxrt-2.2.jar in it, which you can find in the JRE’s lib directory.  Below is a screenshot of the jfxrt wrapper module in the IDE.

image

You will then need to update the pom.xml file for the wrapper to include any JavaFX packages that you want to access within your application.  Below is a code snippet showing the JavaFX packages that my app will need in order to run.

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>nbm-maven-plugin</artifactId>
   <version>3.7</version>
   <extensions>true</extensions>
   <configuration>
       <useOSGiDependencies>true</useOSGiDependencies>
       <publicPackages>
          <publicPackage>javafx.scene.layout</publicPackage>
          <publicPackage>javafx.application</publicPackage>
          <publicPackage>javafx.collections</publicPackage>
          <publicPackage>javafx.scene.control</publicPackage>
          <publicPackage>javafx.event</publicPackage>
          <publicPackage>javafx.geometry</publicPackage>
          <publicPackage>javafx.beans.property</publicPackage>
          <publicPackage>javafx.beans.value</publicPackage>
          <publicPackage>javafx.concurrent</publicPackage>
          <publicPackage>javafx.scene.web</publicPackage>
          <publicPackage>javafx.fxml</publicPackage>
       </publicPackages>
   </configuration>
</plugin>

Now the fun begins

I create a new NetBeans module called “NumberLabelModule”, which contains a TopComponent to host both the Swing JLabel and JavaFX label.  Unfortunately, it doesn’t appear that the NetBeans IDE WYSIWYG designer, Matisse supports drag-and-drop of JFXPanel, which is the Swing panel that can contain JavaFX components, so the JavaFX portion must be added to the TopComponent by hand. (I attempted to add the JFXPanel to the IDE’s component palette to make use of it in Matisse, but when I attempted to drag the JFXPanel onto the TopComponent the IDE froze and had to be killed with extreme prejudice).

image


Building the JavaFX Label with Scene Builder

For this project the next step is to place a style sheet in the “Other Sources” portion of the project, which the JavaFX component will use for its formatting.

JavaFX Scene Builder is then used to graphically design the new JavaFX label and generate the  FXML markup code, which will be saved right along side the style sheet in the “Other Sources” portion of the project. (NumberLabel.fxml)

image


Adding JavaFX to the TopComponent

Once the component is designed and saved, the .fxml file within the IDE is updated automatically.  The final step is to add the JavaFX component to the TopComponent along with a JLabel.  The TopComponent contains a JPanel that uses a FlowLayout with the JLabel added to it as the first component. (See below).

image

The JavaFX label will be the 2nd component added to the panel, and this is done in the constructor of the TopComponent as follows.

public final class SwingFXDemoTopComponent extends TopComponent {

    JFXPanel jfxPanel = new JFXPanel();

    public SwingFXDemoTopComponent() {
        initComponents();
        putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, Boolean.TRUE);
        putClientProperty(TopComponent.PROP_UNDOCKING_DISABLED, Boolean.TRUE);
        setName(Bundle.CTL_SwingFXDemoTopComponent());
        setToolTipText(Bundle.HINT_SwingFXDemoTopComponent());

        Platform.setImplicitExit(false);
        // create JavaFX scene
        Platform.runLater(new Runnable() {
            public void run() {
                Parent root;
                try {
                    root = FXMLLoader.load(getClass().getResource("/com/lynden/fxlabel/NumberLabel.fxml"));
                    Scene scene = new Scene(root);
                    jfxPanel.setScene(scene);
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        });
        jPanel1.add(jfxPanel);
    }

A few more details on the above code:

 Platform.setImplicitExit(false);

This will prevent the JavaFX runtime from exiting when TopComponents are moved around the NetBeans RCP windowing system or when a TopComponent is undocked from the main application frame.  If this is not set, what you will see is that your JavaFX components will disappear when you undock a TopComponent from the main window.

      Platform.runLater(new Runnable() {
            public void run() {
                Parent root;
                try {
                    root = FXMLLoader.load(getClass().getResource("/com/lynden/fxlabel/NumberLabel.fxml"));
                    Scene scene = new Scene(root);
                    jfxPanel.setScene(scene);
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        });
        jPanel1.add(jfxPanel);

 

The code snippet above will create the JavaFX label within the JavaFX event thread (separate from the Swing event thread).  The first step of the process is to load the FXML representation of the JavaFX label that was built in Scene Builder.  Once the root node is loaded it can then be added to a new Scene.  The Scene can then be added to a JFXPanel, which as mentioned earlier, is a Swing component that can contain JavaFX components.   The final step is to then add the JFXPanel to the JPanel that the JLabel is living on.


Final Result

Once this is set up, the application can be run, and displays Swing and JavaFX components (built with FXML) living side by side in perfect harmony.

image

twitter: @RobTerp