My problem is: I want to change something from the .fxml but whatever I do, nothing changes. This is just a simply example.
I went trough the whole internet but none of the solutions worked for me.
Here I want to change the text of the label, by calling the corresponding method from the main class.
Calling the same method (here setLabel()) when clicking a Button, with an event handler in the controller class, everything works fine, but a soon as I try to modify something from another class nothing works.
Main class:
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
Controller controller = new Controller();
Platform.runLater(()->controller.setLabel());
}
FXML Code:
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<center>
<Label fx:id="label" text="This" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Controller class:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
#FXML
private Label label=new Label();
public void setLabel(){
label.setText("Test");
}
}
There are actually two problems with your code.
1) In the start method of your Application you load sample.fxml using an FXMLLoader, which is correct. But creating a new controller like Controller controller = new Controller(); is incorrect, as you should get the controller from the FXMLLoader itself using getController method, and also you should not use the static load function of FXMLLoader, but you should create an instance of it.
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("sample.fxml"));
Controller controller = loader.getController();
When you call load on an FXMLLoader it will load the object hierarchy from the FXML file and it will also create a controller (referenced in the FXML file).
2) In Controller you inject the Label from FXML file, but you re-create it. When the FXMLLoader injects a control to your controller based on the fx:id it also ensures initialization. If you create a new Label it will not point to the Label instance created by the loader.
This
#FXML private Label label= new Label();
should be replaced with
#FXML private Label label;
That is pretty simple... You can change the the label text from your Main class either by adding a getter method in your Controller class for the required label and then get it in the Main class using the controller object (loader.getController()) and update its text. Or call the setter method inside the Controller class using controller object in Main.
As DVagra said, use loader.getController() to get the controller object.
(where loader is the object of FXMLoader).
Moreover you do not need Platform.runLater() to update the gui controls. As you're already running on the FX thread.
Anyway, here is what you need.
Main Class
package sample;
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 {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
Controller controller = loader.getController();
controller.setLabel("Test");
// Or
// controller.getLabel().setText("Test");
}
public static void main(String[] args) {
launch(args);
}
}
Controller Class
package sample;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable{
#FXML
Label label;
//setter
public void setLabel(String labelText){
label.setText(labelText);
}
//getter for label
public Label getLabel() {
return label;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
Sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.60"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<center>
<Label fx:id="label" text="This" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Related
I'm trying to build a chat application that implements Group chat in JavaFX. I want to make a Scroll Pane inside of a Border pane that will contain all Groups in which the User is member of. The Groups icons (ImageViews) need to be added dynamically(cannot be done in Scene Builder) to the Scroll Pane(inside of a HBox) as the User is joining them.
Currently I'm using a SceneController class that is responsible for all Stage and Scene changes.
package com.ong.Controllers;
import com.ong.Main;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.HashMap;
public class ScreenController {
private static HashMap<String, Scene> screenMap = new HashMap<>();
private static Stage main;
public ScreenController(Stage _main){
main = _main;
}
public static void addScreen(String name, FXMLLoader fxmlLoader){
Scene scene = null;
try {
scene = new Scene(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
screenMap.put(name,scene);
}
public static void addScene(String name, Scene scene){
screenMap.put(name,scene);
}
public static void removeScreen(String name){
screenMap.remove(name);
}
public static void setMinimumDimensions(int width, int height){
main.setMinWidth(width);
main.setMinHeight(height);
}
public static void setMaximized(boolean full){
main.setMaximized(full);
}
public static void activate(String name){
main.setScene(screenMap.get(name));
main.getIcons().add(new Image(Main.class.getResource("Images/not_logo.png").toString()));
main.setTitle(name);
main.show();
}
}
I already created an FXML file(using scene builder) which contains Border Pane and a Scroll Pane as top child.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane fx:id="borderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.ong.Controllers.HomepageController">
<top>
<ScrollPane fx:id="groupsMenuScrollPane" hbarPolicy="NEVER" prefHeight="62.0" prefWidth="1920.0" stylesheets="#../CSS/groups_menu.css" vbarPolicy="NEVER" BorderPane.alignment="CENTER" />
</top>
</BorderPane>
My plan is to populate all Groups(on initialization) to the Scroll Pane in the Page controller.
package com.ong.Controllers;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Circle;
import java.net.URL;
import java.util.ResourceBundle;
public class HomepageController implements Initializable {
#FXML
private ScrollPane groupsMenuScrollPane;
#FXML
private BorderPane borderPane;
#Override
public void initialize(URL location, ResourceBundle resources) {
final ImageView imageView = new ImageView("com/ong/Images/not_logo.png");
final Circle clip = new Circle(300, 200, 200);
imageView.setClip(clip);
HBox hbox = new HBox();
hbox.getChildren().add(imageView);
groupsMenuScrollPane.setContent(hbox);
borderPane.setTop(groupsMenuScrollPane);
Scene scene = new Scene(borderPane);
ScreenController.addScene("Homepage", scene);
ScreenController.activate("Homepage");
}
}
I already tried to create a Scene with the updated Border Pane, but I get an error "borderPane is already set as root of another scene". I also tried to directly change the root of the already existing scene, but I get a NullPointerException in that case.
Could you please advise which is the best way to add Nodes dynamically to a Scroll Pane. I will be grateful for comments about the project structure too.
Could you please advise which is the best way to add Nodes dynamically to a Scroll Pane.
You need a reference to the content of the ScrollPane (the HBox in this case), and a method to add things to it:
public class HomepageController implements Initializable {
#FXML
private ScrollPane groupsMenuScrollPane;
#FXML
private BorderPane borderPane;
private HBox groupContainer ;
#Override
public void initialize(URL location, ResourceBundle resources) {
final ImageView imageView = new ImageView("com/ong/Images/not_logo.png");
final Circle clip = new Circle(300, 200, 200);
imageView.setClip(clip);
groupContainer = new HBox();
groupContainer.getChildren().add(imageView);
groupsMenuScrollPane.setContent(groupContainer);
// this is not needed since it's already done in FXML:
// borderPane.setTop(groupsMenuScrollPane);
// ...
}
public void addGroup(...) {
// not sure exactly what you want to add, but for example,
// to add a new image view you would do:
ImageView imageView = new ImageView(...);
groupContainer.getChildren().add(imageView);
}
}
When you load the FXML, keep a reference to the controller:
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
Parent root = loader.load();
HomepageController controller = loader.getController();
And then you can add things to the scroll pane with
controller.addGroup(...);
As mentioned in another answer, a ListView may be a better approach to this.
You should also probably consider a MVC approach (see, for example, Applying MVC With JavaFx), where the model uses an ObservableList<...> to store the list of groups. The controller can observe that list and modify the scroll pane content when it changes. Then all you have to do is add something to the list via the model.
Your question and problem are separate issues.
For your question. Dynamically adding elements you'll want to look into ListView.
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html
Anything like recyclerview or reusable views for javafx
As for your problem. Scenes can only have one root node. Within this root, you should have your boarder pane and within that should be your ListView.
So I am working on a javaFX application and I want to create multiple <ImageView> using for loop!
Is that possible to make for/foreach loop in a .fxml file ?
If yes , then how ?
Also i have another question! how to send data from the controller to sample.fxml file ?
for exemple i want to send a table form controller.java to sample.fxml file and use that table+for loop to make <ImageView> in the fxml file!
Note: I am using the fxml to display the image because I am using those images as buttons.
Here is the code of sample.fxml :
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<ImageView fitHeight="120" fitWidth="120" fx:id="panda" GridPane.columnIndex="0" GridPane.rowIndex="0" onMousePressed="#mousePressed">
<image>
<Image url="#pics/panda.png">
</Image>
</image>
</ImageView>
</GridPane>
Here is the code of Controller.java :
package sample;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.AudioClip;
public class Controller {
public void play_audio()
{
AudioClip sound = new AudioClip(this.getClass().getResource("voices/panda.mp3").toString());
sound.play();
}
public void mousePressed() {
play_audio();
}
}
Code of Main.java :
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Animal Sound");
Scene scene = new Scene(root, 790, 675);
scene.getStylesheets().add("sample/styles.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I don't think flow control is manageable in an fxml file. Also you don't really pass arguments to an fxml file.
I think you've separated this well, and a little more paring down should get it to work. I would recommend to specify the sounds and the images from the controller. So I would make the controller for each button.
package sample;
import javafx.fxml.FXML;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
public class AudioButtonController {
String sound;
#FXML
ImageView image;
public void setAudioLocation(String resourcePath){
sound = resourcePath;
}
public void setImageLocation(String img){
image.setImage( new Image( img ) );
}
public void mousePressed() {
System.out.println(sound);
}
}
Now your fxml for each button can be.
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<HBox xmlns:fx="http://javafx.com/fxml" fx:controller="sample.AudioButtonController">
<ImageView
fitHeight="120" fitWidth="120" fx:id="image" onMousePressed="#mousePressed">
</ImageView>
</HBox>
Here is an example main class that starts up and loads 2 audio buttons, but it could be used to load N buttons.
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Main extends Application{
#Override
public void start(Stage primaryStage) throws Exception{
FlowPane root = new FlowPane(5, 5);
primaryStage.setTitle("Animal Sound");
String[] imgs = { "https://conserveblog.files.wordpress.com/2016/05/flagship-panda-thumbnail.jpeg?w=188", "http://news.bbc.co.uk/media/images/38625000/jpg/_38625095_021223panda150.jpg" };
for(String img: imgs){
FXMLLoader loader = new FXMLLoader( getClass().getResource("audio_button.fxml") );
Parent audioButton = loader.load();
AudioButtonController abc = loader.getController();
abc.setAudioLocation("not supported");
abc.setImageLocation(img);
root.getChildren().add(audioButton);
}
Scene scene = new Scene(root, 790, 675);
primaryStage.setScene(scene);
primaryStage.show();
}
}
This is a bit cumbersome for this example. If your button had more layout controls to it, and your outer layout was more complicated, then this could save some time.
I currently have 3 classes.
ScreenController (controller class):
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
public class ScreenController implements Initializable
{
private AnchorPane window;
public ScreenController()
{
super();
}
public ScreenController(AnchorPane window)
{
setWindow(window);
}
public void setWindow(AnchorPane window)
{
this.window = window;
}
public void setScreen(String screen)
{
try
{
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
window.getChildren().setAll(root);
}
catch (Exception e)
{
e.printStackTrace();
}
}
#Override
public void initialize(URL location, ResourceBundle resources)
{
}
}
LoginScreen (primary screen):
import com.app.client.java.controllers.ScreenController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import java.io.IOException;
public class LoginScreen extends ScreenController
{
#FXML
private AnchorPane loginWindow;
#FXML
private Button goButton;
public LoginScreen()
{
super();
setWindow(loginWindow);
}
#FXML
public void goButtonPressed(ActionEvent event) throws IOException
{
setScreen("Home");
System.out.println("Success.");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="loginWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" opacity="0.5" prefHeight="500.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.LoginScreen">
<children>
<Button fx:id="goButton" layoutX="205.0" layoutY="60.0" mnemonicParsing="false" onAction="#goButtonPressed" text="Button" />
</children>
</AnchorPane>
HomeScreen (secondary screen):
import com.app.client.java.controllers.ScreenController;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
public class HomeScreen extends ScreenController
{
#FXML
private static AnchorPane homeWindow = new AnchorPane();
public HomeScreen()
{
super (homeWindow);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="homeWindow" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.HomeScreen">
<children>
<TextArea layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0" text="aksajkasjkasja" />
</children>
</AnchorPane>
I would like to be able to move from the primary screen to the secondary screen using the setScreen() function. However, I'm finding that the process doesn't complete successfully.
Another approach I've found that works is (Although it resizes the window, rather than filling the initial window with the contents of the new one):
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
Stage stage = (Stage) loginWindow.getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
However, I'd prefer to use the initial implementation due to it being more concise, readable and, theoretically, provides the exact behaviour I would like.
There are a couple issues with what you currently have:
In your LoginScreen constructor you call setWindow with the value of a yet-to-be-injected field:
public LoginScreen()
{
super();
setWindow(loginWindow);
}
No FXML fields will have been injected while the constructor of the controller is executing—meaning loginWindow is null. The reason for this self-evident: The FXMLLoader has to first construct the controller instance before it can start injecting the appropriate fields.
The order of events are: (1) Controller instantiated, (2) fields injected, (3) initialize method invoked; I believe linking any event handlers/change listeners is included in step two. What this means is any initialization that needs to happen regarding FXML fields should be done in the initialize method.
You have the same problem in your HomeScreen constructor with super(homeWindow), though there are other problems there which are addressed in the next point.
In addition to trying to access a yet-to-be-injected field in the constructor, there are two other problems with the following:
#FXML
private static AnchorPane homeWindow = new AnchorPane();
The first problem is you initialize a field that is meant to be injected. Never do this. A good rule of thumb is: If the field is annotated with #FXML then don't manually assign a value to it. The FXML field will eventually be injected which means any value you assign to it beforehand will simply be replaced. This can lead to subtle problems since any code with a reference to the previous value won't be using the object that was actually added to the scene graph.
The other problem is your field is static. Injecting static fields is not supported in JavaFX 8+. It used to be possible in older versions, from what I understand, but that behavior was never officially supported (i.e. was an implementation detail). Besides, it doesn't make sense to have something inherently instance-based (FXML+controllers) set a static field which would affect all instances.
A bonus problem: When you make homeWindow non-static you can no longer use super(homeWindow) because you can't reference it before the super constructor is invoked.
Using the two modified classes should allow your code to run:
LoginScreen.java:
public class LoginScreen extends ScreenController {
#FXML private AnchorPane loginWindow;
#FXML private Button goButton;
#Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(loginWindow); // set window in initialize method
}
#FXML
public void goButtonPressed(ActionEvent event) throws IOException {
setScreen("Home");
System.out.println("Success.");
}
}
HomeScreen.java:
public class HomeScreen extends ScreenController {
#FXML private AnchorPane homeWindow;
#Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(homeWindow); // set window in initialize method
}
}
However don't use:
window.getChildren().setAll(root);
In your ScreenController#setScreen method—it causes a subtle problem. You're adding a root as a child of the window node. But when this happens, the new instance of ScreenController (associated with the new root) has its window == root. In other words, the window created with LoginScreen is now the parent of the window created with HomeScreen. Depending on how a more complex application is designed, this can lead to progressively deeper nesting of "roots".
That said, you already have another approach where you actually replace the entire Scene. The issue you're having there, as you stated, is that the Stage resizes to fit the new Scene. This can be fixed by replacing the root of the Scene, rather than the Scene itself:
window.getScene().setRoot(root);
Some potentially helpful resources:
Introduction to FXML
JavaFX FXML Tutorial (javacodegeeks.com)
JavaFX FXML (jenkov.com)
what does initialize() mean in javafx?
JavaFX : Pass parameters while instantiating controller class
How to swap screens in a javafx-application in the controller-class?
Loading new fxml in the same scene
Passing Parameters JavaFX FXML
Switch between panes in JavaFX
afterburner.fx
mvvmFX
Curated list of JavaFX-related things
You can get the main stage of a JavaFX Application during initialization. Other scene classes should have a Stage field with getter and setter and so you will be able to pass the main stage through their Controller. As for the window resize, you can fix that by adding getStage().getWidth() and getStage().getHeight() in the setScene() statement.
A small example of what I am trying to point:
public class MainClass extends Application {
#Override
public void start(Stage stage) throws Exception {
InputStream sceneStream = MainClass.class.getResourceAsStream("/fxml"+
"/newScene/main.fxml");
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(sceneStream);
Scene scene = new Scene(root);
stage.setTitle("App title");
NewScene controller = loader.getController();
controller.setMainStage(stage);
stage.setScene(scene, stage.getWidth(), stage.getHeight());
stage.show();
}
So the above function starts from MainClass where the main stage is created. Notice the part in the middle which is a bit separeted from the rest of the code, where by getting the controller of the loaded Scene I am passing the stage to it. You can pass the stage that way to all of your scenes. Also notice the part where the scene is set, where I use two more parameters extracted from the stage; the width and the height. Other than that, there are more ways to get the stage in pretty much every scene that runs on the primary stage, just by doing:
#FXML private Button aButton;
public Button getAButton(){
return aButton;
}
Stage stage = (Stage) getAButton().getScene().getWindow();
This will work in all scenes based in the primary stage and it only requires you to have a registered in the Scene Graph Node no matter the type.
I'm trying to set a JavaFx scene where a nested FlowPane can be changed during the runtime. I want to be able to new elements to the Pane, and have them rendered out.
Main
package renderable;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Main extends Application implements EventHandler<ActionEvent{
Button button;
#FXML public FlowPane flowPane;
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("/renderable/ClientRender.fxml"));
Scene scene = new Scene(root, 1000, 720);
primaryStage.setTitle("The Game");
primaryStage.setScene(scene);
primaryStage.show();
loader.getController().updateFlowPanel();
primaryStage.show();
}
public void updateFlowPanel(){
flowPane.getChildren().add(button);
}
#Override
public void handle(ActionEvent event) {
if (event.getSource() == button)
System.out.println("The first button was pushed.");
}
}
RenderManager
public class RenderManager implements EventHandler<ActionEvent>{
cCard tableCards[];
cHand thisPlayerHand;
#FXML public FlowPane flowPane;
Button button;
public RenderManager() {
}
public void updateFlowPanel(){
flowPane.getChildren().add(button);
}
#Override
public void handle(ActionEvent event) {
if (event.getSource() == button)
System.out.println("The first button was pushed.");
}
}
ClientRender.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.*?>
<?package renderable?>
<?import javafx.geometry.*?>
<FlowPane id="flowPane" fx:id="flowPane" hgap="4.0" nodeOrientation="LEFT_TO_RIGHT" prefHeight="480.0" prefWidth="600.0" prefWrapLength="1400.0" style="-fx-background-color: #006600;" vgap="4.0" BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="renderable.Main"/>
I am unable to correctly reference the RenderManager object associated with my current root to then run it's `updateFlowPanel()' method.
How do I correctly add elements to the Pane during runtime?
The reason you are getting a null pointer exception is that the #FXML-injected field flowPane is only initialized in the controller. You are calling updateFlowPanel() on an instance of Main that is not the controller, hence flowPane is null in that instance, and consequently flowPane.getChildren() throws a null pointer exception.
Specifically: Application.launch() creates an instance of the Application class and calls start() on that instance (among other things).
Calling FXMLLoader.load(), by default, creates an instance of the class specified by the fx:controller attribute in the FXML root element. Any #FXML-injected fields are initialized in that instance, event handler methods are called on that instance, and the initialize() method is called on that instance.
Since you use the same class Main for both the Application and the controller class - which is always a mistake, you end up with two instances of Main. You call updateFlowPanel() from start(), i.e. you call it on the instance created by Application.launch(); however flowPane is only initialized in the instance created by the FXMLLoader.
You should use a separate controller class. I.e. do
package renderable;
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 {
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/renderable/ClientRender.fxml"));
Scene scene = new Scene(root, 1000, 720);
primaryStage.setTitle("The Game");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.show();
}
}
and then define a controller class:
package renderable ;
import javafx.fxml.FXML ;
import javafx.scene.layout.FlowPane ;
import javafx.scene.control.Button ;
public class Controller {
#FXML
private FlowPane flowPane ;
public void updateFlowPanel(){
Button button = new Button("...");
flowPane.getChildren().add(button);
}
}
and update the FXML to refer to it:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?package renderable?>
<?import javafx.geometry.*?>
<FlowPane id="flowPane" fx:id="flowPane" hgap="4.0"
nodeOrientation="LEFT_TO_RIGHT" prefHeight="480.0" prefWidth="600.0"
prefWrapLength="1400.0" style="-fx-background-color: #006600;" vgap="4.0"
BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="renderable.Controller"/>
Now you can either just call updateFlowPanel() in the initialize() method:
package renderable ;
import javafx.fxml.FXML ;
import javafx.scene.layout.FlowPane ;
import javafx.scene.control.Button ;
public class Controller {
#FXML
private FlowPane flowPane ;
public void initialize() {
updateFlowPanel();
}
public void updateFlowPanel(){
Button button = new Button("...");
flowPane.getChildren().add(button);
}
}
or you can call it on the correct controller instance from your start() method:
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/renderable/ClientRender.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
controller.updateFlowPanel();
Scene scene = new Scene(root, 1000, 720);
primaryStage.setTitle("The Game");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.show();
}
Edit:
The update you made to your question actually prevents the code from compiling. If you fix the compile error by replacing
loader.getController().updateFlowPanel();
with
loader.<Main>getController().updateFlowPanel();
then the (entirely different) issue you have created is that you create an instance of FXMLLoader:
FXMLLoader loader = new FXMLLoader();
but then instead of using that FXMLLoader instance to load the FXML, you call the static method load(URL):
Parent root = loader.load(getClass().getResource("/renderable/ClientRender.fxml"));
Since you are calling the static method, you never call load() on the FXMLLoader instance, and so its controller property is never initialized. Consequently, loader.<Main>getController() is null, and loader.<Main>getController().updateFlowPanel() throws a null pointer exception (entirely different to the null pointer exception you had in the previous version of your question; it is not even thrown from the same method).
If you want to reference the controller, use the code in the second version of the start() method that I posted above.
I followed the steps of the following blog but cannot create a jar using netbeans whenever I am going to build a dialog box is coming like this
The blog which I have followed:
Adding a Custom JavaFX Component to Scene Builder 2.0
The project contains following three files
i) FXML file
ii) Controller Class
iii) Style Sheet
Here is the tree view of the project:
I seems that you miss a main class where you load your fxml and set and show the scene.
In NetBeans use Run/Build Project (F11) to compile and create a jar file.
Use Run/Run Project (F6) to compile and run your project. For this you need a main class.
The tutorial in the link works for me. I did the following:
create project 'Stackoverflow' (the library)
create project 'Stackoverflow2' (the main application using the library). 'Stackoverflow2' ist using library 'Stackoverflow' as you can see below the 'Libraries' node.
Content of file StackOverflow2.java:
package stackoverflow2;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class StackOverflow2 extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("DemoScreen.fxml"));
Scene scene = new Scene(root, 600, 400);
primaryStage.setTitle("stackoverflow");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Content of file DemoScreenController:
package stackoverflow2;
import javafx.fxml.FXML;
import stackoverflow.CommodityImageLabel;
public class DemoScreenController {
#FXML
protected CommodityImageLabel commodityLabel1;
}
Content of file DemoScreen.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import stackoverflow.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="321.0" prefWidth="543.0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="stackoverflow2.DemoScreenController">
<children>
<Label layoutX="72.0" layoutY="34.0" text="Main App" />
<Separator layoutX="24.0" layoutY="78.0" prefHeight="1.0" prefWidth="499.0" />
<CommodityImageLabel layoutX="43.0" layoutY="98.0" />
</children>
</AnchorPane>
Content of StackOverflow.java:
package stackoverflow;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StackOverflow extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I didn't take care of the functionality - so the code is very basic.