I have a three stages JavaFX app (right now only the login/register/blank view) and I tried to communicate between them. I've created a super class and all stages extend that super class.
Now, when I run the main class like below, I get the runtime errors:
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
conn = DBUtils.connectToDB(DB_SCHEMA);
new LoginController(primaryStage);
}
Exception:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javafx.fxml.LoadException:
/C:/Mihai/Faculta/Anul%203/Licenta/Aplicatie/licenta-mihai-laza/out/production/licenta-mihai-laza/application/login.fxml:7
at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
at javafx.fxml/javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:105)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:941)
at javafx.fxml/javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:980)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:227)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:752)
at javafx.fxml/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3237)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3194)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3163)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3136)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3113)
at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:3106)
at licenta.mihai.laza/application.superclasses.StageSuperclass.<init>(StageSuperclass.java:20)
at licenta.mihai.laza/application.LoginController.<init>(LoginController.java:45)
at licenta.mihai.laza/application.Main.start(Main.java:21)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
... 1 more
Caused by: java.lang.InstantiationException: application.LoginController
at java.base/java.lang.Class.newInstance(Class.java:571)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:936)
... 24 more
Caused by: java.lang.NoSuchMethodException: application.LoginController.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3350)
at java.base/java.lang.Class.newInstance(Class.java:556)
... 25 more
[INFO] Connection to database closed
Exception running application application.Main
StageSuperclass.java:
public class StageSuperclass extends Stage {
// Frame window
private Stage stage;
private double xOffset = 0;
private double yOffset = 0;
public StageSuperclass(Stage stage, String fxmlFilePath) throws java.io.IOException {
this.stage = stage;
Parent root = FXMLLoader.load(getClass().getResource(fxmlFilePath));
Scene scene = new Scene(root, Color.TRANSPARENT);
root.setStyle("-fx-background-color: transparent;");
this.initStyle(StageStyle.UNDECORATED);
root.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
}
});
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
stage.setX(mouseEvent.getScreenX() - xOffset);
stage.setY(mouseEvent.getScreenY() - yOffset);
}
});
this.setScene(scene);
this.show();
}
public Stage getStage() {
return this.stage;
}
LoginController.java:
public class LoginController extends StageSuperclass {
// Constants
private static final String LOGIN_FXML_PATH = "/application/login.fxml";
private static final String WRONG_CREDENTIALS_MSG = "Datele de logare sunt gresite!";
private static final String BLANK_CREDENTIALS_MSG = "Introduceti username si parola!";
// PageObjects
RegisterController registerPage;
// FXML Objects
#FXML
private Button btnLogin;
#FXML
private Button btnIesire;
#FXML
private TextField tfdUsername;
#FXML
private TextField tfdParola;
#FXML
private Label lblInvalid;
#FXML
private Hyperlink lnkInregistrare;
// Constructor
public LoginController(Stage stage) throws IOException {
super(stage, LOGIN_FXML_PATH);
}
public void inregistrareLinkOnAction(ActionEvent event) throws IOException {
new RegisterController(this.getStage());
}
public void loginButtonOnAction(ActionEvent event) throws IOException {
if (!tfdUsername.getText().isBlank() && !tfdParola.getText().isBlank()) {
if (validareLogin()) {
new MainWindowController(this.getStage());
}
} else {
lblInvalid.setText(BLANK_CREDENTIALS_MSG);
}
}
public void iesireButtonOnAction(ActionEvent event) {
Stage stage = (Stage) btnIesire.getScene().getWindow();
stage.close();
}
private boolean validareLogin() {
List<String> columns = Arrays.asList(COL_USERNAME, COL_PAROLA);
List<String> whereValues = Arrays.asList(tfdUsername.getText(), tfdParola.getText());
if (DBUtils.select(columns, columns, whereValues, DBUtils.TABEL_UTILIZATORI.TABLE_NAME, Main.conn).size() == 1) {
return true;
} else {
lblInvalid.setText(WRONG_CREDENTIALS_MSG);
return false;
}
}
}
login.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.*?>
<AnchorPane prefHeight="300.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.LoginController">
<AnchorPane prefHeight="60.0" prefWidth="300.0" style="-fx-background-color: #0084b8;">
<children>
<Label alignment="CENTER" contentDisplay="CENTER" layoutY="17.0" prefHeight="17.0" prefWidth="300.0"
text="LOGIN" textAlignment="CENTER" textFill="WHITE">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
</children>
</AnchorPane>
<Button id="btnLogin" fx:id="btnLogin" layoutX="33.0" layoutY="216.0" mnemonicParsing="false"
onAction="#loginButtonOnAction" prefHeight="25.0" prefWidth="233.0" style="-fx-background-color: #0084b8;"
text="Login" textFill="WHITE">
<font>
<Font size="13.0"/>
</font>
</Button>
<TextField id="tfdUsername" fx:id="tfdUsername" layoutX="89.0" layoutY="82.0" promptText="User">
<font>
<Font size="14.0"/>
</font>
</TextField>
<PasswordField id="tfdParola" fx:id="tfdParola" layoutX="89.0" layoutY="123.0" promptText="Parola">
<font>
<Font size="14.0"/>
</font>
</PasswordField>
<Hyperlink id="lnkInregistrare" fx:id="lnkInregistrare" layoutX="118.0" layoutY="153.0"
onAction="#inregistrareLinkOnAction" text="Nu ai cont? Inregistreaza-te!">
<font>
<Font size="11.0"/>
</font>
</Hyperlink>
<Label layoutX="33.0" layoutY="87.0" text="User">
<font>
<Font size="14.0"/>
</font>
</Label>
<Label layoutX="32.0" layoutY="128.0" text="Parola">
<font>
<Font size="14.0"/>
</font>
</Label>
<Button id="btnCancel" fx:id="btnIesire" layoutX="33.0" layoutY="254.0" mnemonicParsing="false"
onAction="#iesireButtonOnAction" prefHeight="25.0" prefWidth="233.0" style="-fx-background-color: #0084b8;"
text="Iesire" textFill="WHITE">
<font>
<Font size="13.0"/>
</font>
</Button>
<Label id="lblInvalid" fx:id="lblInvalid" alignment="TOP_CENTER" contentDisplay="CENTER" layoutX="44.0"
layoutY="194.0" prefHeight="16.0" prefWidth="233.0" textAlignment="CENTER" textFill="RED">
<font>
<Font size="11.0"/>
</font>
</Label>
</AnchorPane>
Do you have any suggestions solving this exception?
The exception is caused because your FXML file specifies fx:controller="application.LoginController". This will cause the FXMLLoader to create an instance of application.LoginController by (effectively) calling its no-argument constructor. However, your LoginController class doesn't have a no-argument constructor: hence the exception:
Caused by: java.lang.NoSuchMethodException: application.LoginController.<init>()
The typical approach is to load the FXML file, and let the FXMLLoader create the controller for you. Except in quite advanced usage, you should not instantiate the controller class yourself, because the FXMLLoader does that for you.
Get rid of the inheritance in LoginController (i.e. it doesn't need to be a subclass of your other controller class, and certainly should not be a subclass of Stage). Then just do
public class MyApplication extends Application {
private double xOffset = 0 ;
private double yOffset = 0 ;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource(LOGIN_FXML_PATH));
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.UNDECORATED);
root.setOnMousePressed(mouseEvent -> {
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
});
root.setOnMouseDragged(mouseEvent -> {
stage.setX(mouseEvent.getScreenX() - xOffset);
stage.setY(mouseEvent.getScreenY() - yOffset);
});
primaryStage.show();
}
}
If you need to reuse the same functionality repeatedly, just create a separate class in the normal way:
public class StageDragHandler {
private double xOffset = 0 ;
private double yOffset = 0 ;
public Stage createStage(Parent root) {
Stage stage = new Stage();
configureStage(root, stage);
return stage ;
}
public void configureStage(Parent root, Stage stage) {
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.UNDECORATED);
root.setOnMousePressed(mouseEvent -> {
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
});
root.setOnMouseDragged(mouseEvent -> {
stage.setX(mouseEvent.getScreenX() - xOffset);
stage.setY(mouseEvent.getScreenY() - yOffset);
});
}
}
Then your application class reduces to:
public class MyApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource(LOGIN_FXML_PATH));
Parent root = loader.load();
new StageDragHandler().configureStage(root, primaryStage);
primaryStage.show();
}
}
and you can use similar code anywhere you need it.
Related
I have a desktop app that contains:
Main Class: that load the first fxml file -> SideBar.fxml
SideBar.fxml: contains BorderPane -> at the left of it, i create 2 buttons:
- the fist button: load sample fxml file
- the second button: load secondFxml file
sample.fxml: contains a tableView and a Button
secondFxml.fxml: contains a label
Controller: a class that control sample.fxml -> load random double values to a tableView
the issue is :
when i press the button (fill Table) in Pane 1 : it load the data to the tableView, untill now everything is going well
when i switch to the second pane and i return to the first pane the center border pane is reloaded again so the data of the tableView disappeared
what i want is when i return to the first pane the the table view stay as it was first
i try to hide the borderpane center but it doesn't work for me
i screenShot the issue:
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("SideBar.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 700, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
SideBarController:
public class SideBarController implements Initializable {
#FXML BorderPane borderPane;
public void openPane1(ActionEvent event) throws Exception {
loadScene("Sample.fxml");
}
public void openPane2(ActionEvent event) throws Exception {
loadScene("secondFxml.fxml");
}
private void loadScene(String sc) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(sc));
borderPane.setCenter(root);
}
#Override
public void initialize(URL location, ResourceBundle resources) { }
}
Controller:
public class Controller implements Initializable {
double[][] data = new double[5][5];
Random random = new Random();
ObservableList<double[]> observableLists = FXCollections.observableArrayList();
#FXML
TableView<double []> tableView = new TableView<>(observableLists);
#FXML
public void fillTable(ActionEvent event) throws IOException {
//Random Values
for (int i = 0; i <data.length ; i++) {
for (int j = 0; j <data[0].length ; j++) {
data[i][j]= random.nextDouble();
}
}
//Add data to ObservableLists
for (int i = 0; i <data.length ; i++) {
observableLists.add(data[i]);
}
//Create Columns
for (int i = 0; i <data[0].length ; i++) {
TableColumn<double[], Double> column= null;
column = new TableColumn<>("column "+i);
int finalI = i;
column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue()[finalI]));
tableView.getColumns().add(column);
}
// Fill TableView
tableView.setItems(observableLists);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
SideBar.fxml
<BorderPane fx:id="borderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.SideBarController">
<left>
<VBox prefHeight="400.0" prefWidth="173.0" style="-fx-background-color: black;" BorderPane.alignment="CENTER">
<children>
<Button mnemonicParsing="false" onAction="#openPane1" prefHeight="25.0" prefWidth="177.0" style="-fx-background-color: blue; -fx-border-color: white;" text="Pane 1" textFill="WHITE">
<VBox.margin>
<Insets top="50.0" />
</VBox.margin>
<font>
<Font name="System Bold" size="17.0" />
</font>
</Button>
<Button mnemonicParsing="false" onAction="#openPane2" prefHeight="25.0" prefWidth="176.0" style="-fx-background-color: blue; -fx-border-color: white;" text="Pane 2" textFill="WHITE">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<font>
<Font name="System Bold" size="17.0" />
</font>
</Button>
</children>
</VBox>
</left>
<center>
<Pane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<children>
<Label layoutX="163.0" layoutY="152.0" prefHeight="68.0" prefWidth="131.0" text="Home">
<font>
<Font size="46.0" />
</font>
</Label>
</children>
</Pane>
</center>
</BorderPane>
Sample.fxml
<Pane prefHeight="395.0" prefWidth="597.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<TableView fx:id="tableView" layoutX="77.0" layoutY="47.0" prefHeight="266.0" prefWidth="461.0" />
<Button layoutX="257.0" layoutY="329.0" mnemonicParsing="false" onAction="#fillTable" text="fill Table" />
</children>
</Pane>
SecondFxml.fxml
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="232.0" layoutY="153.0" text="Pane 2">
<font>
<Font size="46.0" />
</font>
</Label>
</children>
</Pane>
Do not reload from fxml when button is clicked. Do it once in initialize:
public class SideBarController implements Initializable {
#FXML BorderPane borderPane;
private Parent sample, secondFxml;
public void openPane1(ActionEvent event) throws Exception {
borderPane.setCenter(sample);
}
public void openPane2(ActionEvent event) throws Exception {
borderPane.setCenter(secondFxml);
}
private Parent loadScene(String sc) throws IOException {
return FXMLLoader.load(getClass().getResource(sc));
}
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
sample = loadScene("Sample.fxml");
secondFxml = loadScene("secondFxml.fxml");
} catch (IOException ex) {
ex.printStackTrace();
};
}
}
I have problem with my program.
I want to draw chart after providing data (coefficients of the equation).
I tried to change my import (it helped with some variables)
I changed java.awt* for javafx.scene ... (few imports).
FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
<children>
<TextField fx:id="factorA" layoutX="24.0" layoutY="598.0" prefHeight="33.0" prefWidth="106.0" text="a=" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField fx:id="factorB" layoutX="24.0" layoutY="630.0" prefHeight="33.0" prefWidth="106.0" text="b=" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField fx:id="factorC" layoutX="24.0" layoutY="674.0" prefHeight="33.0" prefWidth="106.0" text="c=" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" />
<TextField layoutX="158.0" layoutY="592.0" prefHeight="47.0" prefWidth="120.0" text="xMin=" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMin" />
<TextField layoutX="158.0" layoutY="650.0" prefHeight="47.0" prefWidth="120.0" text="xMax=" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" fx:id="xMax" />
<Label fx:id="label" layoutX="468.0" layoutY="629.0" prefHeight="61.0" prefWidth="276.0" text="f(x)=" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0">
<font>
<Font size="18.0" />
</font>
</Label>
<LineChart fx:id="drawChart" prefHeight="598.0" prefWidth="800.0" title="Chart">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="button" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" prefHeight="61.0" prefWidth="98.0" text="Rysuj wykres" />
</children>
</AnchorPane>
The part with LineChart (fx:id="drawChart") generated communicate: "Unresolved fx:id reference
My main class:
package grahps;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private Controller controller;//
#Override
public void start(Stage stage) throws Exception {
stage.show();
System.out.println(getClass().getResource("/fxml/sample.fxml").getPath());
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sample.fxml"));
loader.setController(controller);
Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
Scene scene = new Scene(root, 800, 800);
controller.drawChart(stage);
stage.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
And finally my controller:
package grahps;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
public class Controller {
#FXML
TextField factorA;
#FXML
TextField factorB;
#FXML
TextField factorC;
#FXML
TextField xMin;
#FXML
TextField xMax;
#FXML
Label label;
#FXML
Button button;
#FXML
XYChart.Series<Number, Number> chart;
//Parser Text Field -> double
double xMax1 = Double.parseDouble(xMax.getText());
double xMin1 = Double.parseDouble(xMin.getText());
double a = Double.parseDouble(factorA.getText());
double b = Double.parseDouble(factorB.getText());
double c = Double.parseDouble(factorC.getText());
#FXML
public void drawChart(Stage stage) {
XYChart.Series<Number, Number> series = chart;
series.setName("Chart");
final NumberAxis xAxis = new NumberAxis(xMin1, xMax1, 1);
final NumberAxis yAxis = new NumberAxis();
yAxis.setTickUnit(1);
xAxis.setLabel("X Label");
yAxis.setLabel("Y Label");
final javafx.scene.chart.LineChart<Number, Number> lineChart = new javafx.scene.chart.LineChart<Number, Number>(xAxis, yAxis);
double y;
String pattern;
if (a == 0 && c == 0) {
pattern = "f(x)=" + factorB;
label.setText(pattern);
} else if (c == 0) {
pattern = "f(x)=" + factorA + "x+" + factorB;
label.setText(pattern);
for (double i = xMin1; i <= xMax1; i++) {
y = a * i + b;
series.getData().add(new XYChart.Data(i, y));
}
} else {
pattern = "f(x)=" + factorC + "x^2" + factorA + "x+" + factorB;
label.setText(pattern);
for (double i = xMin1; i < xMax1; i++) {
y = a * i * i + b * i + c;
series.getData().add(new XYChart.Data(i, y));
}
}
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 800);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
}
Please notice one thing: this is my first JavaFX project.
Simply I want to fill in coefficients of the equation and generate equation pattern + draw chart after clicking button.
I'll be greaftul for help.
When I deleted #FXML annotation above method and compiled code I got those errors:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:748)
Caused by: javafx.fxml.LoadException:
/C:/Users/Damian/IdeaProjects/Graphs/target/classes/fxml/sample.fxml:14
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at grahps.Main.start(Main.java:22)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
... 1 more
Caused by: java.lang.NullPointerException
at grahps.Controller.<init>(Controller.java:31)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 17 more
Exception running application grahps.Main
Process finished with exit code 1
Also I changed onAction="drawChart" for onAction="chart" and now I can see highlighted communicate: "cannot set javafx.scene.control.LineChart to chart"
There are several bugs in the code, e.g.:
The //Parser Text Field -> double-block in the controller is executed too early => NullPointerException.
When calling controller.drawChart(stage) in the start-method controller equals null => NullPointerException.
In the drawChart-method in the controller series equals null, because chart equals null => NullPointerException.
A reference to the chart is missing in the controller. This has already been noted in the comments.
CategoryAxis is used as the type of the x-axis, although the x-data are numerical values.
Before fixing these bugs, the architecture should be improved (this will automatically fix some of the bugs):
Controller-class: Since the chart has to be initialized and then updated with every click on the button, the following changes in the controller would be useful:
Implement an initialize-method, in which the necessary initializations can be made.
Implement an updateChart-method, which is called when the button is clicked and which updates the chart.
Define a reference to the LineChart.
Define references to both axes.
Thus, the Controller-class looks as follows:
public class Controller {
#FXML
TextField factorA;
#FXML
TextField factorB;
#FXML
TextField factorC;
#FXML
TextField xMin;
#FXML
TextField xMax;
#FXML
Label label;
#FXML
Button button;
#FXML
LineChart<Number, Number> chart;
#FXML
NumberAxis xAxis;
#FXML
NumberAxis yAxis;
#FXML
public void updateChart() {/*ToDo*/}
public void initialize(){/*ToDo*/}
}
Main-class: In the start-method only the FXML needs to be loaded:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/sample.fxml"));
Scene scene = new Scene(root, 800, 800);
stage.setScene(scene);
stage.show();
}
FXML: The following changes should be made:
Change the ID of the LineChart to fx:id="chart"
Change the type of the x-axis of the LineChart to NumberAxis
Add onAction="#updateChart" to the button. This calls the updateChart-method when the button is clicked.
Define an ID for both axes (fx:id="xAxis" and fx:id="yAxis").
Remove all initializations with non-numeric characters (e.g. text="a=") for all text fields, otherwise there are problems with parsing (it makes more sense to use labels or watermarks, e.g. promptText="a").
Then, the FXML becomes:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="grahps.Controller">
<children>
<TextField fx:id="factorA" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="a" />
<TextField fx:id="factorB" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="37.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="b" />
<TextField fx:id="factorC" prefHeight="33.0" prefWidth="106.0" AnchorPane.bottomAnchor="1.0" AnchorPane.leftAnchor="24.0" AnchorPane.rightAnchor="670.0" promptText="c" />
<TextField fx:id="xMin" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="61.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMin" />
<TextField fx:id="xMax" prefHeight="47.0" prefWidth="120.0" AnchorPane.bottomAnchor="3.0" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="522.0" promptText="xMax" />
<Label fx:id="label" prefHeight="61.0" prefWidth="276.0" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="468.0" AnchorPane.rightAnchor="56.0" text="f(x)=" >
<font>
<Font size="18.0" />
</font>
</Label>
<LineChart fx:id="chart" prefHeight="598.0" prefWidth="800.0" title="Chart">
<xAxis>
<NumberAxis fx:id="xAxis" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="button" prefHeight="61.0" prefWidth="98.0" layoutX="317.0" layoutY="612.0" mnemonicParsing="false" text="Rysuj wykres" onAction="#updateChart" />
</children>
</AnchorPane>
With these changes, an empty chart is displayed when the application is started (since no initialization has yet been implemented). Clicking on the button has no effect (since no updating has yet been implemented).
Initialization:
public void initialize(){
initChartProperties();
initInputControls();
XYChart.Series<Number, Number> series = getSeries();
chart.getData().add(series);
}
The getSeries-method essentially contains the logic of the drawChart-method:
private XYChart.Series<Number, Number> getSeries() {
double xMax1 = Double.parseDouble(xMax.getText());
double xMin1 = Double.parseDouble(xMin.getText());
double a = Double.parseDouble(factorA.getText());
double b = Double.parseDouble(factorB.getText());
double c = Double.parseDouble(factorC.getText());
XYChart.Series<Number,Number> series = new XYChart.Series<Number, Number>();
series.setName("Chart");
String pattern;
if (a == 0 && c == 0) {
pattern = "f(x)=" + factorB.getText();
label.setText(pattern);
} else if (c == 0) {
pattern = "f(x)=" + factorA.getText() + "x+" + factorB.getText();
label.setText(pattern);
for (double i = xMin1; i <= xMax1; i++) {
double y = a * i + b;
series.getData().add(new Data<Number, Number>(i, y));
}
} else {
pattern = "f(x)=" + factorA.getText() + "x^2+" + factorB.getText() + "x+" + factorC.getText();
label.setText(pattern);
for (double i = xMin1; i < xMax1; i++) {
double y = a * i * i + b * i + c;
series.getData().add(new Data<Number, Number>(i, y));
}
}
return series;
}
The initInputControls-method initializes the input controls, e.g.:
private void initInputControls() {
xMax.setText("100.0");
xMin.setText("10.0");
factorA.setText("1.0");
factorB.setText("2.0");
factorC.setText("3.0");
}
The initChartProperties-method initializes the chart:
private void initChartProperties() {
chart.setAnimated(true);
xAxis.setLabel("X Label");
yAxis.setLabel("Y Label");
}
If you want to display an empty chart at startup, simply remove the last three lines in the initialize-method.
Updating: Updating simply deletes the old series and adds the new series to the chart:
#FXML
public void updateChart() {
XYChart.Series<Number, Number> series = getSeries();
chart.getData().clear();
chart.getData().add(series);
}
After these changes, the application behaves as expected. The figure on the left shows the application after startup, the figure on the right after updating the input values.
Some things can still be improved, e.g. the application does not scale properly if the window size is changed. In addition, the validation of the input fields is missing. Chart animation can be disabled in the initChartProperties-method.
UPDATE:
If no symbols are to be displayed, add in the initChartProperties-method:
chart.setCreateSymbols(false);
The result is:
I have an desktop application based on javafx.The only requirement is that it has to run continuously for 5-6 days.I am in testing phase.
UI component used in this:
6 Gauges (Medusa)
Line chart that is stacked on JFXDrawer and is controlled by a hamburger-button.
Out of 6 gauges 3 gauges and line chart has to be updated on a period of a sec. For this i have used a thread which update this component per second.
I have tested this application for almost 3 time:
For Three days
For 26 hours(In which graph is working fine and values are updating in the gauges but the gauge needle is stuck at a particular value)
For 24 hours now the gauge needle is moving but gauge value is stuck and the Line chart drawer is not open only the hamburger-icon is changing.
There is no Exception in log and the UI is not hanging, Just Ui components are not responsive.
I did profiling but everything seems OK.I have read this post also but in his case the ui is hanging and i have no issue regarding this, all the button are clicky and i am able to change the screens with not issue.
After reloading the Screen All gets back to normal.
sample code i've used to update the component.
Controller
public class testGaugeController implements Initializable {
// Component
private static final int MAX_DATA_POINTS = 1000;
private int xSeriesData = 0;
private final XYChart.Series<Number, Number> series1 = new XYChart.Series<>();
private ExecutorService executor;
private final ConcurrentLinkedQueue<Number> dataQ1 = new ConcurrentLinkedQueue<>();
private NumberAxis xAxis,yAxis;
LineChart<Number, Number> lineChart;
DatabaseHandler dh = new DatabaseHandler();
Connection connect = dh.MakeConnection();
#FXML
private JFXDrawer drawer;
#FXML
private JFXHamburger burger;
#FXML
private Gauge Gauge;
/**
* Initializes the controller class.
*
* #param url
* #param rb
*/
public void initialize(URL url, ResourceBundle rb) throws IOException {
initializeRecorder();
start_recording();
}
private void initializeRecorder() throws IOException {
try {
xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 100);
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(true);
xAxis.setTickLabelsVisible(true);
xAxis.setTickMarkVisible(true);
xAxis.setMinorTickVisible(true);
yAxis = new NumberAxis();
// Create a LineChart
lineChart = new LineChart<Number, Number>(xAxis, yAxis) {
// Override to remove symbols on each data point
#Override
protected void dataItemAdded(XYChart.Series<Number, Number> series, int itemIndex, XYChart.Data<Number, Number> item) {
}
};
lineChart.setAnimated(false);
lineChart.setTitle("");
lineChart.setHorizontalGridLinesVisible(true);
series1.setName("Test Value");
lineChart.getData().addAll(series1);
drawer.setSidePane(lineChart);
drawer.setOverLayVisible(false);
} catch (Exception e) {
}
HamburgerBackArrowBasicTransition burgermove = new HamburgerBackArrowBasicTransition(burger);
burgermove.setRate(-1);
burger.addEventHandler(MouseEvent.MOUSE_PRESSED, (evt) -> {
burgermove.setRate(burgermove.getRate() * -1);
burgermove.play();
if (drawer.isShown()) {
drawer.close();
} else {
drawer.open();
}
});
}
int count_executer_status = 0;
boolean initial_start_trend = true;
private void start_recording() {
if (initial_start_trend) {
initial_start_trend = false;
} else {
xSeriesData = 0;
System.out.println("Clearing dataQue");
dataQ1.clear();
series1.getData().clear();
}
xAxis.setLowerBound(0);
count_executer_status++;
System.out.println("Cleared dataQue");
executor = Executors.newCachedThreadPool((Runnable r) -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
});
count_executer_status = 0;
AddToQueue addToQueue = new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
private class AddToQueue implements Runnable {
String query = "SELECT test_value FROM test_data_reader ORDER BY test_data_reader_id DESC LIMIT 1";
ResultSet rs;
#Override
public void run() {
try {
// add a item of random data to queue.
rs = dh.getData(query, connect);
if (rs.next()) {
double test_value = Double.parseDouble(rs.getString("test_value"));
dataQ1.add(test_value);
String Record_data = "INSERT INTO `test_data_record` (`test_value`, `date_time`) VALUES( '" + rs.getString("test_value") + "', NOW());";
dh.execute(Record_data, connect);
Platform.runLater(() -> {
Gauge.setValue(test_value);
});
}
xaxis_count++;
Thread.sleep(1000);
executor.execute(this);
} catch (Exception ex) {
}
}
}}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline() {
// Every frame to take any data from queue and add to chart
new AnimationTimer() {
#Override
public void handle(long now) {
addDataToSeries();
}
}.start();
}
int xaxis_count = 0;
private void addDataToSeries() {
try {
for (int i = 0; i < 20; i++) {
//-- add 20 numbers to the plot+
if (dataQ1.isEmpty()) {
break;
}
series1.getData().add(new XYChart.Data<>(xaxis_count, dataQ1.remove()));
}
if (series1.getData().size() > MAX_DATA_POINTS) {
series1.getData().remove(0, series1.getData().size() - MAX_DATA_POINTS);
}
// update
xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
xAxis.setUpperBound(xSeriesData - 1);
} catch (Exception e) {
}
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXDrawer?>
<?import com.jfoenix.controls.JFXHamburger?>
<?import eu.hansolo.medusa.Gauge?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="e913_300mt.testGaugeController">
<children>
<VBox prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<HBox fx:id="sectionHeader" minHeight="-Infinity" prefHeight="50.0" prefWidth="600.0" style="-fx-background-color: #2F333E;">
<children>
<VBox alignment="CENTER" layoutX="10.0" layoutY="10.0" HBox.hgrow="ALWAYS" />
<VBox alignment="CENTER" HBox.hgrow="ALWAYS" />
<VBox alignment="CENTER" HBox.hgrow="ALWAYS" />
<VBox alignment="CENTER" HBox.hgrow="ALWAYS" />
<VBox alignment="CENTER" HBox.hgrow="ALWAYS">
<children>
<JFXHamburger fx:id="burger" />
</children>
</VBox>
</children>
</HBox>
<HBox prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<children>
<Gauge fx:id="gauge" autoScale="false" borderPaint="#0099ff" borderWidth="3.0" decimals="0" foregroundPaint="#0000000b" highlightSections="true" innerShadowEnabled="true" knobType="METAL" lcdDesign="RED" lcdFont="LCD" majorTickMarkType="PILL" majorTickSpace="250.0" markersVisible="true" maxValue="3000.0" mediumTickMarkType="TRAPEZOID" mediumTickMarksVisible="false" minorTickSpace="50.0" needleSize="THIN" needleType="VARIOMETER" shadowsEnabled="true" threshold="300.0" title="Test Gauge" unit="unit" HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</HBox.margin>
</Gauge>
</children>
</HBox>
</children>
</VBox>
<JFXDrawer fx:id="drawer" defaultDrawerSize="600.0" direction="RIGHT" layoutY="56.0" prefHeight="344.0" prefWidth="600.0" />
</children>
</AnchorPane>
Libraries
jfoenix-8.0.1-for-java8.jar Github
fontawesomefx-8.9.jar bitbucket
Medusa-8.0.jar jar-download
For testing purpose i am only updating one gauge.And system Configuration is :
OS : Ubuntu 18.04 lts
Processor : Intel core i5-8400
Ram : 8Gb
Any Idea on this.
I am trying to make a GUI for a application and I have run into a problem when trying to change items after the fxmlloader loaded a file in.
At the start I thought it was that I wanted to edit the items before they were initialised (the rendering of the file was still going and i tried to edit the items before that was done). But then I tried to make a timer that every second (for testing purposes such a long time) checked the state of the object and if the object wasn't null it would do something.
However I noticed that the items never stop being null and thus I can't edit them. Anyone has a idea how to change this? I get this problem with Text and GridPane objects so far but not with buttons :/
The controller (the part that matters):
protected void sceneSetter(String filename) {
//loads the FXMLFile of the right sceneand makes the scene 1280x720 (720p)
try {
root = FXMLLoader.load(getClass().getResource(filename));
} catch (IOException e) {
System.out.println("RIP scene setting");
}
Scene scene = new Scene(root, 1280, 720);
//sets the right scene and displays it
stage.setScene(scene);
stage.show();
if(filename.equals("/InterfaceUserChange.fxml")){
final Timer timer = new Timer(1000, null);
timer.addActionListener(new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
System.out.println("tried it");
if(firstLoginContainer != null){
TextField field = new TextField() {
#Override public void replaceText(int start, int end, String text) {
// If the replaced text would end up being invalid, then simply
// ignore this call!
if (!text.matches("[0-9]*")) {
super.replaceText(start, end, text);
}
}
#Override public void replaceSelection(String text) {
if (!text.matches("[0-9]*")) {
super.replaceSelection(text);
}
}
};
firstLoginContainer.add(field , 1 , 1);
timer.stop();
}
}
});
if(firstLoginContainer == null) {
timer.start();
}
System.out.println("textfield added?");
}
The fxml file (its in a borderpane):
<left>
<VBox fx:id="firstLoginPaneContainer">
<HBox styleClass="profilePane">
<VBox styleClass="pictureDummy">
<ImageView>
<Image url="#testpica200px.jpg"/>
</ImageView>
</VBox>
<GridPane styleClass="firstLoginContainer"
alignment="TOP_LEFT" hgap="10" vgap="10">
<padding>
<Insets top="25" right="25" bottom="10" left="25"/>
</padding>
<Text text="Name: "
GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<TextField fx:id="changeName"
GridPane.rowIndex="0" GridPane.columnIndex="1"/>
<Text text="Age: "
GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<Text text="Study: "
GridPane.rowIndex="2" GridPane.columnIndex="0"/>
<Text text="Courses: "
GridPane.rowIndex="3" GridPane.columnIndex="0"/>
<Text text="Preferences: " fx:id="selfPreferences"
GridPane.rowIndex="4" GridPane.columnIndex="0"/>
<Text text="E-mail: " fx:id="changeEmail"
GridPane.rowIndex="5" GridPane.columnIndex="0"/>
<Text text="Phone number: " fx:id="selfPhonenumber"
GridPane.rowIndex="6" GridPane.columnIndex="0"/>
</GridPane>
</HBox>
</VBox>
</left>
I'm writing a custom control which displays an error icon and a message in a tooltip if the validation in a form fails. My version without the custom control looks like this:
<HBox>
<TextField fx:id="name"></TextField>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorTooltip"/>
</tooltip>
</Label>
</HBox>
The result is this:
My efforts to create a custom control lead to this:
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">
<children/>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorToolTip"/>
</tooltip>
</Label>
</fx:root>
This is the code behind the fxml:
package control;
[imports omitted for brevity]
#DefaultProperty(value = "children")
public final class ValidatedControl extends HBox implements Initializable {
#FXML
private Label error;
#FXML
private Tooltip errorToolTip;
private StringProperty errorToolTipProperty;
public ValidatedControl() {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ValidatedControl.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setErrorToolTip(final String errorToolTip) {
this.getErrorToolTipProperty().setValue(errorToolTip);
}
public String getErrorToolTip() {
return this.getErrorToolTipProperty().getValueSafe();
}
#Override
public void initialize(final URL location, final ResourceBundle resources) {
this.errorToolTip.textProperty().bind(this.getErrorToolTipProperty());
this.error.visibleProperty().bind(this.getErrorToolTipProperty().isNotEmpty());
}
public StringProperty getErrorToolTipProperty() {
if (this.errorToolTipProperty == null) {
this.errorToolTipProperty = new SimpleStringProperty();
}
return this.errorToolTipProperty;
}
}
I can use the control in fxml but the child component I add is always the last child which means the error icon is displayed to its left.
My control is used like this:
<ValidatedControl>
<TextField>
</TextField>
</ValidatedControl>
How do I get it to display the icon on the right side?
Now I do understand your problem. This might not fix your problem when you add your ValidatedControl in FXML, but when you do it programmatically try this:
ValidatedControl vc = new ValidatedControl();
TextField textField = new TextField();
vc.getChildren().add(textField);
textField.toBack();
Another way would be to go ItachiUchiha's way and add a Pane in your FXML as first child. But instead of overwriting the getChildren() method, write a new Method addNode(Node n) and add the node to the Pane.
forget about my first answer ;)