Reputation: 973
I have asked so many questions about Scene Builder that I am starting to feel like I might be a mild annoyance to some people. But now another problem has revealed itself. I previously posted this question in regards to importing nested custom nodes into Scene Builder. My knight in shining armor came through and helped me solve that issue. Now I have a new problem though. The symptoms are exactly the same but the cause seems to be different. Again I have no idea how to bug test this issue (I would be very happy if someone were to teach me how. I do not fully understand the bug testing methods of the last answer unfortunately.) but it seems to have come from nowhere.
So the issue is, just as before, that when I try to import a jar file containing a custom node with other nested nodes inside the outermost container doesn't get imported into Scene Builder. Last time around this was solved by adding
fxmlLoader.setClassLoader(getClass().getClassLoader());
to my java controller code before loading the fxml but this doesn't fix it this time. Here is all the source code. To be honest with you the issue is probably something quite dumb that I have overlooked and if this is the case I am sorry in advance.
Thanks for all help!
EDIT: I should ad that all jars run fine by themselves. They simply do not play nicely with Scene Builder.
EDIT 2: As pointed out in the comments I should try to make the problem a little more accessible, sorry about that. So here is the structure. The outermost component is what I call a NumberSlider. This contains the two other custom components the NumberField and the InfoIcon (see image below). The inner components are imported fine and the outer is not. The structure of the NumberSlider is basically a slider which has its valueProperty bound to the valueProperty of the NumberField. The NumberField is essencially a TextField that allows the user to input text to change its valueProperty and if the valueProperty changes the text changes to match this. I believe the problem lies in the making of the slider variable since this is the component that doesn't import as it should. This is the constructor.
//Properties
private final BooleanProperty logarithmic;
private final BooleanProperty ticks;
//Variables
@SuppressWarnings("unused")
private boolean lock = false;
//Structural Elements
@FXML private Label label;
@FXML private Slider slider;
@FXML public NumberField field;
@FXML private InfoIcon info;
public NumberSlider(@NamedArg("logarithmic") boolean logarithmic, @NamedArg("ticks") boolean ticks,
@NamedArg("intSlider") boolean intSlider, @NamedArg("value") double value,
@NamedArg("min") double min, @NamedArg("max") double max) {
this.logarithmic = new SimpleBooleanProperty(this, "logarithmic", logarithmic);
this.ticks = new SimpleBooleanProperty(this, "ticks", ticks);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("NumberSlider.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
fxmlLoader.setClassLoader(getClass().getClassLoader());
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
field.reconfigureLogic(value, min, max, intSlider);
//An awfully long code block that basically binds the two properties with change listeners in different ways depending on the input arguments.
}
The rest of the NumberSlider is only getters and setters for its various properties. I do not believe there is a problem with the changelisteners since they all work fine when running from my IDE.
EDIT 3: See the answer below for a great explanation. I just want to add that in my source code the NumberSlider needs getters and setters for all its properties, even those inherited from NumberField. Otherwise it won't play nicely with Scene Builder.
Upvotes: 1
Views: 869
Reputation: 45486
There is an easy fix to get your nested control working and being imported by Scene Builder.
The issue is related to the non empty constructor you have used for your controls.
While NumberField
works (as it can be imported) with this constructor:
public NumberField(@NamedArg("intField") boolean intField, @NamedArg("value") double value, @NamedArg("min") double min,
@NamedArg("max") double max);
The outer control NumberSlider
, that makes use of a NumberField
control, and has this constructor:
public NumberSlider(@NamedArg("logarithmic") boolean logarithmic, @NamedArg("ticks") boolean ticks,
@NamedArg("intSlider") boolean intSlider, @NamedArg("value") double value,
@NamedArg("min") double min, @NamedArg("max") double max);
fails to be imported.
It is not a classloader issue this time, but something related to the arguments, and the related default values.
@NamedArg default values
So what are the default values for those arguments when you are creating and instance of the control from Scene Builder?
false
0
0.0
This means that when you create a control you are calling:
final NumberSlider slider = new NumberSlider(false, false, false, 0, 0, 0);
And if you try that on your project, it will fail!!
Caused by: java.lang.IllegalArgumentException: MajorTickUnit cannot be less than or equal to 0.
at javafx.scene.control.Slider.setMajorTickUnit(Slider.java:397)
at com.mycompany.numberslider.NumberSlider.<init>(NumberSlider.java:118)
at com.mycompany.numberslider.NumberSliderApp.start(NumberSliderApp.java:20)
Because of this exception, the control fails to be imported from Scene Builder.
So now that we have found the problem, you have two options fo fix it:
I'll explain how to do the latter, which will be my preferred one.
But note that you will have to modify your code as well to avoid issues running the control, whenever the user selects invalid values.
@NamedArg defaultValue() method
If you check the NamedArg
annotation:
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface NamedArg {
/**
* The name of the annotated argument.
* @return the name of the annotated argument
*/
public String value();
/**
* The default value of the annotated argument.
* @return the default value of the annotated argument
*/
public String defaultValue() default "";
}
there are two methods, and typically we use just one: value()
.
But we could use both and modify the default ""
value, like this:
public NumberSlider(@NamedArg(value="logarithmic", defaultValue="false") boolean logarithmic,
@NamedArg(value="ticks", defaultValue="false") boolean ticks,
@NamedArg(value="intSlider", defaultValue="false") boolean intSlider,
@NamedArg(value="value", defaultValue="50") double value,
@NamedArg(value="min", defaultValue="0") double min,
@NamedArg(value="max", defaultValue="100") double max);
With this change, build your controls and jar, and try to import it again, now you will see the three controls with Scene Builder.
Note also that the preview of the control will reflect these new default values.
Upvotes: 3