I have a function that updates a ListView in my FXML Controller Class. I want this to run every time the user presses F5.
I'm not sure what the best way is to achieve this and tried following:
1. Get the scene from the controller
I tried to get the scene like here and added scene.onKeyPressed(e -> ...);. But I failed to find a way to get the scene reliably.
2. Call the function from outside
Furthermore I tried to handle this from my scene controller, not my preferred way, because I don't want to call this method when this particular file is not loaded. I load the FXML file with layout.setCenter(FXMLLoader.load(...)); I failed to get an instance of the Controller itself, where I could call the method.
What is wrong with my design? Or is there an #FXML annotation that allows me to handle a KeyEvent?
Example
ApplicationManager:
#Override
public void start(Stage stage){
BorderPane layout = new BorderPane();
Scene scene = new Scene(layout);
layout.setCenter(FXMLLoader.load(getClass().getResource("/designs/lobby.fxml");
}
LobbyFxmlController:
#FXML private ListView<Label> lobbyListView;
#FXML
public void initialize(){
//I can't get the scene here
}
private void loadLobbies(){
// I need to run this on F5 presses
lobbyListView.setItems("lobby 1", "lobby 2", "lobby 3");
}
I just needed to add onKeyPressed="#handleKeyPress" to the FXML layout item and handle this method in the Controller.
Related
I am having the following problem with a program that I am currently writing, and I have searched on the internet, but I couldn't really find anything to help me understand the following problem
So inside another class I have written a method that executes this whenever the search button is clicked and the method looks like this:
public void searchButton(){
try {
new SearchController().display();
} catch (IOException e) {
e.printStackTrace();
}
}
And then the SearchController class looks something like this (I simplified it here):
public class SearchController {
#FXML
private Button cancelButton;
#FXML
private Label what;
private static Stage stage;
private static BorderPane borderPane;
#FXML
public void initialize(){
what.setText("Testing"); // this woks
cancelButton.setOnAction(e -> stage.close());
}
public void display() throws IOException {
stage = new Stage();
stage.setResizable(false);
stage.setTitle("Product search");
stage.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(SearchController.class.getResource("Search.fxml"));
borderPane = loader.load();
Scene scene = new Scene(borderPane);
stage.setScene(scene);
//what.setText("Testing") and this doesn't work
stage.showAndWait();
}
}
Can someone please tell me why it is possible to write text on the initialize method (that method gets called after the borderPane = loader.load(); line...so why doesn't it work if I try to write on the label after that line?)
Thank you in advance
The FXMLLoader creates an instance of the class specified in the fx:controller attribute of the FXML root element. It then injects the elements defined in the FXML file into the controller instance it created when the fx:id attributes match the field names. Then it calls the initialize() method on that instance.
You create an instance of the controller "by hand" with new SearchController(). This is not the same object that is created by the FXMLLoader. So now when you have loaded the fxml file you have two different instances of SearchController. So if you call what.setText(...) from the display() method, you are not calling it on the controller instance created by the FXMLLoader. Consequently, what has not been initialized in the instance on which you are calling what.setText(...), and you get a null pointer exception.
Since initialize() is invoked by the FXMLLoader on the instance it created, when you call what.setText(...) from the initialize() method, you are calling it on the instance created by the FXMLLoader, and so the FXML-injected fields for that instance have been initialized.
I got a controller which launchs a new window called file :
// MainController.java
Stage primaryStage = new Stage();
FXMLLoader fx = new FXMLLoader(Paths.get("../file.fxml").toUri().toURL());
Scene scene = new Scene(fx.load());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.centerOnScreen();
FileController control = (FileController ) fx.getController();
control.getValiderBE().setOnAction(event1 -> {
System.out.prinln("here 1");
});
I got a Button in the FXML file :
// file.fxml, fx:controller="FileController"
<Button fx:id="validerBE" onMouseClicked="#validerClickBE" text="Valider">
</Button>
In its controller (instance of FileController) I got :
FileController.java
#FXML
private Button validerBE;
#FXML
void validerClickBE(MouseEvent event) {
System.out.prinln("here 2");
}
public Button getValiderBE() {
return validerBE;
}
And print goes in this order : here 1 => here 2. And sometines method in FileController is even not called^^ Like if event is down in here 1 call
But I'd to have in the other order, because new window add an element to a list, and first source controller should refresh a TableView (so after adding operation)
First, note that you should be using onAction to handle button clicks, not onMouseClicked. The reason for this is that if the user navigates to the button and activates it (typically with the space bar), or otherwise fires the button without the mouse (e.g. with a mnemonic), the onMouseClicked handler won't be invoked.
So you should have
<Button fx:id="validerBE" onAction="#validerClickBE" text="Valider" />
and
#FXML
void validerClickBE(ActionEvent event) {
System.out.prinln("here 2");
}
For ordering the events: there is no general way to control the order in which event handlers are invoked. There are some special cases, which you may be able to take advantage of in some specific circumstances, but in general the order of handler invocation is not specified. Consequently, if you have two actions that are dependent on each other, they should be performed in the same handler.
At any rate, it is generally bad practice to expose the UI controls outside of the controller anyway - it violates the principal of encapsulation and will make your code much harder to maintain. Typically you should pass a data model to the controller, and then the controller can invoke any actions on the data model that it needs to invoke.
At the very least, you can define a field to represent an action to perform when the button is pressed, and invoke it from your existing handler, though of course a proper MVC approach is far preferable:
public class FileController {
private Runnable onValidate = () -> {} ;
public void setOnValidate(Runnable onValidate) {
this.onValidate = onValidate ;
}
#FXML
private void validerClickBE(ActionEvent event) {
// whichever order you need....
onValidate.run();
System.out.println("here 2");
}
}
and then of course
FXMLLoader fx = new FXMLLoader(Paths.get("../file.fxml").toUri().toURL());
Scene scene = new Scene(fx.load());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.centerOnScreen();
FileController control = (FileController ) fx.getController();
control.setOnValidate(() -> System.out.println("here 1"));
I start exploring the JavaFX FXML application technology.
I use one main Stage accessed in Main class with Main.getStage() that is invoked in the start of application with the overriden method public void start(Stage stage). Having two public static Scene inside to keep the persistence while switching them.
#Override
public void start(Stage stage) throws Exception {
STAGE = stage;
LOGIN = new Scene(FXMLLoader.load(getClass().getResource("Login.fxml")));
REGISTER = new Scene(FXMLLoader.load(getClass().getResource("Register.fxml")));
STAGE.setScene(LOGIN);
STAGE.setTitle("FXApplication");
STAGE.show();
}
public static Stage getStage() {
return STAGE;
}
Both Scenes have the same controller class called MainController. Using:
Button with fx:id="buttonLoginRegister" to go to the REGISTER Scene
Button with fx:id="buttonRegisterBack" to go back to the LOGIN one.
and both having the same onClick event handleButtonAction(ActionEvent event). The TextFields are fields for a username to log in/register.
#FXML private Button buttonLoginRegister;
#FXML private Button buttonRegisterBack;
#FXML private TextField fieldLoginUsername;
#FXML private TextField fieldRegisterUsername;
#FXML
private void handleButtonAction(ActionEvent event) throws IOException {
Stage stage = Main.getStage();
if (event.getSource() == buttonLoginRegister) {
stage.setScene(Main.REGISTER);
stage.show();
// Setting the text, the working way
TextField node = (TextField) stage.getScene().lookup("#fieldRegisterUsername");
node.setText(fieldLoginUsername.getText());
// Setting the text, the erroneous way
// fieldRegisterUsername.setText(fieldLoginUsername.getText());
} else {
stage.setScene(Main.LOGIN);
stage.show();
}
}
My goal is to copy the value from the LOGIN TextField to the one in the REGISTER scene. It works well using the code above. However firstly I tried to access the element in the another Scene with:
fieldRegisterUsername.setText(fieldLoginUsername.getText());
And it's erroneous. To be exact, the fieldRegisterUsername is null.
Why are some elements found with the lookup(String id) method and not with #FXML annotation?
As mentioned in my comment, sharing a controller between different views is rarely a good idea, and I'd strongly advise you to make a separate controller for each view.
As to your problem itself - you have two instances of your controller class, one for each time you call FXMLLoader.load. Presumably, one view has the fieldLoginUsername TextField, while the other has fieldRegisterUsername.
If the condition of the if statement is met, it means the active scene was the Login scene, thus the controller handling it is the one which has fieldLoginUsername, so naturally fieldRegisterUsername will be null.
But on the first line inside the if clause you change the active scene to the Register one, so by the time you call scene#lookup you are referring to the scene whose controller is the Register controller, the one that does have fieldRegisterUsername.
If you were to call scene#lookup before changing the active scene you would find it returns null as well.
If you must use the same class for controller, you probably want to make sure you only have one instance of that class. That would necessitate using FXMLLoader#setController.
I've recently delved into JavaFX as a C# developer. One thing I noticed in Java is that you're not spoon fed the way Visual Studio/Microsoft spoonfeed us.
So. When I was creating a form using the scene builder for IntelliJ Idea on JavaFX. I inherited "Stage" for my controller class and created a void called load that will load the instance of the scene from the FXML file. Therefore when I call load() from the Main entry point or anywhere it will load the fxml file and show.
LoginController frmLogin = new LoginController();
frmLogin.load();
The problem is that it works and it does't work.
Here's my code.
Main.Java
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
LoginController frmLogin = new LoginController();
frmLogin.load();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
LoginController.Java
public class LoginController extends Stage
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
this.show(); //The problem occurs here!
}
else
{
Messagebox.Show("Incorrect Login");
}
}
public void load() throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
this.setScene(new Scene(root));
this.setTitle("JavaFX GUI");
this.setResizable(false);
this.initModality(Modality.APPLICATION_MODAL);
this.show();
}
}
Here's a GIF of the problem.
http://i.imgur.com/0hOG76M.gif
I want to know why when I call .show() it shows a blank for?
Any help would be appreicated.
Solution
Don't inherit Stage in your Controller.
JavaFX will implicitly create a Stage for your application and pass it to your application (the primaryStage parameter in your application start method).
Sample
Here is a quick update which should work. Another alternative for this is to factor out the stage management as in James's answer.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("JavaFX GUI");
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
. . .
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Aside: I am not sure what your MessageBox class is, but JavaFX 8u40 has a built-in Alert dialog box for standard message box style functionality, so that would be the preferred method to do that.
It looks like you have confused the different pieces that make up the application.
The FXML typically represents the "view"; i.e. the portion of the UI that is visible. It defines what controls are displayed and how they are laid out.
The controller implements the logic that is connected to (controls) the view. So it typically processes user input and updates the view in various ways.
A Stage is a window.
So, I don't think it really makes sense that your controller is a Stage. There are some scenarios where you might make a controller a subclass of a UI element, but those are somewhat advanced uses of JavaFX, and even then you would typically subclass a layout pane, not a Stage.
Here's roughly what happens when you call load on an FXMLLoader:
The FXMLLoader creates a hierarchy of Nodes (UI elements) corresponding to the elements defined in the FXML file
If the FXML file defines a fx:controller attribute in its root element, the FXMLLoader constructs a new instance of that class. It then injects any elements with fx:id attributes into fields in that controller instance with names matching the fx:id values. It also registers any event handlers mapping to methods in the controller instance.
The FXMLLoader's load() method returns the object corresponding to the root element of the FXML file.
So, in your code, you actually end up with two LoginController instances. You create one yourself in the start() method. You then call load() on that instance. That method calls load(...) on an FXMLLoader (via the really ugly static load method). Calling FXMLLoader.load(...) then causes the FXMLLoader to create an instance of the class declared in fx:controller. I'm guessing (you didn't show the FXML code) that class is also LoginController. So that is the second instance.
Now what happens, is that you get a reference to the UI element from FXMLLoader.load(). You put that in a Scene, and set the Scene in the LoginController, which - unusually - is a Stage. Then you make the Stage appear on the screen with show(). Note this happens in the instance you created in the start method.
When the user presses the button that has btnLogin_Clicked registered as its handler, the handler method is invoked on the controller instance: the one created by the FXMLLoader. That instance never had a Scene set, so when you then call this.show() it shows that instance of the LoginController (which, again, is a Stage). Since it never had its scene set, you see a blank window.
It's not actually clear to me what you intend with the call to this.show() in btnLogin_Clicked anyway. Assuming you thought this was the same Stage you had created from the start(...) method, that Stage is already showing.
The typical pattern is that you use the primaryStage that is passed to the start(...) method, and set a scene in that and show it. So you'd do something like:
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("frmLogin.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("JavaFX GUI");
primaryStage.setResizable(false);
primaryStage.initModality(Modality.APPLICATION_MODAL);
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
and then the controller is just a controller: it just handles the logic:
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
// I don't really know what you were trying to do here
// but if you need a reference to the window containing the
// associated fxml elements, you can get it from one of those
// elements:
Stage stage = (Stage) txtUsername.getScene().getWindow();
//this.show(); //The problem occurs here!
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Typically what you want to do when the user has successfully logged in, is to display something new in the current window. The simplest way to do this is just to set the root of the current scene to the content of a different FXML file. For example:
public class LoginController
{
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private void btnLogin_Clicked(ActionEvent e) throws Exception
{
if (txtUsername.getText().equals("admin") && txtPassword.getText().equals("pass"))
{
Messagebox.Show("Correct Login!");
Scene currentScene = txtUsername.getScene();
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
currentScene.setRoot(root);
// resize window:
currentScene.getWindow().sizeToScene();
}
else
{
Messagebox.Show("Incorrect Login");
}
}
}
Here Main.fxml defines the main application the user sees, having successfully logged in, and defines its own controller class, etc.
Is there any way of getting the Scene object of an FXML loaded file from the associated class controller.
I'm doing something like this:
#FXML
private AnchorPane anchor;
Scene scene = anchor.getScene();
but i'd like a solution that does not reference the AnchorPane control.
Why not? Controller is an abstract class, he's not aware about UI unless you deliberately make him know.
Nodes (inlcuding AnchorPane) are another story, they hardly exists outside for scenegraph. So it's perfectly fine to ask Node about his parent or scene.
If you still want to handle that separately there are next approaches:
you can create a custom controller and set scene after loader. Just note that at the time initialize() called it wouldn't yet initialized.
public class MyController {
private void Scene scene;
public void setScene(Scene scene) { this.scene = scene; }
}
// loading code
FXMLLoader fxmlLoader = new FXMLLoader();
AnchorPane root = (AnchorPane) fxmlLoader.load(getClass().getResource("MyApp.fxml"));
MyController myController = (MyController) fxmlLoader.getController();
myController.setScene(scene);
You can create a custom fxml control which will incorporate controller and he can just call getScene() for itself. See an example here: https://stackoverflow.com/a/10718683/1054140
I tried your answer, but it did not work, I found the reason here:
JavaFX: How to get stage from controller during initialization?
after the comment:
// loading code
don't use the static load method
AnchorPane root=(AnchorPane) FXMLLoader.load(getClass().getResource("MyApp.fxml"));
but instead use instantiated loader's method
AnchorPane root=(AnchorPane) fxmlLoaded.load(getClass().getResource("MyApp.fxml"));