Для использования пользовательского JavaFX-компонента в FXML-описании графа сцены требуется его адаптация.
При объявлении класса JavaFX-компонента в FXML-описании должен существовать способ автоматического создания экземпляра класса. Поэтому класс JavaFX-компонента должен либо иметь конструктор по умолчанию без аргументов, либо иметь статический метод-фабрику без аргументов для создания экземпляра класса, либо иметь класс, реализующий интерфейс javafx.util.Builder, и класс, реализующий интерфейс javafx.util.BuilderFactory.
Для передачи какого-либо параметра из FXML-описания в созданный экземпляр класса JavaFX-компонента в классе должна присутствовать пара методов get/set параметра.
В качестве примера рассмотрим использование пользовательского компонента кубической формы с гиперссылкой.
Главный класс приложения загружает FXML-описание графа сцены:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class JavaFXOGWebFXML extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("OGWeb.fxml"));
Scene scene = new Scene(root);
scene.setFill(Color.BLACK);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
FXML-описание графа сцены:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafxogwebfxml.*?>
<AnchorPane id="AnchorPane" prefHeight="600" prefWidth="800" xmlns:fx="http://javafx.com/fxml" >
<children>
<MenuContainer layoutX="50" layoutY="50" label="Home" />
<MenuContainer layoutX="160" layoutY="50" label="Services" />
<MenuContainer layoutX="270" layoutY="50" label="Blog" />
<MenuContainer layoutX="380" layoutY="50" label="Contacts" />
</children>
</AnchorPane>
Здесь объявляется пользовательский компонент MenuContainer, в экземпляр которого передается параметр label. В данном случае класс компонента MenuContainer имеет конструктор по умолчанию без аргументов:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.HyperlinkBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public class MenuContainer extends Group {
final Rotate rx = new Rotate(0,Rotate.X_AXIS);
final Rotate ry = new Rotate(0,Rotate.Y_AXIS);
final Rotate rz = new Rotate(0,Rotate.Z_AXIS);
private double size=50.0;
private Color colorContainer=Color.BLUE;
private double shade=2.0;
private Hyperlink link;
private Color colorLabel=Color.WHITE;
private Font font=Font.font("Georgia", 18);
public MenuContainer() {
super();
rx.setAngle(15);
ry.setAngle(15);
rz.setAngle(0);
this.getTransforms().addAll(rz, ry, rx);
final Group group=this;
//
final Rectangle backFace = RectangleBuilder.create() // back face
.width(size*2).height(size)
.fill(colorContainer.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
.translateX(-0.5*size)
.translateY(-0.5*size)
.translateZ(0.5*size)
.build();
//
final Rectangle bottomFace = RectangleBuilder.create() // bottom face
.width(size*2).height(size)
.fill(colorContainer.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
.translateX(-0.5*size)
.translateY(0)
.rotationAxis(Rotate.X_AXIS)
.rotate(90)
.build();
//
final Rectangle rightFace = RectangleBuilder.create() // right face
.width(size).height(size)
.fill(colorContainer.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
.translateX(1*size)
.translateY(-0.5*size)
.rotationAxis(Rotate.Y_AXIS)
.rotate(90)
.build();
//
final Rectangle leftFace = RectangleBuilder.create() // left face
.width(size).height(size)
.fill(colorContainer.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
.translateX(-1*size)
.translateY(-0.5*size)
.rotationAxis(Rotate.Y_AXIS)
.rotate(90)
.build();
//
final Rectangle topFace = RectangleBuilder.create() // top face
.width(size*2).height(size)
.fill(colorContainer.deriveColor(0.0, 1.0, (1 - 0.2*shade), 1.0))
.translateX(-0.5*size)
.translateY(-1*size)
.rotationAxis(Rotate.X_AXIS)
.rotate(90)
.build();
//
link = HyperlinkBuilder.create()
.textFill(colorLabel)
.font(font)
.build();
final StackPane face = new StackPane();
face.getChildren().addAll(RectangleBuilder.create() // face
.width(size*2).height(size)
.fill(colorContainer)
.build(),
link
);
face.setTranslateX(-0.5*size);
face.setTranslateY(-0.5*size);
face.setTranslateZ(-0.5*size);
link.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
} });
final Timeline animation = new Timeline();
animation.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, new KeyValue(rx.angleProperty(), 15d)),
new KeyFrame(new Duration(1000), new KeyValue(rx.angleProperty(), 375d))
);
animation.setCycleCount(Animation.INDEFINITE);
group.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
animation.play();
double x= event.getSceneX();
double y = event.getSceneY();
group.setLayoutX(x);
group.setLayoutY(y);
group.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
animation.stop();
rx.angleProperty().set(15);
}
});
} });
//
getChildren().addAll(backFace,bottomFace,rightFace,leftFace,topFace,face);
}
public String getLabel(){
return link.textProperty().get();
}
public void setLabel(String value){
link.textProperty().setValue(value);
}
}