How can I combine a FXML file and a group in JavaFX? - java

So I made a FXML file for a little game I'm making for school and it has some buttons and labels in it, and it has it's own controller. Now I made a group of rectangles and want to add it to the same scene as the fxml file.
button.getParent().getChildren().add(group);
The code I wrote here doesn't work. Anybody an idea on how to add the group in the fxml file or just render it on the scene?
Rendering the fxml and the group in 2 diffrent scenes does work, so there are no errors.
EDIT:
Application class:
package retris;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author Arno Vandersmissen, Casper Vranken, Rani Vanhoudt
*/
public class Retris extends Application {
private Stage stage;
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXMLRetris.fxml"));
Parent root = loader.load();
FXMLRetrisController controller = loader.getController();
controller.playMusic();
stage.setOnCloseRequest(e -> {
e.consume();
FXMLConfirmController confirm= new FXMLConfirmController();
if(confirm.close("Close?")){
Platform.exit();
}
});
Scene scene = new Scene(root);
stage.setTitle("Retris");
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

A Scene can only display ONE Parent at a time. Whatever you want to display in your GUI would be contained in that Parent. Assuming, as you suggested in the comments, that you want to update that parent at runtime, you need to have a reference to whatever child of the parent that should contain your group of rectangles.
let's say the root element of your fxml file is AnchorPane, and you also want to add the group of rectangles to that root. In your .fxml file you need a fx:id tag <AnchorPane fx:id="myRoot"> this allows you to inject the element to your controller class by use of the #FXML annotation.
public class MyController {
#FXML private AnchorPane myRoot;
#FXML private void createAndAddRectangles {
/**myRoot is already instantiated. you can simply add nodes to it at runtime
by using onAction="createAndAddRectangles" tag on a button in your .fxml file.**/
}
}

Related

Error when trying to pass a variable to another controller in javafx/scenebuilder

I'm making a simple program wherein I am trying to add a string from a TextField to a ListView in another scene. The problem is I am getting a NullPointerException when I pass the string to the method which will add it to the List.
Here is the code for controller for the main scene:
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class MainPageController {
#FXML TextField txtfield;
public void addButton(ActionEvent event) {
String a=txtfield.getText();
FXMLLoader loader= new FXMLLoader();
loader.setLocation(getClass().getResource("/progra/view/ListView.fxml"));
ListController obj=loader.getController();
obj.addList(a);// <----NullPointerException
}
public void openList(ActionEvent event) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/program /view/ListView.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setScene(new Scene(root1));
stage.show();
}
}
Here is the code for the controller of the list scene:
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
public class ListController{
#FXML
ListView<String> listofstrs;
public void addList(String a) {
listofstrs.getItems().add(a);
}
}
What do I need to do to fix it? If I may add, is it possible to add variables to a ListView (or any other text containers) in another stage/scene without opening the stage/scene where it is contained, and when you open it you will see the things you added? If so, what do i need to add?
Because you are calling the getController() method, and the controller is not set within the Java source, you must have the controller defined within your fxml file. Can you post the fxml source?
In your fxml, your root element should have the fx:controller attribute set to the class path of the controller, e.g:
fx:controller="com.class.path.to.ListController

Is it possible to define a scene in a separate class?

This may have been asked before but i was not able to find an answer. Im working on a JavaFX app that contains a lot of scenes and a lot of animation. Currently I'm having different Animationtimers and different Scenes all defined inside the start() function, inside the main class that extends Application. However the code gets very messy and long.
Is there a way in which you can define all of these things in a separate Java class, and then simply do something like primaryStage.setScene(MyScene.getScene) - MyScene being the java class that has all your scene code.
Something like this:
public class TestScene {
private Group root = new Group();
Scene test = new Scene(root);
Button button = new Button("test");
root.getChildren.add(button);
}
And actually having that code be a scene that you can just import and set on primaryStage.
Edit: I have no idea why this was so difficult for my mind, as Bertijn said I obviously just need to use a constructer. For whatever reason I forgot that, and so I obviously couldent perform a root.getChildren.add(button), outside a function of some sort.
If anybody else struggles with this here is the super simple solution:
Class containing our scene:
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
public class OurScene {
public Scene getScene() {
Group root = new Group();
Scene scene = new Scene(root, Color.GREEN);
Button button = new Button("Hello world!");
root.getChildren().add(button);
return scene;
}
}
And then to add it to primaryStage:
import javafx.application.Application;
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 {
OurScene ourScene = new OurScene();
primaryStage.setScene(ourScene.getScene());
primaryStage.show();
}
}
Try making a class called Scenes. In the constructor, created all your scenes, you can give them an id if you want. In your main class, just create an instance of this class Scenes scenes = new Scenes();. The scenes get created. Then you can access them by creating a getScene(String id) method.
Hope I understand your question correctly, and if this doesn't answer it, feel free to get back to me!

