I have a class that contains information based on which the title text in the next scene will be modified. So I need to pass the Object or at least a String to the new scene, using which I'll update the text in the next scene during Initialization.
void sceneSwitch(Event event, String fxmlName, Class child){
String fxmlPath = "gui/resources/fxml/";
Node node = (Node) event.getSource();
Stage stage = (Stage) node.getScene().getWindow();
FXMLLoader sceneLoader = fxmlLoadErrorHandler(fxmlPath + fxmlName, child); //This sets up the FXMLLoader
Parent newScene = fxmlLoadErrorHandler(sceneLoader);
/* The last line above is basically the .load() function, but this already
launches the Initialize function, in which I need to use 'session' Object
which I only yet set below via setActive */
CoreController controller = sceneLoader.getController();
controller.setActive(session);
Scene scene = null;
if(newScene != null) {
scene = new Scene(newScene);
}
stage.setScene(scene);
stage.show();
}
So is there any way for me to do what I'm trying to here? It would be sufficient to just send a String to it. I thought of defining a function in the CoreController that I'll run as soon as I set Session before the scene shows, but CoreController is an abstract Class and I don't have access to modifying the objects in the subclass.
Answer by Slaw in a comment in response to the question. Thanks! I feel embarassed I didn't think of this.
"You could expose a method in the abstract class that sub-classes would override; the overrides would call the super implementation as well as do their own thing. Another option is to not use fx:controller and instead call FXMLLoader.setController before loading. A third option is to use a controller factory."
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 would like to communicate with a FXML controller class at any time, to update information on the screen from the main application or other stages.
Is this possible? I havent found any way to do it.
Static functions could be a way, but they don't have access to the form's controls.
Any ideas?
You can get the controller from the FXMLLoader
FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("foo.fxml").openStream());
FooController fooController = (FooController) fxmlLoader.getController();
store it in your main stage and provide getFooController() getter method.
From other classes or stages, every time when you need to refresh the loaded "foo.fxml" page, ask it from its controller:
getFooController().updatePage(strData);
updatePage() can be something like:
// ...
#FXML private Label lblData;
// ...
public void updatePage(String data){
lblData.setText(data);
}
// ...
in the FooController class.
This way other page users do not bother about page's internal structure like what and where Label lblData is.
Also look the https://stackoverflow.com/a/10718683/682495. In JavaFX 2.2 FXMLLoader is improved.
Just to help clarify the accepted answer and maybe save a bit of time for others that are new to JavaFX:
For a JavaFX FXML Application, NetBeans will auto-generate your start method in the main class as follows:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now, all we need to do to have access to the controller class is to change the FXMLLoader load() method from the static implementation to an instantiated implementation and then we can use the instance's method to get the controller, like this:
//Static global variable for the controller (where MyController is the name of your controller class
static MyController myControllerHandle;
#Override
public void start(Stage stage) throws Exception {
//Set up instance instead of using static load() method
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
//Now we have access to getController() through the instance... don't forget the type cast
myControllerHandle = (MyController)loader.getController();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Another solution is to set the controller from your controller class, like so...
public class Controller implements javafx.fxml.Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
// Implementing the Initializable interface means that this method
// will be called when the controller instance is created
App.setController(this);
}
}
This is the solution I prefer to use since the code is somewhat messy to create a fully functional FXMLLoader instance which properly handles local resources etc
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/sample.fxml"));
}
versus
#Override
public void start(Stage stage) throws Exception {
URL location = getClass().getResource("/sample.fxml");
FXMLLoader loader = createFXMLLoader(location);
Parent root = loader.load(location.openStream());
}
public FXMLLoader createFXMLLoader(URL location) {
return new FXMLLoader(location, null, new JavaFXBuilderFactory(), null, Charset.forName(FXMLLoader.DEFAULT_CHARSET_NAME));
}
On the object's loading from the Main screen, one way to pass data that I have found and works is to use lookup and then set the data inside an invisible label that I can retrieve later from the controller class. Like this:
Parent root = FXMLLoader.load(me.getClass().getResource("Form.fxml"));
Label lblData = (Label) root.lookup("#lblData");
if (lblData!=null) lblData.setText(strData);
This works, but there must be a better way.
I have two scenes. The first scene invokes second scene using the following code.
#FXML
private void confirmation(ActionEvent event) throws IOException{
Stage confirmation_stage;
Parent confirmation;
confirmation_stage=new Stage();
confirmation=FXMLLoader.load(getClass().getResource("Confirmation.fxml"));
confirmation_stage.setScene(new Scene(confirmation));
confirmation_stage.initOwner(generate_button.getScene().getWindow());
confirmation_stage.show();
}
There is a label in "Confirmation.fxml" called "Proceed".
I need to change the content of that label from within this function and return the result(true/false). Help?
Create a ConfirmationController for the FXML. From the controller, expose a method which allows you to pass data (string) to set to the label.
public class ConfirmationController implements Initializable {
...
#FXML
private Label proceed;
...
public void setTextToLabel (String text) {
proceed.setText(text);
}
...
}
Inside your method where you are loading the FXML, you can have :
...
FXMLLoader loader = new FXMLLoader(getClass().getResource("Confirmation.fxml"));
confirmation = loader.load();
ConfirmationController controller = (ConfirmationController)loader.getController();
controller.setTextToLabel("Your Text"); // Call the method we wrote before
...
Labels in FXML have a setText method. So for your case the "Proceed" label will look something like:
Proceed.setText("The new text");
As for the second part of the question, I'm not 100% sure as to what you are asking. I don't really see any case for the function to return true or false.
Assuming that you have a controller called:confirmation_controller.java'. inside that controller, you have a public method getProceedLabel() that returns a reference for the label called Proceed. you can try the following code:
Stage confirmation_stage;
Parent confirmation;
confirmation_stage=new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Confirmation.fxml"));
confirmation = loader.load();
confirmation_controller controller = loader.getController();
Label label = controller.getProceedLabel();
label.setText("..."):
confirmation_stage.setScene(new Scene(confirmation));
confirmation_stage.initOwner(generate_button.getScene().getWindow());
confirmation_stage.show();
I have designed a scene using JavaFX Scene Builder/FXML and I want to create many instances of that scene, but each scene with different behavior. Is there a way to change the controller of a scene/FXML dynamically?
What I want is to design one scene and reuse it, but with different behaviors for each instance.
Currently I am loading the FXML and its controller like this:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
Parent root = (Parent) fxmlLoader.load();
Scene scene = new Scene(root);
Controller controller = fxmlLoader.getController();
There is the possibility to use a custom Controllerfactory with the FXML loader.
The setControllerFactory method expects a Callback type, which is an interface with only one callable function: call which the factory uses. The function (called by the FXMLLoader class) expects to get a Class object as an input parameter and provides an instantiated object based on the type. Above Java8 lambdas can be used to provide the factory:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
return new Controller();
}
});
Parent root = (Parent) fxmlLoader.load();
The argument controllerType in the above code is the the type provided by the fxml fx:controller attribute, which is determined by the Java Class loader. When the Controllerfactory is called, nothing is instantiated yet, that's why even abstract classes can be given in the fxml. To achieve different behavior, inheritance can be used.
An example would be:
class Controller{...}
class FirstController extends Controller{...}
class SecondController extends Controller{...}
And the factory can be used like so:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
final int[] instantiatedClasses = {0};
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
if(0 == instantiatedClasses[0]){
++instantiatedClasses[0];
return new FirstController();
} else return new SecondController();
}
});
Parent root = (Parent) fxmlLoader.load();
Please Note, that this way different arguments can also be supplied to the controller, so inheritance might be an overkill.
For example the primaryStage can be provided to the controller, e.g. for eliminating the need for different setters in the controllers.
class Controller{
public Controller(int behaviorParam){...}
}
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
final int[] instantiatedClasses = {-1}; /* so it would start at 0 */
loader.setControllerFactory((Class<?> controllerType) ->{
if(controllerType == Controller.class){
++instantiatedClasses[0];
return new Controller(instantiatedClasses[0]);
}
});
Parent root = (Parent) fxmlLoader.load();
The challenges of this method, however is that it is still not straightforward to differentiate between the different fxml instances of the same controller type. Counting the number of instantiated classes is one way that works, but it doesn't give much control, compared to anything identifier based.
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"));