SceneBuilder Custom Control Classpath


Question

I am at wit's end with SceneBuilder and Java FX's way of handling custom controls.

  1. Do I use fx:root, or do I NOT use it? I opted to use it. So within the Controller+Root class of the control, I set itself as the root and controller, just as required. But it still says "Root hasn't been set. Use method setRoot() before load."

  2. Within the parent FXML that will house the custom control, what am I supposed to import?

  3. What is the correct classpath so that I can display my custom control in SceneBuilder 2.0? I don't quite understand the "/.../.../.../bin" thing at all.

  4. With the new <fx:include> tag, is it necessary to use <MyCustomControl /> within the markup anymore?

So far, my custom control is nothing but a progress indicator, to avoid complications.

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

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


<fx:root type="HBox" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
   <children>
      <ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0" />
   </children>
</fx:root>

package application.ctrl;

import java.io.IOException;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import application.Main;

Its controller:

public class ProgressSelector extends HBox {

    @FXML
    private ProgressIndicator progressBar;



    public ProgressSelector() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource(
                Main.PROGRESS_SELECTOR));
        loader.setRoot(this);
        loader.setController(this);
        loader.setClassLoader(this.getClass().getClassLoader());
        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The parent FXML:

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

<?import javafx.scene.text.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.VBox?>
<?import application.ctrl.ProgressSelector?>

<VBox fx:id="vbox" spacing="5.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ctrl.ObjectiveEditorCtrl">
   <children>
      <HBox>
         <children>
            <Button mnemonicParsing="false" onAction="#back" text="&lt;" />
            <Label text="Editing Objective:" />
         </children>
      </HBox>
      <TextField fx:id="objectiveName" />
      <ProgressSelector />
      <HBox>
         <children>
            <Label text="Planned Start:" />
            <Region HBox.hgrow="ALWAYS" />
            <DatePicker fx:id="plannedStart" />
         </children>
      </HBox>
      <HBox>
         <children>
            <Label text="Actual Start:" />
            <Region HBox.hgrow="ALWAYS" />
            <DatePicker fx:id="actualStart" />
         </children>
      </HBox>
      <HBox>
         <children>
            <Label text="Planned Finish:" />
            <Region HBox.hgrow="ALWAYS" />
            <DatePicker fx:id="plannedFinish" />
         </children>
      </HBox>
      <HBox>
         <children>
            <Label text="Actual Finish:" />
            <Region HBox.hgrow="ALWAYS" />
            <DatePicker fx:id="actualFinish" />
         </children>
      </HBox>
      <HBox alignment="CENTER_RIGHT">
         <children>
            <Button mnemonicParsing="false" onAction="#save" text="Save" />
            <Button mnemonicParsing="false" onAction="#back" text="Cancel" />
         </children>
      </HBox>
   </children>
   <padding>
      <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
   </padding>
</VBox>

Its controller:

package application.ctrl;

import interfaces.ControlledScreenInterface;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import application.ScreenController;
import application.objects.Objective;

public class ObjectiveEditorCtrl implements Initializable,
        ControlledScreenInterface {

    @FXML
    private TextField objectiveName;
    @FXML
    private ProgressSelector completion;
    @FXML
    private DatePicker plannedStart;
    @FXML
    private DatePicker plannedFinish;
    @FXML
    private DatePicker actualStart;
    @FXML
    private DatePicker actualFinish;
    @FXML
    private VBox vbox;

    private ScreenController parent;
    private Objective current;



    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // TODO Auto-generated method stub

    }



    public void init(Objective current) {
        this.current = current;
        objectiveName.setText(current.getName());
        // completion.setProgress(current.getCompletion());
        plannedStart.setValue(current.getPlannedStart());
        actualStart.setValue(current.getActualStart());
        plannedFinish.setValue(current.getPlannedFinish());
        actualFinish.setValue(current.getActualFinish());
    }



    public void save() {
        // current.setName(objectiveName.getText());
        // // current.setCompletion(completion.getProgress());
        // current.setPlannedStart(plannedStart.getValue());
        // current.setPlannedFinish(plannedFinish.getValue());
        // current.setActualStart(actualStart.getValue());
        // current.setActualFinish(actualFinish.getValue());
        // back();
    }



    public void back() {
        parent.back();
    }



    @Override
    public void setParentScreen(ScreenController parent) {
        this.parent = parent;
    }
}
1
3
6/30/2014 10:38:53 PM

Accepted Answer

Many questions here.

1 - Using fx:root allows you to customize FXMLLoader, you can change the way you instanciate components: How to create multiple javafx controllers with different fxml files?

But, with SceneBuilder 2 it does not work well at all! The "canonic" way with Scene Builder 2 (worked fine with 1) seems to NOT use fx:root.

2 - It depends on your pattern chosen in 1. Could be a custom java component in FXML with an explicit import, or using fxml:include. Either FXML first then controller. Or abstract component (and controller as fx:root) then FXML.

3 - SceneBuilder 2 has many troubles with classloaders as each custom component is loaded with a distinct class loader which means you'll run a lot with class not found like exceptions. One way to fix it is to fork SceneBuilder to force it using only one classloader for all custom components: Custom Components in Scenebuilder 2.0

Or wait for a fix. Or not using fx:root and prefering fx:include. Or not using Scene Builder at all which is for many a valid solution.

2
5/23/2017 10:31:23 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon