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"));
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 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.
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 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 a JavaFx project I created using SceneBuilder.
I am also using a Guice plugin architecture.
I have one .fxml file that has a pane that I want to be the the content of another .fxml file.
Is there any easy way to link .fxml content from one file to another?
I have not used fx.guice plugin architecture before. Is there an easier way to this with plugin control?
Thanks!!
This was a big problem for us since we also are using Guice and JavaFX.
tl;dr I've attached some code at the bottom that we've been using for a year and a bit now without issue.
edit: I should've mentioned we did all this stuff before fx,guice existed, so this exists entirely outside that, and I probably should be using it.
Yes, but you will have to modify the view tree from java, and you must instantiate two controllers. If you're willing to let the fxml loader instantiate your controllers (we're not, more in a sec), then you simply need to have code along the lines of
solution 1:
loadMergedView(){
fxmlLoader.setLocation(getClass().getResource("/com/yourpkg/YourOuterView.fxml"));
Pane outerRoot = fxmlLoader.load();
fxmlLoader.setLocation(getClass().getResource("/com/yourpkg/YourInnerView.fxml"));
Pane innerView = fxmlLoader.load();
((Region)outerRoot.getChildren().get(2))...getChildren().add(innerView);
}
which isn't nice because it means the FX loader will try to create your controller for you, but you probably want guice to do that
luckily you can call setController (or setControllerFactory) to leverage guice, so now we have
solution 2:
#Inject private OuterController outerController
#Inject private InnerController innerController
loadMergedView(){
fxmlLoader.setLocation(getClass().getResource("/com/yourpkg/YourOuterView.fxml"));
//using the setControllerFactory instead of the setController
//means you can still declare the controller type in FXML
//which is good for our IDE intelliJ and general readability
fxmlLoader.setControllerFactory(() -> outerController);
Pane outerRoot = fxmlLoader.load();
fxmlLoader.setLocation(getClass().getResource("/com/yourpkg/YourInnerView.fxml"));
fxmlLoader.setControllerFactory(() -> innerController);
Pane innerView = fxmlLoader.load();
((Region)outerRoot.getChildren().get(2))...getChildren().add(innerView);
}
which is better but requires a 3rd party to load your components. What you really want, a le dependency-injection, is to have the child view be resolved first and as part of the resolution of the parent view.
For us, this brings us to
Solution 3:
class OuterController{
#FXML Pane rootPane;
#FXML Stuff otherStuffBoundInFXML;
#FXML AnchorPane innerContactPaneOne;
#Inject
public OuterController(InnerController inner, FXMLLoader loader){
loader.setControllerFactory(type -> this);
loader.setLocation(getClass().getResource("/com/yourpkg/YourOuterView.fxml"));
loader.load();
innerContactPaneOne.getChildren().add(inner.getRootView());
}
}
class InnerController{
#FXML Pane innerContentPaneTwo; //this will be a child of PaneOne in OuterController
#FXML Button otherStuff;
#Inject
public InnerController(FXMLLoader loader){
loader.setControllerFactory(type -> this);
loader.setLocation(getClass().getResource("/com/yourpkg/YourInnerVIew.fxml"));
loader.load();
}
public Node getRootView(){
return innerContentPaneTwo;
}
}
//with somebody calling
OuterController rootController = injector.getInstance(OuterController.class);
And finally, in the name of 'convention over configuration', we created a few classes (attached below) that attempt to 'automatically' find the view by reflecting on the controllers name (eg OuterController) and assuming that it will find an FXML view view in the same directory as the controller's class file with the word controller replaced with view (eg OuterView.fxml)). We also leveraged a neat trick in super-ctor-order in java to allow us to have pre-setup FXML values.
So now we get:
solution4 :
class OuterController extends PreloadedFX{
#FXML Pane rootPane;
#FXML Stuff otherStuffBoundInFXML;
#FXML AnchorPane innerContactPaneOne;
#FXML Checkbox importantCheckbox;
#FXML Label importantLabel;
// because of 'PrealoadedFX' getting called first,
// you can actually inline initialize object constants
// like this
private final ObservableBooleanValue isSelected = importantCheckbox.selectedProperty();
// or using an initializer
{
int x = 4;
importantLabel.setText(importantLable.getText() + x);
}
#Inject
public OuterController(InnerController inner, FXMLLoader loader){
super(loader);
innerContactPaneOne.getChildren().add(inner.getRootView());
}
}
class InnerController extends PreloadedFX{
#FXML Pane innerContentPaneTwo; //this will be a child of PaneOne in OuterController
#FXML Button otherStuff;
#Inject
public OuterController(FXMLLoader loader){
super(loader);
}
public Node getRootView(){
return innerContentPaneTwo;
}
}
//with somebody calling
OuterController rootController = injector.getInstance(OuterController.class);
you can get the source code for the PreloadedFX and View-By-Convention code here:
https://gist.github.com/Groostav/ff35eb2d19b348f2e25c
which is as elegant as I've been able to make this particular union of frameworks.
Hope that helps!