Can I use multiple classes to control one FXML file?

I made an application in Scene Builder on one FXML. I am making a server with JavaFX so I can learn JavaFX and get more familiar with Java's networking libraries.
I have a server terminal Tab and additional tabs within a TabPane. I wanted to make classes that extend upon the main controller class to handle components in each tab.
While trying to implement this I found that the FXMLLoader won't be able to read things if the #FXML annotated variables are static. And the #FXML annotated event listener method won't be read if that is static either.
And if I try any kind of workaround I get nullpointerexceptions when trying to change text in the TextArea. I really don't want to have to use multiple FXML files but it's seeming like I'll have to because if I can't make these static then it just won't work.
Within the Server Terminal Tab there is a TextArea, a TextField, and a Button.
Here's my working code:
package me.Cronin.Keith.JavaServer;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class JavaServer extends Application {
#FXML
public Button btnSendCommand;
#FXML
public TextField consoleInputField;
#FXML
public TextArea serverTerminal;
public static FXMLLoader fxmlLoader = new FXMLLoader(JavaServer.class.getResource("JavaServer.fxml"));
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage) throws IOException
{
Parent p = fxmlLoader.load();
Scene scene = new Scene(p);
stage.setTitle("Java Server v1.0");
stage.setScene(scene);
stage.show();
}
#FXML
public void clickSendCommand(MouseEvent event)
{
serverTerminal.setText("I got clicked..");
}
}
Here's the other class that I don't know what to do with yet:
package me.Cronin.Keith.JavaServer.Terminal;
import me.Cronin.Keith.JavaServer.JavaServer;
public class Terminal extends JavaServer {
public static void logTerminal(String msg)
{
}
}
Is there anything I can do to change this to support what I want to do?
I want to be able to control the variables (JavaFX Components from FXML) in JavaServer.class with my other classes that extend upon it.
I have seen this question here:
Multiple controller for one FXML file
But it doesn't answer my question.
It's seeming like I'd have to put everything in the main controller class or have multiple Fxml files.
I was able to make this work by replacing my Terminal class with this:
Terminal.java
package me.Cronin.Keith.JavaServer.Terminal;
import me.Cronin.Keith.JavaServer.JavaServer;
public class Terminal extends JavaServer {
public static void logTerminal(String msg)
{
JavaServer mainController = fxmlLoader.getController();
mainController.serverTerminal.setText(msg + "\n");
}
}
So the answer is YES you can.
In JavaServer.java I just called Terminal.class statically and then used the static method logTerminal() within it which worked. I extended JavaServer.java in Terminal.java so I could statically call the controller's FXMLLoader.

JavaFX Sync Duplicate Views to the Same Controller (FXML & MVC)

