Issue
I'm trying to open multiple windows with JavaFX, I have an eventlistener that opens a new window when a button is clicked it looks like this:
@FXML
private void joinAction() {
Parent root;
try {
Stage stage = (Stage) joinButton.getScene().getWindow();
stage.close();
root = FXMLLoader.load(getClass().getResource("main.fxml"));
stage = new Stage();
stage.setTitle("TuneUs");
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {e.printStackTrace();}
}
the first window opens and the new one opens, but my problem is getting events to work with my second window
in main.fxml
I have this line:
<TextField id="chat_bar" onAction="#sendChat" layoutX="14.0" layoutY="106.0" prefHeight="22.0" prefWidth="403.0"/>
Then in my controller class I have this method:
@FXML
private void sendChat() {
System.out.println("test");
}
but Intellij is telling me that; no controller specified for top level element
So, my question is: Do I need to create multiple controller classes or can I use just one for multiple windows if so how?
Solution
The recommended approach is to define a controller for each FXML. Since controllers are very lightweight this shouldn't add much overhead. The controller for your main.fxml file might be as simple as
import javafx.fxml.FXML ;
public class MainController {
@FXML
private void sendChat() {
// ...
}
}
I have used this approach with fairly large numbers of FXML files and corresponding controllers in a single project, and have had no issues with managing the code etc. I recommend using a naming convention of the form Main.fxml <-> MainController
.
If your controllers need to share data, use the techniques outlined in Passing Parameters JavaFX FXML
As @Vertex points out in the comments, there is an alternative approach provided by the FXMLLoader.setController(...)
method. So in your example above, you could do
@FXML
private void joinAction() {
Parent root;
try {
Stage stage = (Stage) joinButton.getScene().getWindow();
stage.close();
FXMLLoader loader = new FXMLLoader (getClass().getResource("main.fxml"));
loader.setController(this);
root = loader.load();
stage = new Stage();
stage.setTitle("TuneUs");
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {e.printStackTrace();}
}
@FXML
private void sendChat() {
// ...
}
This approach is fine if you are not setting any fields (controls) via FXML injection (i.e. with an fx:id
attribute in the fxml and a corresponding @FXML
annotation in the controller). If you are, it will be very difficult to keep track of when those fields have been set. Moreover, if your joinAction
handler is invoked multiple times, you will have multiple instances of the node created by main.fxml, but all sharing a single controller instance (and consequently overwriting the same injected fields). Also note that with this approach, your initialize()
method will be invoked both when the original fxml file is loaded, and when the main.fxml file is loaded, which will almost certainly cause undesired effects.
One last note: if you have many FXML files, and corresponding controllers, you might want to look at the afterburner.fx framework. This is a very lightweight framework that mandates a naming convention on FXML files and their corresponding controllers, and also provides a (very) easy mechanism for sharing data between them.
Answered By - James_D
Answer Checked By - Katrina (JavaFixing Volunteer)