I have a fxml file build from fxml builder and I am using it by a loader in Java.
URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");
FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
Pane rootPane = (Pane) loader.load();
this fxml file maps click event to my class;
<Group id="Group" layoutX="0.0" layoutY="0.0" onMouseReleased="#handleThis" scaleX="1.0" scaleY="1.0">
...
<Group/>
so I implement my handler in my class, lets call it MyClass;
public class MyClass {
public void createScene() throws IOException
{
URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");
FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
Pane rootPane = (Pane) loader.load();
...
}
#FXML
public void handleThis(ActionEvent event) {
System.out.println("from MyClass");
}
...
}
Now I extend MyClass as MyExtendedClass and override handleThis method;
public class MyExtendedClass extends MyClass {
#Override
public void handleThis(ActionEvent event) {
System.out.println("from MyExtendedClass");
}
}
My question is, I cannot manage to work handle method in my extended class. It does not overrides it. How can I achieve to make it print "from MyExtendedClass" instead of "from MyClass"?
When createScene() is called on an instance of MyExtendedClass, the FXMLLoader parses the FXML file, reads the fx:controller="MyClass" attribute and instantiates a new object of type MyClass. That is why the base method is always called. The FXMLLoader doesn't know about MyExtendedClass.
There is a - hackish - way to achieve what you want (i.e. doing the loading in MyClass and still defining the controller in FXML):
public class MyClass
{
public void createScene()
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
// set a controller factory that returns this instance as controller
// (works in this case, but not recommended)
loader.setControllerFactory(controllerType -> this);
pane = (Pane) loader.load();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
It would be cleaner to instantiate the controller and pass it to the FXMLLoader.
For this the fx:controller="" attribute must be removed from the FXML file.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
MyClass controller = new MyExtendedClass();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
loader.setController(controller);
Pane pane = (Pane) loader.load();
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Or use fx:controller="MyClass" to define the base type in the FXML file and let a controller factory decide the actual implementation.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
loader.setControllerFactory(controllerType -> {
if (MyClass.class.equals(controllerType))
return new MyExtendedClass();
else
return null; // return some other controller
});
Pane pane = (Pane) loader.load();
MyClass controller = (MyClass) loader.getController();
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Related
So I'm trying to load and save Images into an imageView where the location of the image is chosen through a file browser. I've been working on this for several days now and I'm gonna have a stroke if I can't get it fixed. I've tried everything I can think of. Thank you in advance for helping.
UPDATED:
Here is my main class:
public class Main extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
public Main(){}
#Override
public void start(Stage primaryStage) throws Exception{
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Help Please");
initRootLayout();
showScreen();
}
public void initRootLayout(){
try{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
RootLayout controller = loader.getController();
controller.setMain(this);
primaryStage.show();
}catch(Exception e ){e.printStackTrace();}
}
public void showScreen(){
try{FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/sample.fxml"));
BorderPane sample = (BorderPane)loader.load();
rootLayout.setCenter(sample);
Controller controller = loader.getController();
controller.setMain(this);
}catch (Exception e){e.printStackTrace();}
}
public Stage getPrimaryStage(){return primaryStage;}
public static void main(String[] args) {
launch(args);
}
}
Here is the rootLayout:
public class RootLayout {
private Main main;
private Controller controller = new Controller();
public void setMain(Main main){this.main = main;}
#FXML
private void handleOpen(){
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"PNG files (*.png)","*png");
fileChooser.getExtensionFilters().add(extensionFilter);
File file = fileChooser.showOpenDialog(main.getPrimaryStage());
if(file!= null){
controller.updateImage(file.toURI().toString());
}
}
}
And here is the controller:
public class Controller implements Initializable {
#FXML
ImageView imageView = new ImageView();
String imageURL;
Main main = new Main();
public void setMain(Main main){
this.main = main;
}
public void updateImage(String url){
if(url.length()>=1){
Image image = new Image(url);
imageView.setImage(image);
System.out.println(url);
}
else{
System.out.println(url);
System.out.println("image invalid");
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
Two things:
Never assign a field whose value is to be injected by an FXMLLoader (e.g. #FXML fields). Doing so is a waste of resources at best and introduces subtle bugs at worst. For instance, if you were to leave the imageView field uninitialized you'd be getting a NullPointerException which would indicate a problem with your setup. Since you do initialize the field, however, you don't get any errors and there's a false impression of the code working.
In your RootLayout controller class, you have:
private Controller controller = new Controller();
That instance of Controller you just created is not linked to any FXML file. And since you initialize the imageView field (see first point) you end up updating an ImageView which is not being displayed anywhere; this is where not initializing said field would have given a nice indication of there being a problem. The solution is to pass the Controller instance created by the FXMLLoader to the RootLayout instance created by the other FXMLLoader.
Also, in the same class you have:
Main main = new Main();
Which is also unnecessary since the created instance of Main is both not the correct instance and is replaced by the call to #setMain(Main) almost immediately.
Assuming your FXML files (which you did not provide) are correct, the Java classes should look more like:
Main.java
public class Main extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private RootLayout rootLayoutController;
public Main() {}
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Help Please");
initRootLayout();
showScreen();
}
public void initRootLayout() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
// store RootLayout instance in field so #showScreen()
// can reference it
rootLayoutController = loader.getController();
rootLayoutController.setMain(this);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public void showScreen() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/sample.fxml"));
BorderPane sample = (BorderPane) loader.load();
rootLayout.setCenter(sample);
Controller controller = loader.getController();
controller.setMain(this);
// set Controller instance on RootLayout instance
rootLayoutController.setController(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
RootLayout.java
public class RootLayout {
private Main main;
private Controller controller;
public void setMain(Main main) {
this.main = main;
}
public void setController(Controller controller) {
this.controller = controller;
}
#FXML
private void handleOpen() {
FileChooser fileChooser = new FileChooser();
// Note extensions should be prefixed with "*."
FileChooser.ExtensionFilter extensionFilter =
new FileChooser.ExtensionFilter("PNG files (*.png)", "*.png");
fileChooser.getExtensionFilters().add(extensionFilter);
File file = fileChooser.showOpenDialog(main.getPrimaryStage());
if (file != null) {
controller.updateImage(file.toURI().toString());
}
}
}
Controller.java
public class Controller implements Initializable {
#FXML ImageView imageView; // leave uninitialized, will be injected
String imageURL;
Main main;
public void setMain(Main main) {
this.main = main;
}
public void updateImage(String url) {
if (url.length() >= 1) {
Image image = new Image(url);
imageView.setImage(image);
System.out.println(url);
} else {
System.out.println(url);
System.out.println("image invalid");
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {}
}
Note: Did not test new code.
I am playing around with SceneBuilder and come across a few questions about the intialize() method and how to change ComboBox items after it's already been initialized in said method. So basically, after I set the items in initialize, I am not able to change them anymore from another method in the controller.
Here is my code:
public class AppController implements Initializable {
private ObservableList<String> list = FXCollections.observableArrayList();
private MainModel model;
#FXML
private ComboBox<String> cobUsers = new ComboBox<String>();
#Override
public void initialize(URL url, ResourceBundle rb) {
list.add("name1");
list.add("name2");
cobUsers.setItems(list); // this works!
}
public void initModel(MainModel model) {
this.model = model;
}
public void addItems(){
list.add("name3");
list.add("name4");
cobUsers.setItems(list); // this does not work. ComboBox items remain "name1" and "name2"
}
}
public class App extends Application {
private Stage primaryStage;
private AnchorPane rootLayout;
private AppController appController = new AppController();
MainModel model = new MainModel();
#Override
public void start(Stage primaryStage) {
appController.initModel(model);
this.primaryStage = primaryStage;
this.primaryStage.setTitle("App");
initRootLayout();
appController.addItems();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXMLDocument.fxml"));
rootLayout = (AnchorPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
So guess my question is, how can I access/change my ComboBox later on, after it's been initialized in intialize()?
Thanks! :)
UPDATE 1:
I have changed the initRootLayout() in the App class (see below) and it WORKS now. list now contains 4 items and all of them show up in the ComboBox after calling addItems(). Thanks everyone!
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader(); loader.setLocation(getClass().getResource("FXMLDocument.fxml"));
rootLayout = (AnchorPane) loader.load();
AppController controller = loader.<AppController>getController();
controller.addItems();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
I am experementing with the "new" JavaFX and it worked very well.
Now I am at a point which is incomprehensible for me. I have a Controller for my View and I want to load my Controller from a main-method so the controller can load a view or do whatever it likes.
My problem ist, that I have to load my FXML-File with the FXMLLoader.load() method. The FXMLLoader himselfe loads the controller. So in fact, with my method I will load the controller two times: I load the controller with XController xcontroller = new XController(); and inside that controller I load te view with the FXMLLoader.load() which will load the controller again.
do I have to use FXMLLoader or can I let my controller load the view with an other method?
edit I want to use the Presentation-Abstraction-Control (PAC) Pattern (variation of MVC), that's why I think it's importand to let the controller load the View.
the main class
public class Main extends Application
{
Override
public void start(Stage primaryStage)
{
LoginController loginController = null;
try
{
loginController = new LoginController();
loginController.loadSceneInto(primaryStage);
primaryStage.show();
}
.......
public static void main(String[] args)
{
launch(args);
}
}
the controller
public class LoginController
{
.....
public void loadSceneInto(Stage stage) throws IOException
{
this.stage = stage;
Scene scene = null;
Pane root = null;
try
{
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
scene = new Scene(root, initialWidth, initialHeight);
this.stage.setTitle(this.stageTitle);
this.stage.setScene(scene);
this.centralizeStage();
}
.....
}
}
If I understand correctly, instead of
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
Just do
FXMLLoader loader = new FXMLLoader(
getClass().getResource(this.initialView.getPath());
);
loader.setController(this);
root = loader.load();
You will need to remove the fx:controller attribute from the FXML file for this to work.
I want to create a reference for the FXML Controller by this line in Class Main
Controller controller = (Controller) loader.getController();
to have access to a Controller methode from the Class GrblListener.
Main.controller.updateCurrentPosition(input);
But I always get the error
Exception in thread "EventThread COM5" java.lang.NullPointerException
What's wrong?
Class Main:
public class Main extends Application {
public static Stage stage;
public static Controller controller;
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Scene.fxml"));
Parent root = (Parent) loader.load();
Controller controller = (Controller) loader.getController();
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("css/default.css").toExternalForm());
stage.setTitle("...");
stage.setScene(scene);
stage.show();
this.stage = stage;
}
public static void main(String[] args) {
launch(args);
}
}
Class GrblListener:
class GrblListener implements SerialPortEventListener {
#Override
public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR() && event.getEventValue() > 0){
try {
String input = GrblSender.serialPort.readString();
System.out.println(input.trim());
Main.controller.updateCurrentPosition(input);
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}
}
You are declaring a local variable in start(), and initializing it:
Controller controller = (Controller) loader.getController();
instead of initializing the static variable you declared:
public static Controller controller ;
public void start(Stage stage) {
controller = (Controller) loader.getController();
// ...
}
I'm currently doing a Java (+ using MySQL) application for my studies : an Database for an Hopital
I code my interface using JavaFX.
I have a Main FXML(for the general view) where I have tabs and in each tab I import another FXML using (fx:include). So that each module of my application has his own Controller and own designed View.
How can pass a variable from the main Controller to the others controllers?
Thanks!
Edit : Let me show you my code
So first there it's the class in which I load my fxml (I have on window of Connexion first and if the informations required for the connexion are ok I load the fxml Main with the main interface) And I set the connexion (THE VARIABLE I NEED TO SEND) that I got from my fxml Connexion to the FXML Main
public class MainApp extends Application {
private Stage primaryStage;
private Connection conn;
MainController controllermain = new MainController();
//ConnexionController controllerconnex;
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("BASE DE L'HOPITAL DU ZOB");
showConnexion();
}
public void showConnexion() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("Connexion.fxml"));
Parent page = (AnchorPane) loader.load();
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.show();
ConnexionController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
public void showMainApp(Connection conn) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("Main.fxml"));
AnchorPane page = (AnchorPane) loader.load();
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.show();
this.conn = conn;
controllermain = loader.getController();
controllermain.setMainApp(this);
controllermain.setConnexion(conn); // I want to send the variable conn to the others
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Then this is my Main Controller and you can see that I get the variable connexion only with the set method and then I can send it the the other controller
public class MainController implements Initializable {
private MainApp mainApp;
private Button retour;
protected Connection conn;
FXML AchorPane ;
public MainController() {
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
public void setConnexion(Connection conn){
this.conn=conn;
}
public void handleRetour(){
mainApp.showConnexion();
}
}
}
You just need a reference to the controller corresponding to the included fxml in the controller corresponding to the "main" fxml. You can do this using the Nested Controllers mechanism.
Briefly, if you have a "main" fxml with a <fx:include> tag, add an fx:id to the <fx:include>:
Main.fxml:
<!-- imports etc -->
<!-- root element, e.g. BorderPane -->
<BorderPane fx:controller="com.example.MainController" xmlns="..." ... >
<!-- ... -->
<fx:include source="tab.fxml" fx:id="tab" />
<!-- ... -->
</BorderPane>
Then in the MainController you can inject the controller from the included fxml using #FXML. The rule is that you append the word "Controller" to the fx:id used in the fx:include. For example, if the controller class for tab.fxml is TabController, given the fx:id is tab, you would do:
public class MainController {
#FXML
private TabController tabController ;
private Connection conn ;
// other injected fields, etc...
public void setConnexion(Connection conn) {
this.conn = conn ;
// pass Connection to TabController:
tabController.setConnexion(conn);
}
}
Now just define a setConnexion(...) method in TabController (if you haven't already) to receive the Connection object (and update anything it needs to update as a result).