Javafx adding dynamically pane to vbox Duplicate Children error - javafx

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);

Related

converting java fx code into java scenebuilder

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));
}
}

How to set the scrollPane to the desired vValue?

I have a tilepane inside a scrollpane. The tilepane is filled with buttons that have actions like delete or edit. After I delete or edit a button, the scrollpane automatically goes back to the top, which I don't want. I want it to remain at the current position (the position the user scrolled to).
I tried getting and setting the vValue of the scrollpane. getvValue gets the value and the setter sets it, but the scrollpane doesn't respond to it and goes back to the top after the action (delete/ edit). I tried a solution from this question: JavaFX ScrollPane setVvalue() not working as intended, but the layout() method doesn't do anything either. What am I doing wrong here? How do I make the scrollpane stay where it is?
EDIT: I found the problem, but still don't know how to fix it. Apparently setting and changing the visibility on the hBoxes in fxml puts the scrollpane back to the top. If I remove the setVisible methods and run it, I can delete items and the scrollpane stays in position. How to fix this?
On request I created a Minimal, Complete, and Verifiable example of my code. Run it, scroll down a bit, right click a button with a name and price on it and then click delete. It scrolls back up to the top.
This is my code:
public class Controller {
private static LinkedHashMap<String, BigDecimal> mProductMap = new LinkedHashMap<>();
#FXML private TilePane fieldContainer = new TilePane();
#FXML private ScrollPane scroll;
#FXML private Button deletebtn;
#FXML private Button editbtn;
#FXML private HBox homeBar;
#FXML private HBox actionBar;
#FXML
private void initialize() {
addProduct("Coffee", new BigDecimal("2.00"));
addProduct("Tea", new BigDecimal("2.00"));
addProduct("Cappuccino", new BigDecimal("2.00"));
addProduct("Espresso", new BigDecimal("2.00"));
addProduct("Cooky", new BigDecimal("2.00"));
addProduct("Candy", new BigDecimal("2.00"));
addProduct("Chocobar", new BigDecimal("2.00"));
addProduct("Cola", new BigDecimal("2.00"));
addProduct("Fanta", new BigDecimal("2.00"));
addProduct("Beer", new BigDecimal("2.00"));
addProduct("Salad", new BigDecimal("2.00"));
addProduct("Sandwich", new BigDecimal("2.00"));
addProduct("Water", new BigDecimal("2.00"));
addProduct("Cassis", new BigDecimal("2.00"));
}
// makes the delete and edit buttons appear after selecting a button from the tilepane
private void select(String selectedProduct) {
/*
the setVisible method for the hBoxes in fxml cause the scrollbar to go back to the top
without them, the scrollpane stays where it is.
I tried changing visibility with both setVisible and CSS, but they both cause the problem
I need the actionbar to appear when you select a button (right click on it)
*/
homeBar.setVisible(false);
actionBar.setVisible(true);
EventHandler<ActionEvent> delete = event -> {
deleteProduct(selectedProduct); // deletes an item from a LinkedHashMap
homeBar.setVisible(true);
actionBar.setVisible(false);
};
deletebtn.setOnAction(delete);
// I want the same to happen when the edit handler is used, scrollpane needs to remain its position
EventHandler<ActionEvent> edit = event -> {
editProduct(selectedProduct); // edits an item from a LinkedHashMap
homeBar.setVisible(true);
actionBar.setVisible(false);
};
editbtn.setOnAction(edit);
}
/*
Code below does not cause the problem, but I added it as a reference
*/
private void deleteProduct(String product) {
if (mProductMap.containsKey(product)) {
mProductMap.remove(product);
System.out.printf("%s has been deleted!%n", product);
} else {
System.out.printf("%s does not exist. Please try again.%n", product);
}
addButtons();
}
private void editProduct(String product) {
List<String> indexKeys = new ArrayList<>(mProductMap.keySet());
List<BigDecimal> indexValues = new ArrayList<>(mProductMap.values());
BigDecimal price = mProductMap.get(product); // gets the product's value (the price)
int indexKey = indexKeys.indexOf(product);
int indexValue = indexValues.indexOf(price);
if (mProductMap.containsKey(product)) {
int sizeBefore = mProductMap.size();
addingProduct();
int sizeAfter = mProductMap.size();
if (sizeAfter > sizeBefore) {
indexKeys.remove(product);
indexValues.remove(price);
mProductMap.remove(product);
// Make a new list to get the new entry at the end
List<Map.Entry<String,BigDecimal>> entryList = new ArrayList<>(mProductMap.entrySet());
Map.Entry<String, BigDecimal> lastEntry = entryList.get(entryList.size()-1);
String key = lastEntry.getKey();
BigDecimal value = lastEntry.getValue();
indexKeys.add(indexKey, key);
indexValues.add(indexValue, value);
mProductMap.clear();
// Put the keys and values from the two lists back to the map
for (int i=0; i<indexKeys.size(); i++) {
addProduct(indexKeys.get(i), indexValues.get(i));
}
}
} else {
System.out.printf("%s does not exist. Please try again.%n", product);
}
}
void addProduct(String product, BigDecimal price) {
mProductMap.put(product, price);
addButtons();
}
// Adding buttons to the TilePane fieldContainer in center of BorderPane
// One button per key-value pair of mProductMap
private void addButtons() {
// clears the TilePane to prevent duplicate buttons
fieldContainer.getChildren().clear();
for (Map.Entry<String, BigDecimal> entry : mProductMap.entrySet()) {
StackPane newField = new StackPane();
Button main = new Button();
main.setOnMousePressed(me -> {
if (me.getButton() == MouseButton.SECONDARY) { // = right click
select(entry.getKey());
}
});
main.setText(entry.getKey() + "\n" + entry.getValue());
newField.getChildren().add(main);
fieldContainer.setAlignment(Pos.TOP_LEFT);
fieldContainer.getChildren().add(newField);
}
}
// Popup for adding products to the Map with the + button
#FXML
private void addingProduct(){
Stage newStage = new Stage();
VBox popup = new VBox();
final BooleanProperty firstTime = new SimpleBooleanProperty(true); // Variable to store the focus on stage load
TextField product = new TextField("");
product.setId("product");
product.setPromptText("Enter the item name...");
// code to remove the focus from first textfield on stage load
product.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue && firstTime.get()){
popup.requestFocus(); // Delegate the focus to container
firstTime.setValue(false); // Variable value changed for future references
}
});
TextField price = new TextField("");
price.setId("price");
price.setPromptText("Enter the item price...");
Button submit = new Button("Submit");
Label label = new Label();
label.setId("label");
submit.setOnAction(e -> {
if ( (product.getText() != null && !product.getText().isEmpty() &&
price.getText() != null && !price.getText().isEmpty() ) ) {
addProduct(product.getText(), new BigDecimal(price.getText()) );
newStage.close();
} else {
label.setText("Fill in both fields");
}
});
popup.getChildren().add(product);
popup.getChildren().add(price);
popup.getChildren().add(submit);
popup.getChildren().add(label);
Scene stageScene = new Scene(popup, 300, 200);
newStage.setScene(stageScene);
newStage.showAndWait();
}
}
The FXML:
<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<top>
<StackPane>
<HBox fx:id="homeBar" styleClass="main-bar" visible="true">
<Button StackPane.alignment="BOTTOM_LEFT">Home</Button>
<Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
</HBox>
<HBox fx:id="actionBar" styleClass="main-bar" visible="false">
<Button fx:id="deletebtn" StackPane.alignment="BOTTOM_CENTER">Delete</Button>
<Button fx:id="editbtn" StackPane.alignment="BOTTOM_CENTER">Edit</Button>
<Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
</HBox>
</StackPane>
</top>
<center>
<ScrollPane fx:id="scroll" hbarPolicy="NEVER">
<TilePane fx:id="fieldContainer" prefColumns="2" prefTileHeight="100.0" prefTileWidth="144.0">
</TilePane>
</ScrollPane>
</center>
<bottom>
</bottom>
</BorderPane>
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I fixed it myself, with help from answers from this post: ScrollPane jumps to top when deleting nodes.
After deleting a button from the TilePane, the ScrollPane finds the next node to focus on, which by default is the first node from the pane. By requesting the focus on the TilePane (fieldContainer) the ScrollPane stays in place.
I added this code to both the delete and edit methods:
fieldContainer.requestFocus();

JavaFX - creating custom dialogs using fxml

I'm new in javafx and I was trying to create custom dialogs/alerts. The thing is that I'm using Scene Builder to design the GUI, and I want to modify the dialog each time I load the fxml file (i.e change the title, label text, etc.), so I wanted to know if there's a way to send parameters and modify the stage/scene, or any other way I can achieve this.
To be more specific, let's say there's an error I want to handle anywhere in my program, so I load a new fxml file that represents the error dialog I created, and I modify the components inside it, depending on the type of error I need to handle, similar to, for example, JOptionPane.showMessageDialog(...) in swing.
For the use case you describe, you can just use the Dialog API, or the specialized Alert class that is part of that.
For the more general question you ask:
I wanted to know if there's a way to send parameters and change the stage/scene
the way to do this is to use the custom component mechanism described in the documentation.
In short, make a subclass of the UI type you need that loads the FXML file, and defines the properties you need, e.g.
public class ExceptionPane extends BorderPane {
private final ObjectProperty<Exception> exception ;
public ObjectProperty<Exception> exceptionProperty() {
return exception ;
}
public final Exception getException() {
return exceptionProperty().get();
}
public final void setException(Exception exception) {
exceptionProperty().set(exception);
}
#FXML
private final TextArea stackTrace ;
#FXML
private final Label message ;
public ExceptionPane() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
loader.setRoot(this);
loader.setController(this);
loader.load();
exception.addListener((obs, oldException, newException) -> {
if (newException == null) {
message.setText(null);
stackTrace.setText(null);
} else {
message.setText(newException.getMessage());
StringWriter sw = new StringWriter();
newException.printStackTrace(new PrintWriter(sw));
stackTrace.setText(sw.toString());
}
});
}
}
Then define the FXML using a "dynamic root":
<!-- imports etc -->
<fx:root type="BorderPane" ...>
<center>
<TextArea fx:id="stackTrace" editable="false" wrapText="false" />
</center>
<top>
<Label fx:id="message" />
</top>
</fx:root>
Now you can use this directly in either Java or in FXML:
try {
// some code...
} catch (Exception exc) {
ExceptionPane excPane = new ExceptionPane();
excPane.setException(exc);
Stage stage = new Stage();
stage.setScene(new Scene(excPane));
stage.show();
}
or
<fx:define fx:id="exc"><!-- define exception somehow --></fx:define>
<ExceptionPane exception="${exc}" />

