How to create custom JavaFX controls using inheritance

Ken Fogel investigates on the best method for making a custom JavaFX control, without FXML, that is available to the Scene Builder.

This article brings together information I reviewed from numerous sources to learn how to carry out a specific task—how to create a custom JavaFX control. There is much written on this topic and the general assumption is you are planning to group multiple standard components in an FXML layout that can be used as if it were a single control.

That is not what I wanted to do. Instead, I wanted to use a custom control created by extending an existing control in a class and without any FXML and then I wanted to make that custom JavaFX control available to the Scene Builder.

Let’s begin by looking at the final user interface of the application as seen when the program executes:

There are only two input fields. They are the ComboBox to select a letter and a TextField where the number of hours are entered. This simple application is a billing calculator with rules left over from the days of the dial up modem.

The custom control I wanted to create was a specialization of a JavaFX TextField into which the hours used in a month are entered. I wanted to use a regular expression to validate what was being entered into the text field. To accomplish this I needed to override a TextField’s “replaceText” and “replaceSelection” methods. Here is the code for the specialized TextField:

public class DoubleTextField extends TextField { public DoubleTextField() { super(); } //http://utilitymill.com/utility/Regex_For_Range String numberRegEx = "\\b([0-9]{1,2}|[1-6][0-9]{2}|7[0-3][0-9]|74[0-4])\\b"; @Override public void replaceText(int start, int end, String text) { String oldValue = getText(); if ((validate(text))) { super.replaceText(start, end, text); String newText = super.getText(); if (!validate(newText)) { super.setText(oldValue); } } } @Override public void replaceSelection(String text) { String oldValue = getText(); if (validate(text)) { super.replaceSelection(text); String newText = super.getText(); if (!validate(newText)) { super.setText(oldValue); } } } private boolean validate(String text) { return ("".equals(text) || text.matches(numberRegEx)); } }

The regular expression accepts only numbers and only within the range of integers from 0 to 744. Unfortunately the web site http://utilitymill.com/utility/Regex_For_Range is not online as of this writing. It was one of the easiest web sites to use when you needed a regular expression.

I wrote this DoubleTextField class over a year ago and while it worked as desired there were a few issues. I did not know how to make Scene Builder aware of the control. If I could make Scene Builder aware then I might be able to use this or other controls I create in other projects.

I could make Scene Builder accept the name of the control in the FXML file. All that was necessary was to edit an XML file and add the import for the DoubleTextField and then change the TextField for hours to a DoubleTextField. Here is an excerpt from the hand modified FXML file:

. . . <?import com.kenfogel.controls.*?> . . . <HBox alignment="CENTER" GridPane.columnSpan="2" GridPane.rowIndex="4"> <children> <DoubleTextField fx:id="planHours" alignment="CENTER_RIGHT"> <font> <Font name="Tahoma" size="14.0" /> </font> </DoubleTextField> </children> </HBox> . . .

This eliminated errors from the FXML file but the problem was that Scene Builder could not display the custom control. Here are two views in Scene Builder:

Notice that the TextField for the hours used in the month does not appear. It was fine when the application ran. The DoubleTextField does appear in the graph of the container but Scene Builder does not know how to display it.

Notice that the node labeled DoubleTextField has a yellow caution symbol. This indicates that Scene Builder does not know how to display this control even though it is an acceptable in the FXML.

In my research I learned that Scene Builder could import JAR or FXML files that contained custom controls. Since the custom control was defined in my project I decided to import the project’s jar file into Scene Builder. This is easy to do by clicking on the gear/settings icon adjacent to the Library label.

I browsed to my target directory in the project and included the project’s JAR . Doing this brought up a display of the custom controls that were found in the JAR.

I clicked on Import Component and a new category of control appeared called Custom. The JAR file was copied into Scene Builder’s library folder which on a Windows system is found at C:\Users\Ken\AppData\Roaming\Scene Builder\Library.

In the Custom list was my DoubleTextField.

All that was left to do was to delete the DoubleTextField in the Hierarchy and then drag the new DoubleTextField into its position in the HBox. Now Scene Builder displayed the field.

At this point I could have declared success and ended this article right here. However, two things bothered me. The first was the fact that the JAR file imported into Scene Builder’s library contained unused classes and libraries. The fix for this was simple. I created a new project, all my projects are Maven based, and copied just the package with the DoubleTextField class into the project. I edited the POM file removing anything not required for the control such as creating a shaded JAR and executing the code. I changed the default goal to:

<defaultGoal>clean compile package install</defaultGoal>

The install setting installed the JAR into the local Maven repository. I used the import JAR command in Scene Builder and added the JAR file from the repository into Scene Builder. Placing the JAR in the local repository resolved the second problem: how to easily reuse the component in other projects. While Scene Builder was now aware of DoubleTextField, the location where Scene Builder copied the JAR to was not on the project’s classpath. The solution was to add a dependency for the control in the POM file.

<dependency> <groupId>com.kenfogel</groupId> <artifactId>doubletextfield</artifactId> <version>1.0</version> </dependency>

Now, anytime I want to use this DoubleTextField all I need to do is add the dependency to a project and it can be imported into the source code. The custom control will remain available to Scene Builder as long as its JAR is in the Scene Builder library.

Now go forth and make custom controls!

Bibliography