I'm attempting to modify a JavaFX scene, loaded via a FXML file, in the Java code of the application I'm running.
I have the following test FXML file, created with SceneBuilder:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.shape.Line?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="1280.0" xmlns="" xmlns:fx="">
<top>
<TabPane prefHeight="100.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
<Tab text="Untitled Tab 1">
<content>
<AnchorPane prefHeight="100.0" prefWidth="200.0" />
</content>
</Tab>
<Tab text="Untitled Tab 2">
<content>
<AnchorPane prefHeight="100.0" prefWidth="200.0" />
</content>
</Tab>
</tabs>
</TabPane>
</top>
<center>
</center>
</BorderPane>
Which is just a BorderPane
with two empty tabs in the Top
section, and an empty Center
section. I wish to be able to draw some shapes in the Center
section. I took the following example code for drawing some random shapes in a Canvas
window, and made sure the example code works by-itself:
I then wanted to insert that same exact drawn Canvas
, with the exact same shapes, into the Center
section of my BorderPane
declared in the FXML file shown above.
However, I'm a bit confused as to how I'm supposed to properly reference the various children of the scene loaded by the FXML loader. If I try the following code in my start
function:
public void start(Stage stage) throws IOException
{
stage.setTitle("Drawing Operations Test");
Scene scene = new Scene(loadFXML("primary"), 1280, 720);
Canvas canvas = new Canvas(300, 250);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawShapes(gc);
scene.getRoot().getChildrenUnmodifiable().get(0).getChildren().add(canvas);
stage.setScene(scene);
stage.show();
}
Where drawShapes
is the function that draws the shapes on the Canvas
, the line:
scene.getRoot().getChildrenUnmodifiable().get(0).getChildren().add(canvas);
Is listed as erroneous, because the class-function getChildren
cannot be found in the Node
class.
Now, my initial assumption for the cause of that error was because I'm supposed to typecast the Node
object into the respective class that I'm wanting to add canvas
to, in my case the BorderPane
class. However, I wanted to clarify exactly what the getChildren
class-function will return me here.
Does getChildren
return the BorderPane
object, as that's the first object that's declared in the FXML file? Or is that element parented under something else? What would be the proper way of adding my Canvas
to the Center
section of my BorderPane
?
Thanks for reading my post, any guidance is appreciated.
scene.getRoot()
will return the root of the scene, which you set via the call to the Scene
constructor:
Scene scene = new Scene(loadFXML("primary"), 1280, 720);
In other words, scene.getRoot()
is the object returned from load.FXML(...)
. You haven't shown us that method definition, so we cannot know for sure, but it is reasonable to assume it returns the result of calling FXMLLoader.load(...)
, which would be the BorderPane
(the root element, in the XML sense, of the FXML file).
So you could cast the result of scene.getRoot()
to a BorderPane
and then call setCenter(...)
(or, obviously better, just keep a reference to the result of loadFXML(...)
in the first place).
The code
scene.getRoot().getChildrenUnmodifiable()
will return a list of the child nodes of the BorderPane
, i.e. a list containing just the TabPane
, and so
scene.getRoot().getChildrenUnmodifiable().get(0)
is a reference to the TabPane
, and
scene.getRoot().getChildrenUnmodifiable().get(0).getChildren()
(with appropriate casts added) is a list of the child nodes of the TabPane
. The content of that list is basically an implementation detail of the TabPane
and its skin.
None of this is the way the API designers intended this to be used, though. Instead, define a controller class for the FXML, and inject the BorderPane
into it:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.shape.Line?>
<BorderPane
maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
prefHeight="720.0" prefWidth="1280.0"
xmlns="" xmlns:fx=""
fx:controller="my.package.MyController"
fx:id="borderPane"
>
<!-- ... -->
</BorderPane>
Then define the controller, and modify the border pane in the initialize()
method (if you want to add the canvas immediately), or in an event handler (if you want to modify the scene graph in response to user actions):
package my.package;
// imports ...
public class MyController {
@FXML
private BorderPane borderPane;
@FXML
private void initialize() {
Canvas canvas = new Canvas(300, 250);
GraphicsContext gc = canvas.getGraphicsContext2D();
drawShapes(gc);
borderPane.setCenter(canvas);
}
private void drawShapes(GraphicsContext gc) {
// ...
}
}