Below is a small Application that illustrates the problem:
ButtonPanel.fxml
<ScrollPane fx:controller="ButtonPanelController">
<VBox>
<Button fx:id="myButton" text="Click Me" onAction="#buttonClickedAction" />
</VBox>
</ScrollPane>
ButtonPanelController.java
public class ButtonPanelController {
#FXML
Button myButton;
boolean isRed = false;
public void buttonClickedAction(ActionEvent event) {
if(isRed) {
myButton.setStyle("");
} else {
myButton.setStyle("-fx-background-color: red");
}
isRed = !isRed;
}
}
TestApp.java
public class TestApp extends Application {
ButtonPanelController buttonController;
#Override
public void start(Stage stage) throws Exception {
// 1st Stage
stage.setTitle("1st Stage");
stage.setWidth(200);
stage.setHeight(200);
stage.setResizable(false);
// Load FXML
FXMLLoader loader = new FXMLLoader(
ButtonPanelController.class.getResource("ButtonPanel.fxml"));
Parent root = (Parent) loader.load();
// Grab the instance of ButtonPanelController
buttonController = loader.getController();
// Show 1st Scene
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
// 2nd Stage
Stage stage2 = new Stage();
stage2.setTitle("2nd Stage");
stage2.setWidth(200);
stage2.setHeight(200);
stage2.setResizable(false);
/* Override the ControllerFactory callback to use
* the stored instance of ButtonPanelController
* instead of creating a new one.
*/
Callback<Class<?>, Object> controllerFactory = type -> {
if(type == ButtonPanelController.class) {
return buttonController;
} else {
try {
return type.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
};
// Load FXML
FXMLLoader loader2 = new FXMLLoader(
ButtonPanelController.class.getResource("ButtonPanel.fxml"));
// Set the ControllerFactory before the load takes place
loader2.setControllerFactory(controllerFactory);
Parent root2 = (Parent) loader2.load();
// Show 2nd Scene
Scene scene2 = new Scene(root2);
stage2.setScene(scene2);
stage2.show();
}
public static void main(String[] args) {
launch(args);
}
}
Basically, I have a single FXML that I am using for two separate scenes that may or may not be active on the screen at the same time. A practical example of this would be having content docked to a side panel plus a button that opens the same content in a separate window that can be dragged/resized/etc.
The goal I am trying to achieve is to keep the views in sync (changes to one of the views affects the other one).
I am able to point both views to the same controller via a callback however the issue I am running into now is that the UI changes are only reflected on the 2nd scene. Both views talk to the controller but the controller only talks back to the 2nd scene. I'm assuming something with JavaFX's implementation of MVC or IOC is linking the controller to the view in some 1:1 relationship when it is loaded via the FXMLLoader.
I am well aware that trying to link two views to 1 controller is bad MVC practice, however I would like to avoid having to implement a separate FXML and Controller that are practically identical.
Is it possible to achieve this kind of synchronization that I listed above?
If I need to create a separate Controller, what's the best way to ensure that both UI's are in sync (even down to sidebar movements)?
Thanks in Advance!
-Steve
The reason your code doesn't work is that the FXMLLoader injects references to elements in the FXML with fx:id attributes into fields in the controller with matching names. So when you load the FXML file the first time, the FXMLLoader sets the field myButton to be a reference to the button it creates when it loads the FXML. Since you use the exact same controller instance the second time you load the FXML, the FXMLLoader now sets that same field (in the same controller instance) to be a reference to the button it creates when the FXML file is loaded again. In other words, buttonController.myButton now refers to the second button created, not the first. So when you call myButton.setStyle(...) it updates the style of the second button.
Basically, you always want one controller instance per view instance. What you need is for both controllers to access the same shared state.
Create a model class that stores the data. In a MVC architecture, the View observes the model and updates when the data in the model changes. The controller reacts to user interaction with the view and updates the model.
(Arguably, FXML gives you more of a MVP architecture, which is similar. There are variants of this too, but generally the presenter will observe the model and update the view when data in the model changes, as well as update the model in response to user interaction.)
So your model might look like:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
public class Model {
private final BooleanProperty red = new SimpleBooleanProperty();
public final BooleanProperty redProperty() {
return this.red;
}
public final boolean isRed() {
return this.redProperty().get();
}
public final void setRed(final boolean red) {
this.redProperty().set(red);
}
public void toggleRed() {
setRed(! isRed() );
}
}
Your ButtonPanel.fxml doesn't change:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<ScrollPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="ButtonPanelController">
<VBox >
<Button fx:id="myButton" text="Click Me" onAction="#buttonClickedAction" />
</VBox>
</ScrollPane>
Your controller has a reference to the model. It can use bindings or listeners on the model properties to update the UI, and the handler methods just update the model:
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class ButtonPanelController {
#FXML
Button myButton;
boolean isRed = false;
private Model model ;
public ButtonPanelController(Model model) {
this.model = model ;
}
public void initialize() {
myButton.styleProperty().bind(Bindings.
when(model.redProperty()).
then("-fx-background-color: red;").
otherwise("")
);
}
public void buttonClickedAction(ActionEvent event) {
model.toggleRed();
}
}
Finally, you keep everything synchronized because the views are views of the same model. In other words you just create one model and hand its reference to both controllers. Since I made the model a constructor parameter in the controller (which is nice, because you know you have a model as soon as the instance is created), we need a controller factory to create the controller instances:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TestApp extends Application {
#Override
public void start(Stage stage) throws Exception {
// 1st Stage
stage.setTitle("1st Stage");
stage.setWidth(200);
stage.setHeight(200);
stage.setResizable(false);
// The one and only model we will use for both views and controllers:
Model model = new Model();
/* Override the ControllerFactory callback to create
* the controller using the model:
*/
Callback<Class<?>, Object> controllerFactory = type -> {
if(type == ButtonPanelController.class) {
return new ButtonPanelController(model);
} else {
try {
return type.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
};
// Load FXML
FXMLLoader loader = new FXMLLoader(
ButtonPanelController.class.getResource("ButtonPanel.fxml"));
loader.setControllerFactory(controllerFactory);
Parent root = (Parent) loader.load();
// Show 1st Scene
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
// 2nd Stage
Stage stage2 = new Stage();
stage2.setTitle("2nd Stage");
stage2.setWidth(200);
stage2.setHeight(200);
stage2.setResizable(false);
// Load FXML
FXMLLoader loader2 = new FXMLLoader(
ButtonPanelController.class.getResource("ButtonPanel.fxml"));
// Set the ControllerFactory before the load takes place
loader2.setControllerFactory(controllerFactory);
Parent root2 = (Parent) loader2.load();
// Show 2nd Scene
Scene scene2 = new Scene(root2);
stage2.setScene(scene2);
stage2.show();
}
public static void main(String[] args) {
launch(args);
}
}

Javafx Donut Chart in FXML

I want to do a Doughnut/Donut chart on JavaFX and searching I came to this example: Can PieChart from JavaFX be displayed as a doughnut?
I Works really nice, but since I'm using FXML to make my GUI, I can't use this example. First, I tried to add the DoughtnutChart.java class as a #FXML var in the controller class of the panel where I want to insert it, but launched errors.
Then, searched in Google to make the DoughnutChart a custom component, but all the examples are based on Panes. Also, If I try to import my donu.jar to SceneBuilder, the window to select a component is empty.
So, my question is: How do I implement this Doughnut Chart on JavaFX when my GUI is made on FXML?
Thanks a lot.
It's hard to tell what the cause of your error is without seeing the FXML and the error message.
I got this to work pretty easily: the one thing to be aware of is that the FXMLLoader instantiates classes by invoking the no-argument constructor. If it can't find one, it tries to use a builder class as a back-up plan. So the one modification you need to make to #jewelsea's DoughnutChart implementation is to add a no-argument constructor. (You could also define a DoughnutClassBuilder, but that's a lot more work, and doesn't get you any extra benefit.) So I did this:
package doughnut ;
// imports as before...
public class DoughnutChart extends PieChart {
private final Circle innerCircle;
public DoughnutChart() {
this(FXCollections.observableArrayList());
}
// everything else as before...
}
Then the following FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<?import doughnut.DoughnutChart?>
<StackPane xmlns:fx="http://javafx.com/fxml" fx:controller="doughnut.SampleController">
<DoughnutChart fx:id="doughnutChart" />
</StackPane>
with the controller SampleController.java:
package doughnut;
import javafx.fxml.FXML;
import javafx.scene.chart.PieChart;
public class SampleController {
#FXML
private PieChart doughnutChart ;
public void initialize() {
doughnutChart.getData().addAll(
new PieChart.Data("Grapefruit", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
}
}
and the application class
package doughnut;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
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 {
StackPane root = (StackPane)FXMLLoader.load(getClass().getResource("DoughnutChartDemo.fxml"));
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
work exactly as expected.
I had to replace this with super in James_D answer to make it work.
For me the constructor looked like:
public DoughnutChart()
{
super(FXCollections.observableArrayList());
innerCircle = new Circle();
// just styled in code for demo purposes,
// use a style class instead to style via css.
innerCircle.setFill(Color.WHITESMOKE);
innerCircle.setStroke(Color.WHITE);
innerCircle.setStrokeWidth(3);
}

Categories