I decided this deserved an own issue:
I tested the feature introduced in RT-23413 with the current Java 8 EAP (b74) and I liked it. However, I noticed that the fxml that is used as a template is not allowed to have a controller set:
========== Box.fxml ============
<?xml version="1.0" encoding="utf-8"?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.shape.*?>
<Group xmlns:fx="http://javafx.com/fxml"
fx:controller="template.SampleController"
fx:id="root">
<children>
<Rectangle layoutX="0" layoutY="0" width="75" height="50" fill="green"/>
<Label layoutX="0" layoutY="20" text="init"/>
</children>
</Group>
========== SampleController.java ===========
public class SampleController {
}
========== SampleApplication.java =========
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class SampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("Box.fxml"));
loader.setTemplate(true);
Group box1 = (Group) loader.load();
setStuff(box1, "Third", 300, 50);
Group box2 = (Group) loader.load();
setStuff(box2, "Second", 200, 50);
Group box3 = (Group) loader.load();
setStuff(box3, "First", 100, 50);
Group root = new Group();
root.getChildren().addAll(box1, box2, box3);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setStuff(Group box, String text, int x, int y) {
Label label = (Label) box.getChildren().get(1);
label.setText(text);
box.setLayoutX(x);
box.setLayoutY(y);
}
}
========================
throws:
Controller value already specified.
/C:/Users/rheinnecker/IdeaProjects/JavaFX%20Tests/out/production/JavaFX%20Tests/template/Box.fxml:10
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:752)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:809)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:209)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:592)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2366)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2177)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2076)
now, calling loader.setController(null) between the load() calls is an obvious fix for this. However, if I do this in my real application, I get the strange behavior that all generated Nodes by the FXMLLoader refer to the same Controller.
This picture shows our application how it should look like:
http://250kb.de/ZCwoopb
The nodes are generated using FXML and filled with data that is administrated by a controller class.
Using setTemplate(true), the application throws the above error.
Using setController(null) between the load() calls, this is what we get:
http://250kb.de/yNu5dPV
Notice that every single node is filled with the same content. Also note that the displayed content is the last loaded content.
Please advice on how to use controllers when setTemplate(true) is set.
%%%%%%%%%%%%%%------ EDIT ------%%%%%%%%%%%%%%%%%%%%%
here is a SSCCE that shows the issue I referenced above with the two screenshots in our application:
========== Box.fxml ============
<?xml version="1.0" encoding="utf-8"?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.paint.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="template.SampleController"
fx:id="root">
<children>
<Rectangle layoutX="0" layoutY="0" width="75" height="50" stroke="green" fill="${controller.color}"/>
<Label layoutX="0" layoutY="20" text="${controller.name}"/>
</children>
</VBox>
========== SampleController.java ===========
package template;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
public class SampleController{
public VBox root;
private ObjectProperty<Color> color = new SimpleObjectProperty<>(this, "color", Color.RED);
public ObjectProperty<Color> colorProperty(){
return color;
}
public final Color getColor() {
return color.get();
}
public final void setColor(Color value) {
color.set(value);
}
public StringProperty name = new SimpleStringProperty(this, "name", "Peter");
public StringProperty nameProperty(){
return name;
}
public final String getName() {
return name.get();
}
public final void setName(String value) {
name.set(value);
}
}
========== SampleApplication.java =========
package template;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.net.URL;
public class SampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
URL resource = getClass().getResource("Box.fxml");
System.out.println(resource);
loader.setLocation(resource);
loader.setTemplate(true);
// the first time we load a template
VBox box1 = (VBox) loader.load();
setLayout(box1, 300, 50);
SampleController controller = loader.getController();
controller.setName("Hans");
ColorBinder binder1 = new ColorBinder();
controller.colorProperty().bind(binder1.colorProperty());
loader.setController(null);
controller = null;
// second time, notice that the controller is forgotten.
VBox box2 = (VBox) loader.load();
setLayout(box2, 200, 50);
SampleController controller2 = loader.getController();
controller2.setName("Gustav");
ColorBinder binder2 = new ColorBinder();
controller2.colorProperty().bind(binder2.colorProperty());
loader.setController(null);
controller2 = null;
// third time, notice that the controller is forgotten.
VBox box3 = (VBox) loader.load();
setLayout(box3, 100, 50);
SampleController controller3 = loader.getController();
controller3.setName("Gunther");
ColorBinder binder3 = new ColorBinder();
controller3.colorProperty().bind(binder3.colorProperty());
Group root = new Group();
root.getChildren().addAll(box1, box2, box3);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
binder1.setColor(Color.AQUAMARINE);
binder2.setColor(Color.GOLD);
binder3.setColor(Color.GRAY);
}
private void setLayout(VBox box, int x, int y) {
box.setLayoutX(x);
box.setLayoutY(y);
}
class ColorBinder {
private ObjectProperty<Color> color = new SimpleObjectProperty<>(this, "color", Color.RED);
public ObjectProperty<Color> colorProperty(){
return color;
}
public final Color getColor() {
return colorProperty().get();
}
public final void setColor(Color value) {
colorProperty().set(value);
}
}
}
====================
explanation:
in SampleApplication.java, using the new template feature, I create three VBoxes with a Rectangle and a Label in them. Using databinding in fxml, I bind the fill and text values to properties in the controller class. The controller class is received after calling load() on the FXMLLoader. On each controller retrieved from the FXMLLoader, I set different values and bind different properties to the properties of the controller. The result shows three rectangles and labels that are _all set to the same values_.
I tested the feature introduced in RT-23413 with the current Java 8 EAP (b74) and I liked it. However, I noticed that the fxml that is used as a template is not allowed to have a controller set:
========== Box.fxml ============
<?xml version="1.0" encoding="utf-8"?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.shape.*?>
<Group xmlns:fx="http://javafx.com/fxml"
fx:controller="template.SampleController"
fx:id="root">
<children>
<Rectangle layoutX="0" layoutY="0" width="75" height="50" fill="green"/>
<Label layoutX="0" layoutY="20" text="init"/>
</children>
</Group>
========== SampleController.java ===========
public class SampleController {
}
========== SampleApplication.java =========
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class SampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("Box.fxml"));
loader.setTemplate(true);
Group box1 = (Group) loader.load();
setStuff(box1, "Third", 300, 50);
Group box2 = (Group) loader.load();
setStuff(box2, "Second", 200, 50);
Group box3 = (Group) loader.load();
setStuff(box3, "First", 100, 50);
Group root = new Group();
root.getChildren().addAll(box1, box2, box3);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setStuff(Group box, String text, int x, int y) {
Label label = (Label) box.getChildren().get(1);
label.setText(text);
box.setLayoutX(x);
box.setLayoutY(y);
}
}
========================
throws:
Controller value already specified.
/C:/Users/rheinnecker/IdeaProjects/JavaFX%20Tests/out/production/JavaFX%20Tests/template/Box.fxml:10
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:752)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:809)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:209)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:592)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2366)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2177)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2076)
now, calling loader.setController(null) between the load() calls is an obvious fix for this. However, if I do this in my real application, I get the strange behavior that all generated Nodes by the FXMLLoader refer to the same Controller.
This picture shows our application how it should look like:
http://250kb.de/ZCwoopb
The nodes are generated using FXML and filled with data that is administrated by a controller class.
Using setTemplate(true), the application throws the above error.
Using setController(null) between the load() calls, this is what we get:
http://250kb.de/yNu5dPV
Notice that every single node is filled with the same content. Also note that the displayed content is the last loaded content.
Please advice on how to use controllers when setTemplate(true) is set.
%%%%%%%%%%%%%%------ EDIT ------%%%%%%%%%%%%%%%%%%%%%
here is a SSCCE that shows the issue I referenced above with the two screenshots in our application:
========== Box.fxml ============
<?xml version="1.0" encoding="utf-8"?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.paint.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="template.SampleController"
fx:id="root">
<children>
<Rectangle layoutX="0" layoutY="0" width="75" height="50" stroke="green" fill="${controller.color}"/>
<Label layoutX="0" layoutY="20" text="${controller.name}"/>
</children>
</VBox>
========== SampleController.java ===========
package template;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
public class SampleController{
public VBox root;
private ObjectProperty<Color> color = new SimpleObjectProperty<>(this, "color", Color.RED);
public ObjectProperty<Color> colorProperty(){
return color;
}
public final Color getColor() {
return color.get();
}
public final void setColor(Color value) {
color.set(value);
}
public StringProperty name = new SimpleStringProperty(this, "name", "Peter");
public StringProperty nameProperty(){
return name;
}
public final String getName() {
return name.get();
}
public final void setName(String value) {
name.set(value);
}
}
========== SampleApplication.java =========
package template;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.net.URL;
public class SampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
URL resource = getClass().getResource("Box.fxml");
System.out.println(resource);
loader.setLocation(resource);
loader.setTemplate(true);
// the first time we load a template
VBox box1 = (VBox) loader.load();
setLayout(box1, 300, 50);
SampleController controller = loader.getController();
controller.setName("Hans");
ColorBinder binder1 = new ColorBinder();
controller.colorProperty().bind(binder1.colorProperty());
loader.setController(null);
controller = null;
// second time, notice that the controller is forgotten.
VBox box2 = (VBox) loader.load();
setLayout(box2, 200, 50);
SampleController controller2 = loader.getController();
controller2.setName("Gustav");
ColorBinder binder2 = new ColorBinder();
controller2.colorProperty().bind(binder2.colorProperty());
loader.setController(null);
controller2 = null;
// third time, notice that the controller is forgotten.
VBox box3 = (VBox) loader.load();
setLayout(box3, 100, 50);
SampleController controller3 = loader.getController();
controller3.setName("Gunther");
ColorBinder binder3 = new ColorBinder();
controller3.colorProperty().bind(binder3.colorProperty());
Group root = new Group();
root.getChildren().addAll(box1, box2, box3);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
binder1.setColor(Color.AQUAMARINE);
binder2.setColor(Color.GOLD);
binder3.setColor(Color.GRAY);
}
private void setLayout(VBox box, int x, int y) {
box.setLayoutX(x);
box.setLayoutY(y);
}
class ColorBinder {
private ObjectProperty<Color> color = new SimpleObjectProperty<>(this, "color", Color.RED);
public ObjectProperty<Color> colorProperty(){
return color;
}
public final Color getColor() {
return colorProperty().get();
}
public final void setColor(Color value) {
colorProperty().set(value);
}
}
}
====================
explanation:
in SampleApplication.java, using the new template feature, I create three VBoxes with a Rectangle and a Label in them. Using databinding in fxml, I bind the fill and text values to properties in the controller class. The controller class is received after calling load() on the FXMLLoader. On each controller retrieved from the FXMLLoader, I set different values and bind different properties to the properties of the controller. The result shows three rectangles and labels that are _all set to the same values_.