Issue
I need to get to my stackpane that is nested inside a borberpane which is also nested in another borderpane(root).
I did not create this by code, I used scene-builder/FXML.
I am now trying to access it through code. The closes I got was by using a parent and got a unmodifiable list which seems to only have its two child but I cant seem to go deeper.
Here is the Hierarchy---
<BorderPane fx:id="rootPane" focusTraversable="true" minHeight="0.0" minWidth="0.0" prefHeight="771.0" prefWidth="1100.0" stylesheets="@styling.css" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.passwordmanager2.HelloController">
<center>
<BorderPane fx:id="borderPane2" BorderPane.alignment="CENTER">
<top>
<StackPane fx:id="topBar" alignment="TOP_CENTER" focusTraversable="true" prefHeight="95.0" BorderPane.alignment="CENTER">
<children>
.....
Here is main method
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
double width = 900.0;
double height = 950.0;
Scene scene = new Scene(fxmlLoader.load(), width, height);
Parent root = fxmlLoader.getRoot();
System.out.println(root.getChildrenUnmodifiable().get(3)); // trying to get stackPane
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
ResizeHelper.addResizeListener(stage);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Solution
You have to create fields named the same way you specified in the .fxml file (with fx:id="..."
), for each component and use the @FXML
annotation.
For example you have to add @FXML private BorderPane borderPane2;
to access the border pane object.
The best way to implement that is using the MVC design pattern. References:
- Wikipedia MVC
- Useful article
- MVC observations (StackOverflow answer by jewelsea)
- MVC really good example (StackOverflow answer by James_D)
Basically the View is the GUI (your .fxml files), the Model is the classes you use (for example you could have a Student, a Professor, and some other classes you need to implement a school score system), and the Controller is where you make changes to the model data and update the View consequently.
I'll leave here a very simple example which implements the MVC pattern:
Result:
Project structure:
src
|
+- application (package)
|
+-- Controller.java
|
+-- Main.java
|
+-- Test.fxml
|
+-- application.css
Main.java class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("ViewTest.fxml"));
AnchorPane basePane = (AnchorPane) loader.load();
Scene scene = new Scene(basePane);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setTitle("Test");
stage.setScene(scene);
stage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
launch(args);
}
}
Model: Counter.java class:
package application;
public class Counter {
private int value;
public Counter() {
this.value = 0;
}
public void increase() {
this.value++;
}
public int getValue() {
return this.value;
}
}
Controller: Controller.java class:
package application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
public class Controller {
@FXML
private AnchorPane base;
@FXML private Slider sliderTest;
@FXML private TextField textFieldTest;
@FXML private Button buttonTest;
private Counter counter;
public void initialize()
{
this.counter = new Counter();
textFieldTest.setText("" + counter.getValue());
}
@FXML private void test(ActionEvent event)
{
this.counter.increase();
this.textFieldTest.setText("" + counter.getValue());
}
}
View ViewTest.fxml file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="base" fx:id="base" prefHeight="400.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<children>
<TextField fx:id="textFieldTest" layoutX="125.0" layoutY="187.0" />
<Button fx:id="buttonTest" layoutX="174.0" layoutY="273.0" mnemonicParsing="false" onAction="#test" text="Increment" />
<Slider fx:id="sliderTest" layoutX="14.0" layoutY="33.0" />
</children>
</AnchorPane>
application.css file:
.slider {
-fx-base: red;
}
.button {
-fx-base: blue;
}
Another way to access a component (for example if it doesn't have a fx:id), you could start from the parent and, knowing the child node index, or iterating through them, you could get the interested node and cast it.
Example:
Node n = base.getChildren().get(0);
TextField tf = (TextField) n;
System.out.println(tf);
tf.setText("Hello JavaFX");
Output:
TextField[id=textFieldTest, styleClass=text-input text-field]
here the TextField is the 1st child of AnchorPane:
Answered By - mikyll98
Answer Checked By - Clifford M. (JavaFixing Volunteer)