Software Engineer and NASA contractor in the Washington D.C. area. Specializes in Java, the NetBeans Platform, D3.JS and ANSI C. Sean is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone. You can read more from them at their website. View Full User Profile

How to Integrate JavaFX into a NetBeans Platform Wizard (Part 1)

04.26.2013
| 6507 views |
  • submit to reddit

When working within the NetBeans Platform, Swing is King. JavaFX is the crown prince. However, some developers avoid developing GUI controls with JavaFX in the NetBeans Platform because Swing is available by default. Well, it is possible to develop your JavaFX forms and simply replace the default NetBeans panels. The following tutorial explains how a developer can take a JavaFX GUI form and FXML developed using Scene Builder and replace a NetBeans Platform Wizard visual panel with minimal effort. 

This tutorial builds upon the following previous entries:

http://netbeans.dzone.com/articles/javafx-fxml-meets-netbeans 

http://netbeans.dzone.com/articles/how-embed-javafx-chart-visual

If you are looking for the second part of this Wizard/JavaFX tutorial, go here:

http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part5


Now, why would this concept be useful? Well, consider a development team where new Java applications are being written in JavaFX.  Why rewrite the useful Panel classes to Swing just to use them within a NetBeans Platform Wizard? Why force new form development to be in Swing just to be compatible with a NetBeans Platform application?  NetBeans Platform applications are perfectly capable of rendering JavaFX interop'd with Swing. Here's how:

