Reputation: 927
I would like to create a custom tag
/element
for my fxml
file but I do not want this element to be forced to inherit from a Pane
, Button
, TextField
and so on. Is there some interface that can be implemented in my custom element that would require me to implement lets say fx()
method which is required to return a Node
/Region
element which is supposed to be rendered in the Scene
. What I mean
if something like the following is possible?
public class CustomElement implements SOME_FXML_INTERFACE {
private String myArg;
public CustomElement(@NamedArg("myArg") myArg) {
this.myArg = myArg;
}
// method that is required to be implemented by SOME_FXML_INTERFACE
// this method retuns some GUI element which actually needs to be rendered in the Scene
@Override
public Object fx() {
return new TextField(myArg);
}
}
<HBox>
<CustomElement myArg="some_argument"/>
</HBox>
All this so that I could have CustomElement
who can accept custom argument in the contructor.
Upvotes: 0
Views: 344
Reputation: 209684
As well as using a factory method, as described by fabian, you can repurpose the builder mechanism to do this. As with <fx:factory>
, which is limited to static
methods with no arguments, the usage is a bit limited. However, here's an example factory/builder that creates either a TextField
or a Label
, depending on whether the editable
flag is set:
package org.jamesd.examples.fxmlfactory;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.util.Builder;
public class Factory implements Builder<Node> {
private String text = "" ;
private boolean editable = false ;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public boolean isEditable() {
return editable;
}
public void setEditable(boolean editable) {
this.editable = editable;
}
@Override
public Node build() {
if (editable) {
return new TextField(text);
} else {
return new Label(text);
}
}
}
Here's an FXML file that uses this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane ?>
<?import javafx.scene.layout.HBox ?>
<?import javafx.scene.Node ?>
<BorderPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1">
<center>
<HBox spacing="5">
<Node text="Hello" editable="false" />
<Node text="Name" editable="true" />
</HBox>
</center>
</BorderPane>
To make the FXMLLoader
aware of your builder, you have to specify a class to use it for. Here we can use it for Node
(but this requirement is a bit restrictive, as you'll be essentially limited to a single builder). You do this by creating a BuilderFactory
implementation:
package org.jamesd.examples.fxmlfactory;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Node;
import javafx.util.Builder;
import javafx.util.BuilderFactory;
public class NodeBuilderFactory implements BuilderFactory{
private final JavaFXBuilderFactory defaultFactory = new JavaFXBuilderFactory();
public Builder<?> getBuilder(Class<?> type) {
if (type == Node.class) {
return new Factory();
}
return defaultFactory.getBuilder(type);
}
}
and then registering it with the FXMLLoader
:
package org.jamesd.examples.fxmlfactory;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Example.fxml"));
loader.setBuilderFactory(new NodeBuilderFactory());
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Upvotes: 0
Reputation: 82491
You can create arbitrary objects, but factory objects can only be used by nodes that support this. (Technically you could do this but it would involve using a getter returning a new instance every time it's invoked.) However you can e.g. use your custom class as cellFactory
for a ListView
.
If you do not need to rely on an instance method, but are satisfied with a static
factory method, you can use the fx:factory
tag to specify a method to create the node instance and you do not need to implement any interface to do that:
package my.package;
...
public class CustomElement {
public static Node fx() {
return new TextField(myArg);
}
}
<?import my.package.CustomElement?>
<?import javafx.scene.layout.HBox?>
<HBox xmlns:fx="http://javafx.com/fxml">
<children>
<CustomElement fx:factory="fx" />
</children>
</HBox>
More information is available in the Introduction to FXML
BTW: If your class has a public
constructor not taking any parameters you can create an instance of that class simply by adding a element with the name of the class to the fxml. E.g. the following fxml results in an ArrayList
when loaded; the ways you can use those instances is limited though;
<?import java.util.ArrayList?>
<ArrayList />
Upvotes: 1