I'm working on a project that requires a "tabled" representation of VBoxes. My hierarchical layout of the application is GridPane -> VBox (in one of the Cells) -> VBoxes (that display different datasets on top of each other) -> Data. I have two Scenes.
Data is displayed on Scene 1. The user can add data through a form and by clicking a button on Scene 2. Then, the added data should be displayed below the existing data as a VBox within the parent-VBox on Scene 1 again.
Here is the code that will make it clear:
My Scene 1 .fxml file looks the following (Simplified):
<GridPane fx:id="grid" fx:controller="application.Controller">
[here: ColumnConstraints]
<children>
<VBox fx:id="parentBox" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<Button fx:id="goToScene2" text="+" onAction="#goToScene2"/>
</children>
</GridPane>
Scene 2 just has a button and a TextField:
<GridPane fx:id="grid" fx:controller="application.AddDataController">
[here: ColumnConstraints]
<children>
<Button fx:id="addData" text="add" onAction="#bAddData"/>
<TextField fx:id="data"/>
</children>
</GridPane>
My Scene 1 controller (controller) looks like this:
public class Controller implements Initializable {
#FXML Button goToScene2;
#FXML VBox parentBox;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void addData(String s) {
Label lbl = new Label(s);
VBox dataBox = new VBox();
dataBox.setPadding(new Insets(15, 5, 15, 5));
dataBox.setSpacing(5);
dataBox.setMaxHeight(80);
dataBox.getChildren().add(lbl);
parentBox.getChildren().add(dataBox);
}
}
This is designed as it is because the dataBox contains more elements than the label, but that doesn't seem relevant to me in this context.
My Scene 2 controller (addDataController) looks like this:
#FXML Button addData;
#FXML TextField data;
#FXML protected void bAddData(){
String content = data.getText();
FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("scn1.fxml").openStream());
Controller cont = (Controller) fxmlLoader.getController();
cont.addData(content);
}
So, when one clicks on the Add-Data-Button in Scene 2, the triggered method passes the entered data to the Controller of Scene 1. This is because the new data should be displayed in Scene 1 now.
I feel like the logic does not work (edited here), because when I ask for
System.out.println(parentBox.getChildren().size();
before and after the data was added, it always has one single Child, even though it should have one more...
If I artificially fill a String-Array and move everything from addData to(String s) to Initialize(...), it does work and the data shows up as VBoxes in the parent-VBox.
I didn't post the main class, because loading Controllers and Scene change is not an issue.
Thank you all very very much for your help! :)
Just to provide more detailed information, together with an idea, that I don't know how to implement.
This is my main class:
#Override
public void start(Stage primaryStage) {
currentStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("Scene1.fxml"));
scene1 = new Scene(root);
}
Could I load the controller at this point and make a getter that passes the Controller? So I'd only have one single instance of it throughout the program.
I tried inserting:
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.load(getClass().getResource("Scene1.fxml").openStream());
Controller controller = (Controller) fxmlLoader.getController();
after the
Parent root ...
line. But that's loading the .xml file twice. How can I nicely connect both parts?
Thanks so much for you patience!
EDIT:::::::::
The following code worked for me:
#Override
public void start(Stage primaryStage) {
currentStage = primaryStage;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Scene1.fxml"));
Parent root =(Parent) fxmlLoader.load();
controller = (Controller) fxmlLoader.getController();
}
Now, I can ask the main class for controller and it always passes that single instance.
Thank you for pointing me to the right direction! :)
Related
Let's assume, for simplicity's sake, I have an FXML with a SplitPane. On one side I have a Button; the other has an empty AnchorPane (for now).
The button simply places a pane on the other side of the SplitPane from another FXML file. The newly added pane contains a Button and a Label. Both FXMLs have the same controller for two reasons: 1. The Added Pane FXML is not used elsewhere, 2. The Added Pane FXML has so little functions that it's not really worth it to have a separate controller for it.
Pressing the button on the newly added pane sets the text in its label to "Hello World".
Split Pane
<SplitPane fx:controller="myController".... >
<items>
<VBox .... >
<Button fx:id="mainButton" onAction="#handleFirstButton".... />
</VBox>
<AnchorPane fx:id="secondPane" ....>
</items>
</SplitPane>
Added Pane
<VBox fx:controller="myController" ....>
<Button fx:id="btn" onAction="#changeText" ... />
<Label fx:id="lbl" .... />
</VBox>
Controller Class
// Regular stuff here
#FXML
AnchorPane secondPane;
#FXML
Button mainButton;
#FXML
Button btn;
#FXML
Label lbl;
public void initialize(URL arg0, ResourceBundle arg1) {
}
private void loadSecondPane {
FXMLLoader loader = FXMLLoader.load(getClass().getResource("AddedPane.fxml"));
VBox box = loader.load();
secondPane.setTopAnchor(box);
}
#FXML
private void handleFirstButton(ActionEvent event) {
loadSecondPane();
}
#FXML
private void changeText(ActionEvent event) {
lbl.setText("Hello World");
}
The problem is, when the Added Pane is loaded, its components will NOT be defined in the controller, and trying to perform any method on them will produce a NullPointerException.
Is there a way around this or is having a separate controller mandatory in this case? And assuming I have 2+ buttons on the first side of the pane, and each button produces a different pane on the other side, does the solution remain the same?
NOTE: I'm fairly new with javafx, so excuse any mistypes.
I cant convert this code into scene builder...
The problem is in event handler....
I am not getting how to use the confirmCloseEventHandler event handeler in java
fx scene builder...
thanks in advance.
mainly i cant use those event handlers... in fxml controllers...
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
// closeConfirmation.setX(mainStage.getX());
//closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
Registering some handler for the primary stage via fxml could only be done with a bad hack, since FXMLLoader only has access to objects it creates itself.
You could add a listener to the Node.scene property of some node in your scene and add a listener to the window property of that scene as soon as it's set and access the window as soon as it's assigned, which is quite complex for something that could be done using much simpler code in the start method.
Other than that hack you won't get around registering that event handler in the start method (or passing the Stage to the controller resulting in more complex code than the one posted).
As for the close button onAction event: You can use a method of your controller as handler:
fxml
<StackPane xmlns:fx="http://javafx.com/fxml" fx:controller="mypackage.MyController">
<children>
<Button text="Close Application" onAction="#close"/>
</children>
<padding>
<Insets topRightBottomLeft="100"/>
</padding>
</StackPane>
controller
package mypackage;
...
public class MyController {
#FXML
private void close(ActionEvent event) {
Node source = (Node) event.getSource();
Window window = source.getScene().getWindow();
window.fireEvent(new WindowEvent(
window,
WindowEvent.WINDOW_CLOSE_REQUEST));
}
}
I have a pane with a label, a text field and a combo box inside a VBox in fxml file. Let´s call it tempPane.
In the same stage I have a button.
Once the button is pressed I need to add to the VBox a pane exactly the same as tempPane. This is, adding dynamically a pane to the VBOX.
I am able to add individual controls such as buttons or labels or text fields to the VBox, but I can´t obtain the same results when trying to add this new pane.
Part of the controller code:
#FXML
private Pane tempPane;
#FXML
private Button btnAddNewPane;;
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
Pane newPane = new Pane();
newPane = tempPane;
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
newPane.setId(newID);
vBox.getChildren().add(newPane);
...
}
And the error I´m getting is:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = VBox[id=filterBox]
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:580)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at com.sener.dbgui.controller.SearchController$1.run(SearchController.java:53)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
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:175)
at java.base/java.lang.Thread.run(Thread.java:844)
So, why am I getting this duplicate children error? I´m changing the newPane ID before adding it to the VBox.
Pane newPane = new Pane();
newPane = tempPane;
...
vBox.getChildren().add(newPane);
This code does create a new Pane (first line) but immediately drops the new instance by overwriting it with the old one (second line).
The error happens since the contract of Node does not allow it to be placed twice in a scene and you're adding the same Pane that is already a child of vBox again. Modifying the id property does not change that fact.
You need to create a new copy of the subscene rooted at tempPane if this is supposed to work.
You could create a custom Pane for this scene:
subFXML.fxml
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.Pane">
<!-- content of tempPane from old fxml goes here -->
...
<Button fx:id="btnAddNewPane" />
...
</fx:root>
public class MyPane extends Pane {
public MyPane() {
FXMLLoader loader = getClass().getResource("subFXML.fxml");
loader.setRoot(this);
loader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
#FXML
private Button btnAddNewPane;
public void setOnAction(EventHandler<ActionEvent> handler) {
btnAddNewPane.setOnAction(handler);
}
public EventHandler<ActionEvent> getOnAction() {
return btnAddNewPane.getOnAction();
}
}
old fxml
Be sure to import MyPane.
...
<VBox fx:id="vBox">
<children>
<!-- replace tempPane with MyPane -->
<MyPane onAction="#addNewPane"/>
</children>
</VBox>
...
old controller
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
MyPane newPane = new MyPane();
newPane.setId(newID); // Don't know why setting the CSS id is necessary here
newPane.setOnAction(this::addNewPane); // set onAction property
vBox.getChildren().add(newPane);
...
}
It is written in your comments already why you are getting duplicate ID.
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
You are passing the same string as a parameter.
newPane.setId("NewID");
try using a dynamically generated and unique id for each pane.
String newId; //generate the id by user input or internally
newPane.setId(newId);
I created a GridPane in my JFX Fluidon Designer and I'm trying to reference it in one of my java files.
More specifically, I'd like to programatically put it inside a Vbox with an Hbox I also created in Java. I'll post the code below, it might make more sense.
When I run this code, I get an error that my gridPane variable is null when I use it in the addAll method.
Thanks in advance!
public class MyProgram extends Application {
Label mStatus;
ImageView img_x;
ImageView img_o;
#FXML public GridPane gridPane;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
img_x = new ImageView("file:x.png");
img_o = new ImageView("file:o.png");
img_x.setUserData("X");
img_o.setUserData("O");
HBox hbox = new HBox();
hbox.getChildren().addAll(img_x, img_o);
VBox vbox = new VBox();
vbox.getChildren().addAll(hbox, gridPane);
root.setCenter(vbox);
I'm working in NetBeans 8 with Java 8/JavaFX.
I have an application that starts a main stage based on an fxml file.
There is a menu option for a user to bring up a second stage on request. The function that opens the window looks like this:
#FXML
private void openChildWindow(ActionEvent event) throws Exception {
Group root = new Group();
Stage stage = new Stage();
AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
root.getChildren().add(frame);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
The content of second.fxml looks like this (after all the import statements):
<AnchorPane id="AnchorPane" prefHeight="680.0" prefWidth="1020.0" xmlns:fx="http://javafx.com/fxml/1" >
<stylesheets><URL value="#css/mycss.css" /></stylesheets>
<children>
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="10.0"
AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="0.0" >
<Tab text="A" closable="false"></Tab>
</TabPane>
</children>
</AnchorPane>
The problem: when I click on the corner of the second window to drag and resize it, the contents are not resizing. What am I missing or what do I need so that it will auto-resize?
Group is not directly resizable as stated in its javadoc. So it does not being resized by the scene while the window is resized from the corner. You can use the subclasses of Pane instead of. For example try with
VBox root = new VBox();
Instead of using a Group, please put your AnchorPane directly into your Scene
#FXML
private void openChildWindow(ActionEvent event) throws Exception {
Stage stage = new Stage();
AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
Scene scene = new Scene(frame );
stage.setScene(scene);
stage.show();
}
A Group cannot be resized, "A Group will take on the collective bounds of its children and is not directly resizable." http://docs.oracle.com/javafx/2/api/javafx/scene/Group.html
Replace Group with a different of layout such as StackPane, or BorderPane.
I struggled with the same thing, if we only had read the documentation. :D