First you will need to do a little prep work to setup an application for this tutorial. Do the following:

  1. Create the JavaFX GUI. Create a new JavaFX FXML GUI using SceneBuilder.  Add the controls you want and generate your FXML file and controller class.

  2. Update your Controller Class by Extending JFXPanel. This is part of the Swing Interop pattern that we all know and love.  You will also need to @Override the getName() method so that the wizard framework can update the current step title.

  3. Encapsulate fields/values. Create public methods that will provide Wizard framework with the fields it needs to pass from panel to panel.  This is the same thing you would need to do with a standard Swing Wizard JPanel class.

    The code for your controller class still runs without a problem within your JavaFX application but is now Swing Interop compatible.  The code might look like this:

    package jfxwizpanel.jfxwiz;
    
    import java.io.File;
    import java.net.URL;
    import java.util.ResourceBundle;
    import javafx.embed.swing.JFXPanel;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.TextField;
    import javafx.stage.FileChooser;
    
    /**
     *
     * @author SPhillips (King of Australia)
     */
    public class WizPanelController extends JFXPanel implements Initializable {
        @FXML //  fx:id="browseButton"
        private Button browseButton; // Value injected by FXMLLoader
        @FXML //  fx:id="pathText"
        private TextField pathText; //Field that Path is stored in
        private String filePath = ""; //some value to pass to the next Wizard panel
        
        // Handler for Button[fx:id="browseButton"] onAction
        public void handleButtonAction(ActionEvent event) {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Select File");
            //Show open file dialog
            File file = fileChooser.showOpenDialog(null);
            if(file!=null) {
                setFilePath(file.getPath());
                pathText.setText(filePath);
            }
        }
        @Override // This method is called by the FXMLLoader when initialization is complete
        public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
            assert browseButton != null : "fx:id=\"browseButton\" was not injected: check your FXML file 'WizPanel.fxml'.";
            assert pathText != null : "fx:id=\"pathText\" was not injected: check your FXML file 'WizPanel.fxml'.";
            // initialize your logic here: all @FXML variables will have been injected
        }
        @Override //This method is used by Wizard Framework to generate list of steps
        public String getName() {
            return "FXML JFXPanel";
        }    
        /**
         * @return the filePath
         */
        public String getFilePath() {
            return filePath;
        }
        /**
         * @param filePath the filePath to set
         */
        public void setFilePath(String filePath) {
            this.filePath = filePath;
        }
    }
    And when you run this Code within the JavaFX FXML application you get something like the following screenshot:



  4. Create the NetBeans Platform Application. Create a new NetBeans Platform application and add a new module. Add a Wizard using the "Wizard" Wizard.

  5. Include the JavaFX Runtime. Create a NetBeans library wrapper module to include "jfxrt.jar" and set a dependency on it in the module described above.

  6. Copy Controller class and FXML file. As of NetBeans 7.3 you cannot refactor copy these files from your JavaFX FXML project to your NetBeans Platform application package.  After manually copying these two files you will need to do a manual replace of the package path in both the Controller class and the fx:controller string in the FXML file.

    Your FXML code might now look something like this:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import java.lang.*?>
    <?import java.util.*?>
    <?import javafx.geometry.*?>
    <?import javafx.scene.*?>
    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.web.*?>
    
    <AnchorPane id="AnchorPane" prefHeight="410.0" prefWidth="582.0" xmlns:fx="http://javafx.com/fxml" fx:controller="javafxwizard.jfxwiz.WizPanelController">
      <children>
        <VBox id="VBox" prefHeight="410.0000999999975" prefWidth="582.0" spacing="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
          <children>
            <AnchorPane id="AnchorPane">
              <children>
                <RadioButton layoutX="161.0" layoutY="14.0" mnemonicParsing="false" prefWidth="134.0" text="Prefer Swing" />
                <RadioButton layoutX="14.0" layoutY="14.0" mnemonicParsing="false" prefWidth="134.0" selected="true" text="Prefer JavaFX" />
              </children>
            </AnchorPane>
            <AnchorPane prefHeight="45.0" prefWidth="310.0">
              <children>
                <Button fx:id="browseButton" layoutX="486.0" mnemonicParsing="false" onAction="#handleButtonAction" prefHeight="45.0" prefWidth="72.0" text="Browse" />
                <TextField fx:id="pathText" layoutX="14.0" layoutY="4.0" prefHeight="41.0" prefWidth="461.0" />
              </children>
            </AnchorPane>
            <AnchorPane prefHeight="272.0" prefWidth="572.0">
              <children>
                <HTMLEditor prefHeight="272.0" prefWidth="572.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
              </children>
            </AnchorPane>
          </children>
          <padding>
            <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
          </padding>
        </VBox>
      </children>
    </AnchorPane>
    
  7. Replace Swing Panel with FXML Controller. At this point you can replace the autogenerated Swing JPanel class that would normally be loaded by the Wizard control class with your JavaFX FXML controller.  Remember we extended JFXPanel and it pays off here.  All we have to do now is follow our standard Swing Interop technique.  However this time we have to use our Platform.runLater() pattern in the getComponent() method of the Wizard controller class. 

    Below is the relevant code after the update.  Notice how little we had to change:

    public class JfxwizWizardPanel1 implements WizardDescriptor.Panel<WizardDescriptor> {
    
        /**
         * The visual component that displays this panel. If you need to access the
         * component from this class, just use getComponent().
         */
        //private JfxwizVisualPanel1 component;
        public WizPanelController component; //Replaces original autogenerated JPanel class 
    
        // Get the visual component for the panel. In this template, the component
        // is kept separate. This can be more efficient: if the wizard is created
        // but never displayed, or not all panels are displayed, it is better to
        // create only those which really need to be visible.
        @Override
        public WizPanelController getComponent() {
            if (component == null) {
                component = new WizPanelController(); //return new JFXPanel controller
                Platform.setImplicitExit(false);
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        createScene();  //standard Swing Interop Pattern
                    }
                });
            }
            return component;
        }
    
        private void createScene() {
            try {
                URL location = getClass().getResource("WizPanel.fxml"); //same FXML copied from JavaFX app
                FXMLLoader fxmlLoader = new FXMLLoader();
                fxmlLoader.setLocation(location);
                fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
                Parent root = (Parent) fxmlLoader.load(location.openStream());
                Scene scene = new Scene(root);
                component.setScene(scene);    
                component = (WizPanelController) fxmlLoader.getController();    
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        }

    At this point, you should be able to resolve any import issues, compile and run. You should see your JavaFX GUI nicely loaded within the Wizard Dialog frame like the screenshot below:


Wow that's awesome that you can load JavaFX GUIs into your wizards.  But you didn't do anything with the information, so you didn't actually leverage the WizardDescriptor framework.  Learn to do that in the next part of the Wizard/JavaFX tutorial here:

http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part5



Published at DZone with permission of Sean Phillips, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)