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.
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):
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.
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)
First, a look below at the FXML code that was generated by Scene Builder
/*
* 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
*/publicclassCommodityImageLabelextendsAnchorPane{@FXMLprotectedLabel directionLabel;@FXMLprotectedLabel commodityLabel;@FXMLprotectedLabel percentChangeLabel;@FXMLprivateImageView commodityImageView;protectedDecimalFormat decimalFormat =newDecimalFormat("#0.00");protectedString currentStyle ="";publicCommodityImageLabel(){FXMLLoader fxmlLoader =newFXMLLoader(
getClass().getResource("/fxml/CommodityImageLabel.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);try{
fxmlLoader.load();}catch(IOException exception){thrownewRuntimeException(exception);}}publicvoid setPercentChange(double percentChange){StringBuilder builder =newStringBuilder();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.
The DemoScreen.fxml component for this application currently looks as follows.
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.
Next, browse to the .jar file that contains your custom component.
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.
At this point the custom component is added to the Scene Builder pallette, and can be dragged on to the main UI.
Screen shot showing 2 CommodityImageLabels placed on the main application UI.
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.
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.
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
*/publicclassDemoScreenControllerimplementsInitializable{@FXMLprotectedCommodityImageLabel commodityLabel1;@FXMLprotectedCommodityImageLabel commodityLabel2;/**
* Initializes the controller class.
*/@Overridepublicvoid 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.
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.
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.
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.
Browse the the FXML file of the custom component.
The ButtonLabel now appears in the Custom section of the library and can be dragged onto the new Scene.
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.
Jar file is now executable and will run the sample application by default.
Better support for enumerations in JavaScript
Fixed a bug with setting the MapType after the Map had already been initialized.
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.
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.
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
/* 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 */publicclass 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
protectedvoid 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;
} elseif( inboundBean.isRecovered() ) {
cssStyle = vfcRecoveredStyle;
} elseif( 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.
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.
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
In NetBeans IDE there are now 3 projects.
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.
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.
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).
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)
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).
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() {
publicvoid 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.