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
<?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.
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.
<?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