New to JAVAFX so this maybe a simple fix, but I have controllers in my application setup using FXML files. I reference the controller to use via the FXML file and to load the file i use the following code in my Application class
private void replaceScene(String resource) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
Pane screen = (Pane) loader.load();
Scene scene = new Scene(screen);
scene.getStylesheets().addAll(getClass().getResource("/css/application.css").toExternalForm());
stage.setScene(scene);
stage.sizeToScene();
IControlledScreen controller = (IControlledScreen) loader.getController();
controller.setApp(this);
} catch (Exception e) {
System.out.println("Cannot load resource " + resource);
System.out.println(e.getMessage());
}
}
And here is a basic controller
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// code here
}
});
}
}
I have also got an interface called IControlledScreen to set the reference to the application
public interface IControlledScreen {
// ALlows us a reference to the application
public void setApp(MyApplication app);
}
Now this all works fine, until i try to access the app variable during the initialize event. So changing the above controller to this now breaks, because app = NULL.
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
// HERE app = NULL
app.GetSomeProperty = "";
}
}
How can i get round this?
Well I think you have to change your design.
The initialize method is called during FXMLLoader.load()
So the call stack would be something like
..replaceScene
..loader.load
....MyController.initialize()
..loader.getController
..controller.setApp(app)
If you really have to access the application from inside your controller you would need to make it a singleton.
Related
I'm currently working on a 'small' project with JavaFX. I used the SceneBuilder to create the first sketch of my GUI. it still needs some adjustment and styling but I wanted to see if it's working so far.
I have 2 hyperlinks on the GUI, if the user clicks one of them the default system-browser should open with a specific URL.
So far I got this:
Main.java:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws MalformedURLException, IOException {
DataBean dataBean= new DataBean(primaryStage);
Controller controller = new Controller(dataBean);
controller.show();
}
public static void main(String[] args) {
launch(args);
}
}
DataBean.java:
public class DataBean {
private Stage primaryStage;
public DataBean(Stage stage) {
primaryStage = stage;
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
TestautomatView.java:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
root = FXMLLoader.load(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
scene = new Scene(root);
}
public void show(Stage stage) {
stage.setTitle("CrossReport Testautomat");
stage.setScene(scene);
stage.show();
}
public ComboBox<String> getEnvironmentCombo() {
return environmentCombo;
}
public Hyperlink getCrhl() {
return crhl;
}
public Hyperlink getHelp() {
return help;
}
public Scene getScene() {
return scene;
}
}
In my controller I want to set the ActionHandler to the hyperlinks but it's not working because the getters in my view return null.
public class Controller {
private DataBean dataBean;
private TestautomatView view;
public Controller(DataBean databean) throws MalformedURLException, IOException {
this.dataBean = databean;
this.view = new TestautomatView();
setActionHandlers();
}
public void show() throws MalformedURLException, IOException {
view.show(dataBean.getPrimaryStage());
}
private void setActionHandlers() {
// setHyperlink(view.getCrhl(), "www.example.com");
// setHyperlink(view.getHelp(), "www.example2.com");
}
private void setHyperlink(Hyperlink hl, String uri) {
hl.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//TODO - Open Default Browser
}
});
}
}
When I start my application, I can see the GUI but when I want to add the ActionHandlers I get a NullPointerException.
In the ´Sample.fxml´ file the hyperlinks are children of a HBox
<Hyperlink fx:id="crhl" text="Report" />
<Hyperlink fx:id="help" text="Help" />
But it's not just the hyperlinks even the ComboBox is null when I inspect my app in the debugger.
Where is my mistake?
The problem is that you are creating your controller manually by using new TestautomatView(). It must be created by FXMLLoader for annotations to work. You must also set fx:controller attribute in Sample.fxml to your controller (TestautomatView) fully qualified class name.
Example code:
FXMLLoader fl = new FXMLLoader(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
root = fl.load();
TestautomatView controller = fl.getController();
PS: You should rename your TestautomatView to TestautomatController. FXML file is your "view".
As pointed out in another answer, the issue is that you create an instance of TestautomatView "by hand". The default behavior of the FXMLLoader is to create an instance of the controller class specified in the FXML file, and use that instance as the controller. Consequently, you have two instances of TestautomatView: the one you created (and have a reference to), and the one that was created by the FXMLLoader. It is the second one that has the #FXML-annotated fields initialized.
You can change this default behavior by creating an FXMLLoader instance, and setting the controller on it directly. E.g. consider doing:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
FXMLLoader loader = new FXMLLoader(TestautomatView.class.getResource("Sample.fxml"));
loader.setController(this);
root = loader.load();
scene = new Scene(root);
}
// etc...
}
Since you are directly setting the controller, you need to remove the fx:controller attribute from the Sample.fxml file for this to work.
You may also be interested in this pattern, which is quite similar (though not exactly the same) as what you are trying to do here.
I'm still fighting with my issue. I want to use Spring Framework in order to incject dependencies and I have to use Spring boot to integrate both.
Unfortunately, in first view autowiring is run correctly, but if I go next Stage, I got still only Null Pointer Exception.
Thats main class:
#SpringBootApplication(scanBasePackages = "boxingchallenge")
public class BoxingChallengeApplication extends Application {
public ConfigurableApplicationContext springContext;
private Parent root;
public static Stage stage;
#Override
public void init() throws Exception {
springContext = SpringApplication.run(BoxingChallengeApplication.class);
springContext.getAutowireCapableBeanFactory().autowireBean(this);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FXML/start.fxml"));
fxmlLoader.setControllerFactory(springContext::getBean);
root = fxmlLoader.load();
}
#Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
primaryStage.setTitle("Boxing challenge");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() {
springContext.stop();
}
public static void main(String[] args) {
launch(BoxingChallengeApplication.class, args);
}
}
Here in first controller class autowiring run cool:
#Component
public class Start {
#FXML
public Button loadGame;
#FXML
public Button create;
#Autowired
private Boxer boxer;
public void load(ActionEvent event) {
System.out.println(boxer.getName());
}
//next stage
public void createNew(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/FXML/creator.fxml"));
BoxingChallengeApplication.stage.setScene(new Scene(root));
}
}
Here in second stage, autowiring not working:
#Component
public class Creator {
#FXML
public Button ready;
public TextField nation;
public TextField name;
public Boxer boxer;
/*#Autowired
private ApplicationContext context;*/
#Autowired
public void setBoxer(Boxer boxer) {
this.boxer = boxer;
}
public void createdAndPlay(ActionEvent event) {
if (boxer == null)
System.out.println("boxer is null");
else
System.out.println("Injected correctly");
}
}
Thanks, i hope it's going to finished...
#Jewelsea's comment is correct: you must set the controller factory when you load creator.fxml. If you don't do this, the FXMLLoader will create the controller simply by calling its no-arg constructor, so Spring will know nothing about it and will have no opportunity to inject any dependencies.
To do this, all you need is access to the ApplicationContext in Start, and you can inject "well-known objects", of which the ApplicationContext is an example, into your Spring-managed beans:
#Component
public class Start {
#FXML
public Button loadGame;
#FXML
public Button create;
#Autowired
private Boxer boxer;
#Autowired
private ApplicationContext context ;
public void load(ActionEvent event) {
System.out.println(boxer.getName());
}
//next stage
public void createNew(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/FXML/creator.fxml"));
load.setControllerFactory(context::getBean);
Parent root = loader.load();
BoxingChallengeApplication.stage.setScene(new Scene(root));
}
}
As an aside, you almost certainly want a new instance of any controller when you load an FXML file, so you should probably make any controllers prototype scope.
I am using afterburner.fx http://afterburner.adam-bien.com/
It works as advertised. I can add multiple fxml Files to a central/main "view".
But if I want to add another fxml/presenter later, for example, using a button on a different navigationPane to add another fxml to the mainAnchorPane.
Then it throws a NullPointerException.
public class MainscenePresenter implements Initializable {
#FXML
AnchorPane breadcrumbAnchor;
#FXML
AnchorPane navigationAnchor;
//--------------------------------------------------------
#FXML
private AnchorPane mainAnchorPane; //ADD NEW ATPANE HERE
private AtPresenter atPresenter;
private AtView atView;
//--------------------------------------------------------
#Override
public void initialize(URL url, ResourceBundle rb) {
//add BreadCrumBar WORKS
BreadcrumbbarView breadcrumbbarView = new BreadcrumbbarView();
breadcrumbbarView.getViewAsync(breadcrumbAnchor.getChildren()::add);
//add DFD WORKS
DfdView dfdView = new DfdView();
Parent view2 = dfdView.getView();
this.mainAnchorPane.getChildren().add(view2);
//add Navigation WORKS
NavigationView navigationView = new NavigationView();
Parent view = navigationView.getView();
navigationAnchor.getChildren().add(view);
//add AT
this.atView = new AtView();
this.atPresenter = (AtPresenter) this.atView.getPresenter();
//ADDING AT VIEW LIKE THIS WORKS <=========================
this.showAt();
}
void showAt() {
this.mainAnchorPane.getChildren().add(this.atView.getView()); // <== NLP here if invoked with buttonAt
}
public void buttonAt() {
//ADDING AT VIEW LIKE THIS(Button on different Presenter) DOES NOT WORK => NLP
this.showAt();
}
}
public class NavigationPresenter implements Initializable {
#FXML
Button atNavButton;
#Inject
MainscenePresenter mainscene;
private ResourceBundle resources = null;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.resources = resources;
}
#FXML
void showDfdScene(ActionEvent event) {
mainscene.buttonAt();
}
}
It seems I don't understand some central mechanism of JavaFX! And can't name it, to look it up!
Why does it throw NullPointerException in this case and not durin initialization?
Caused by: java.lang.NullPointerException
at abc.abc.app.mainscene.MainscenePresenter.showAt(MainscenePresenter.java:107)
at abc.abc.app.mainscene.MainscenePresenter.buttonAt(MainscenePresenter.java:112)
at abc.abc.app.navigation.NavigationPresenter.showDfdScene(NavigationPresenter.java:41)
... 58 more
Afterburner.fx is a dependency-injection framework for JavaFX. The main functionality it provides is the ability to inject objects into the controllers/presenters that are created when you load an FXML file (by instantiating a subclass of FXMLView). The basic process that happens when you instantiate a FXMLView is:
A new instance of the corresponding presenter is created
The presenter is inspected to find any #Inject-annotated fields
For each #Inject-annotated field, if an instance of that type exists in the injector's cache, it is set as the value of that field. Otherwise, a new instance of that type is created and placed in the cache, and set as the value of the field.
The main point to note here is that the presenters themselves are treated differently to their dependencies. If you try (as in your code) to inject one presenter in another, an instance of the presenter class will be created specifically for injection purposes: this will not be the same instance that is created when the FXML file is loaded, and consequently it won't have any #FXML-fields injected. This is why you get a null pointer exception: mainAnchorPane is null in the ``MainScenePresenterthat is injected into theNavigationPresenter`.
One presenter having a reference to another is generally a bad idea anyway: it creates unnecessary coupling between the two presenters. Instead, you should inject a model into both presenters that represents the state you want to share between them. In your case you might have something like
public class ViewState {
private final BooleanProperty atShowing = new SimpleBooleanProperty();
public BooleanProperty atShowingProperty() {
return atShowing ;
}
public final boolean isAtShowing() {
return atShowingProperty().get();
}
public final void setAtShowing(boolean atShowing) {
atShowingProperty().set(atShowing);
}
}
Now in your presenters, do
public class MainscenePresenter implements Initializable {
#Inject
private ViewState viewState ;
#FXML
AnchorPane breadcrumbAnchor;
#FXML
AnchorPane navigationAnchor;
//------------------------------------------------------
#FXML
private AnchorPane mainAnchorPane; //ADD NEW ATPANE HERE
private AtPresenter atPresenter;
private AtView atView;
//------------------------------------------------------
#Override
public void initialize(URL url, ResourceBundle rb) {
//add BreadCrumBar WORKS
BreadcrumbbarView breadcrumbbarView = new BreadcrumbbarView();
breadcrumbbarView.getViewAsync(breadcrumbAnchor.getChildren()::add);
//add DFD WORKS
DfdView dfdView = new DfdView();
Parent view2 = dfdView.getView();
this.mainAnchorPane.getChildren().add(view2);
//add Navigation WORKS
NavigationView navigationView = new NavigationView();
Parent view = navigationView.getView();
navigationAnchor.getChildren().add(view);
//add AT
this.atView = new AtView();
this.atPresenter = (AtPresenter) this.atView.getPresenter();
this.viewState.atShowingProperty().addListener((obs, wasShowing, isNowShowing) -> {
if (isNowShowing) {
this.mainAnchorPane.getChildren().remove(this.atView.getView());
} else {
this.mainAnchorPane.getChildren().add(this.atView.getView());
}
});
}
}
and
public class NavigationPresenter implements Initializable {
#FXML
Button atNavButton;
#Inject
private ViewState viewState ;
private ResourceBundle resources = null;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.resources = resources;
}
#FXML
void showDfdScene(ActionEvent event) {
viewState.setAtShowing(true);
}
}
I recently started programming with JavaFx. I have the following Problem:
I am writing a controller class, in which I hava a Table with orders, but the content can only be set in the initialize method.
The List with orders should be set in the orderTable with the method setorderList. The problem is that the Orders are not shown in the TableView.
I have already tried to trigger an update of the TableView by removing the items of the Table and filling them up again.
For test purposes i created the orderList with a test-order in the initialize() method and set them to the table with orderTable.setItems(orderList). When i did this it worked perfectly.
So the order from the initialize method (with number 23) is shown in the Table but the List thats set in setOrderList is not.
This confuses me, because it seems like the orderTable.setItems(orderList) Statement only works in the initialize() method.
Here is my code:
public class OrderOverviewController implements Initializable{
#FXML
private TableView<Order> orderTable;
#FXML
private TableColumn<Order,Integer> orderNumberColumn;
private ObservableList<Order> orderList;
private OrderDao orderDao;
#Override
public void initialize(URL location, ResourceBundle resources) {
orderNumberColumn.setCellValueFactory(new PropertyValueFactory<>("orderId"));
orderList=FXCollections.observableArrayList();
//ordernumber and Current user
orderList.add(new Order(23,null));
orderTable.setItems(orderList);
}
public void setOrderList(ObservableList<Order> orderList) {
orderTable.setItems(orderList);
}
}
This is the start method of the main class where i setup the scenes for my application. First the LoginN.fxml gets loaded, and the mainScene is only set up. After this the LoginManager gets created.
#Override
public void start(Stage primaryStage) throws IOException {
this.stage=primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("/view /LoginN.fxml"));
loginScene=new Scene(root);
primaryStage.setScene(loginScene);
primaryStage.show();
root=null;
root = FXMLLoader.load(getClass().getResource("/view/Main.fxml"));
mainScene=new Scene(root);
new LoginManager(this);
}
So here is my LoginManager which has an static instance of itself, so i can access it easier.
The method setLoggedInUser sets the user after the login procedure as you can see in the next code sample which contains the method login.
Here i get all orders of the user, with which i call then the setOrderMethod of the controller where my Problem with the table is.
public class LoginManager {
private static LoginManager loginManager;
private Main main;
private User loggedInUser;
private OrderDao orderDao;
private MainController mainController;
private OrderOverviewController ooc;
public LoginManager(Main main){
loginManager=this;
this.main=main;
}
public static LoginManager getInstance(){
return loginManager;
}
public void setMainView(){
main.setMainView();
}
public void setLoggedInUser(User loggedInUser) {
this.loggedInUser = loggedInUser;
//load OrderViewController to set Previous orders
try {
URL location=getClass().getResource("/view/Orders.fxml");
FXMLLoader loader=new FXMLLoader();
loader.setLocation(location);
loader.setBuilderFactory(new JavaFXBuilderFactory());
Parent root=(Parent)loader.load(location.openStream());
orderDao=new OrderDao();
ooc=loader.getController();
ooc.setOrderList(orderDao.getOrdersOfUser(loggedInUser));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here is the login method of the LoginController which gets triggered when the loginButton is pressed:
public void login() throws Exception{
userName=usernameField.getText();
password=AESCrypt.encrypt(passwordField.getText());
if(userdao.isUserValid(userName, password))
{
LoginManager.getInstance().setMainView();
LoginManager.getInstance().setLoggedInUser(userdao.getUserByName(userName));
}
else
errorLabel.setText("Login failed!");
}
If the userData was valid the mainScene gets set up with the LoginManager and the logged in user is set.
This is how i included the Orders.fxml (The view of the OrderOverviewController) in a Tab in the Main view:
<fx:define>
<fx:include source="Orders.fxml" fx:id="orderOverviewContent"/>
</fx:define>
<Tab content="$orderOverviewContent" text="Order Overview" />
Thank you for your help.
I just can't figure out how to add data to static ComboBox in JavaFX 2.2. Whatever I try to do ComboBox is empty. Here is the code:
#FXML private MenuItem menuItemNewTile;
#FXML private static ComboBox<Tile> comboBoxTileList;
#FXML
private void menuItemNewTileSetOnAction(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(TILE_WINDOW_URL));
Scene scene = new Scene(root);
Stage tileStage = new Stage();
tileStage.setScene(scene);
tileStage.show();
}
#FXML
private void comboBoxTileListSetOnAction(ActionEvent event) {
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
comboBoxTileList = new ComboBox<>();
}
public static void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
If ComboBox is private, and I add item in initialize method it's working, but with static ComboBox I tried million things and still no progress.
Solution
Don't use static and #FXML together.
Rework your design so that the static keyword is no longer required for the comboBoxTileList and use an instance variable instead.
Additional Issue
An #FXML member such as comboBoxTileList should never be set to a new value, so you should not have comboBoxTileList = new ComboBox<>();
Answer to additional questions
I use another window to create new Tile object and from controller class of that window i call refreshTileList method. How to do that without using static?
There are numerous ways of writing your code so that you don't need a static reference to controller members. Here is a sample based on a solution from: Passing Parameters JavaFX FXML. You will need to modify the example to fit your exact case, it's just presented to demonstrate a possible pattern that you could use.
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
class ComboController {
#FXML private static ComboBox<Tile> comboBoxTileList;
public void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
}
class AnotherController {
#FXML private Button createTile;
#FXML private Button newCombo;
#FXML private StackPane mainPane;
private comboController;
#FXML private void createTile(ActionEvent event) {
if (comboController == null) {
return;
}
comboController.refreshTileList(
new Tile()
);
}
#FXML private void newCombo(ActionEvent event) {
try {
comboController = new ComboController();
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"combo.fxml"
)
);
loader.setController(comboController);
Pane comboPane = (Pane) loader.load();
mainPane.getChildren().setAll(comboPane);
} catch (IOException e) {
// handle exception.
}
}
}