JavaFX: Dynamically added VBox does not show up

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! :)

How can I use .setText on a non-Static Label from a different Class [duplicate]

I have written a controller for two windows /stages.
The first window is opened in the MainClass. The second in the Controller, if the user clicks onto a button.
How can I get the TextFields from second.fxml in the applyFor()-method?
Thanks.
#FXML
protected void requestNewAccount(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("second.fxml")); // TextFields in there
Parent root = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Second Window");
Scene scene = new Scene(root);
String css = MainOnlineCustomer.class.getResource("/style.css").toExternalForm();
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
logger.error(e);
}
}
/**
* closes the "second"-Window
* #param event
*/
#FXML
protected void cancel(ActionEvent event) {
final Node source = (Node) event.getSource();
final Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
#FXML
protected void applyFor(ActionEvent event) {
// get values from TextField in second.fxml here!!!
}
It's not good to share controllers between fxmls unless they serve the same purpose. Here both fxml seem to serve a different purpose (account management, login or something similar for one of them and creating a new account for the other). What is even worse is that those classes do not share the same controller instance, which means the small (and probably only) benefit you could get from using the same controller, is not used here. You should better use different controllers.
Since you use Modality.APPLICATION_MODAL as modality, I'd recommend using showAndWait instead of show to open the new stage. This will enter a nested event loop, which allows the UI to remain responsive and continues after the invocation of showAndWait once the stage is closed.
Furthermore add a method to the controller of second.fxml that allows you to retrieve the result.
Example
This creates a Person object with given name and family name.
"primary window (opening the "inner" stage)
FXMLLoader loader = new FXMLLoader(getClass().getResource("second.fxml"));
Stage subStage = new Stage();
subStage.initModality(Modality.APPLICATION_MODAL);
subStage.setTitle("Second Window");
Scene scene = new Scene(loader.load());
subStage.setScene(scene);
subStage.showAndWait();
Optional<Person> result = loader.<Supplier<Optional<Person>>>getController().get();
if (result.isPresent()) {
// do something with the result
}
controller for "inner" content
public class SecondController implements Supplier<Optional<Person>> {
#FXML
private TextField givenName;
#FXML
private TextField familyName;
private boolean submitted = false;
// handler for submit action
#FXML
private void submit() {
submitted = true;
givenName.getScene().getWindow().hide();
}
// handler for cancel action
#FXML
private void cancel() {
givenName.getScene().getWindow().hide();
}
#Override
public Optional<Person> get() {
return submitted ? Optional.of(new Person(givenName.getText(), familyName.getText())) : Optional.empty();
}
}
Note that you can gain access to any data available to the controller this way. I wouldn't recommend accessing any nodes (like TextFields) directly though, since this makes changing the UI harder.
Using the Supplier interface here is not necessary, but I chose to do this to achieve a loose coupling between SecondController and the main window.

Resources