JavaFX - load FXML-file without FXLoader - javafx

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.

Related

Add KeyEvent for calculator numpad in JavaFX using MVC

I created in Eclipse a simple calculator using JavaFx and MVC pattern. I would like to add keylisteners in order to press the buttons of my calculator by simply pressing the buttons in my keyboard. I tried to add #onKeyPress in SceneBuilder and then a method onKeypress (with some coding inside) in my Controller class but nothing happens.Could you please give some general instructions how to implement something like this? Thanks!
Thanks for your comments. I added the following code snippet in App.java:
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
controller.numFromKeyboard(event.getCode().toString());
}
});
And also, I had to add:
Parent root = loader.load();
Controller controller = loader.getController();
// The above line MUST be
// inserted after root is loaded in order the controller of my
// app to be instantiated,
// otherwise we will get a null exception when handler will be
// invoked
App.java
public class App extends Application {
//controller = new Controller();
#Override
public void start(Stage primaryStage) {
try {
// Read file fxml and draw interface.
FXMLLoader loader = new FXMLLoader(getClass()
.getResource("/application/View.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("/application/application.css").toExternalForm());
Image icon = new Image(getClass().getResourceAsStream("/application/Assets/App.png"));
primaryStage.getIcons().add(icon);
primaryStage.setTitle("JavaFX Calculator by Dimitris Baltas");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
controller.numFromKeyboard(event.getCode().toString());
}
});
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX Concurrency: How to updateMessage() to the controller with interface?

Before I learned about running tasks on the background thread I learned how to post messages to my UI via my controller with an Interface. The interface allows me to have a systems messages in any controller. but I cant figure out how to bind it to updateMessage from a task.
My interface:
public interface SystemMessage {
void postMessage(String outText);
}
My controller:
public class MainController implements SystemMessage {
#FXML
public DialogPane systemMessage;
#Override
public void postMessage(String outText) {
systemMessage.setContentText(outText);
}
//***I have tried the following to bind the dialog pane to the updateMessage
systemMessage.contentTextProperty().bind(task.messageProperty());
//***InteliJ tells me that it can not resolve contetTextProperty or task.
}
My Main class with the Task on a new thread.
private SystemMessage mainController;
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
this.mainController = (SystemMessage) loader.getController();
api = new Api();
machineId = new Identity();
db = new SqLite();
//mainController.postMessage(task.messageProperty()).bind();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Task<String> task = new Task<String>() {
#Override protected String call() throws Exception {
checkAsiServer();
updateMessage("API Checked.");
checkMachineId();
updateMessage("Machine Id Checked.");
checkDb();
updateMessage("Server Checked.");
return "done";
}
};
I have tried to bind my DialogPane in the cotroller by:
systemMessage.contentTextProperty().bind(task.messageProperty());
But it can not resolve contentTextProperty and I don't know how to tell the controller about the task object either.
Not sure I understand the problem. Why don't you just expose the property from the controller? You can do
public class MainController implements SystemMessage {
#FXML
private DialogPane systemMessage;
public StringProperty messageProperty() {
return systemMessage.contentTextProperty();
}
// ...
}
and then
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
MainController mainController = loader.getController();
Task<String> task = new Task<String>() { /* existing code... */};
mainController.messageProperty().bind(task.messageProperty());
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
If you want to keep the type of the controller as SystemMessage and only rely on the postMessage method, then obviously you can't do this with a binding (because you don't have any way to access the property); you would need to use a listener instead:
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
SystemMessage mainController = loader.getController();
Task<String> task = new Task<String>() { /* existing code... */};
task.messageProperty().addListener((obs, oldMessage, newMessage) ->
mainController.postMessage(newMessage));
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();

JavaFx handler override

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);
}
}

Getting a variable from a FXML to an imported FXML

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).

JavaFX load FXML inside parent controller

I have a borderpane where I'm loading an fxml (alias FirstPanel) with relative controller inside of it and positioned in the center. The fxml contains a TableView and a button which should load another fxml (alias SecondPanel) and relative controller instead the first panel. Basically, the SecondPanel needs to show some details about the data selected in the table.
Is it possible to do it? How can I get the parent of my FirstPanel and use it for the SecondPanel instead of the first?
UPDATE
I've tried many solutions but without reach my goal. The application load the UsersMainPageController which contains only an AnchorPane like parent control, so this is the relative code:
[UsersMainPageController]
public class UsersMainPageController implements Initializable {
private PostOffice application;
#FXML
private AnchorPane ParentControl;
public void setApp(PostOffice application){
this.application = application;
}
public void loadPage(String pageName) {
try {
URL url = getClass().getResource(pageName);
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(url);
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
AnchorPane page = (AnchorPane) fxmlLoader.load(url.openStream());
ParentControl.getChildren().clear();///name of pane where you want to put the fxml.
ParentControl.getChildren().add(page);
}
catch (IOException e) {
e.printStackTrace();
}
}
public void loadManageUsers () {
loadPage("UsersManage.fxml");
}
public void loadListUsers () {
loadPage("UsersList.fxml");
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
loadListUsers();
}
}
loadListUsers calls UsersList.fxml with the relative controller UsersListController that contains a TableView with some records and some buttons. When I click a specific button, it should call loadManageUsers with relative controller UsersManageController which contains some fields for editing data and inserting new users. When users are edited or inserted, it should be able to return to the previous page with the TableView and clear the current page (in this case UsersManageController).
[UsersListController]
public class UsersListController implements Initializable {
private UsersMainPageController mainController;
#FXML
private void handleButtonEditAction(ActionEvent event) throws IOException {
mainController.loadManageUsers();
}
}
[UsersManageController]
public class UsersManageController implements Initializable {
private UsersMainPageController mainController;
#FXML
private void handleButtonBackAction(ActionEvent event) throws IOException {
mainController.loadListUsers();
}
}
When I click from the UsersListController the ButtonEdit to load the UsersManageController, I get this error:
Caused by: java.lang.NullPointerException
at postoffice.multiuser.UsersListController.handleButtonAggiornaAction(UsersListController.java:210)
... 50 more
you can add your child fxml into parent controller..
1.first just take a anchor pane and set its bounds where you want to put your FXML code.
now try this...
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
openNewWindow("Example.fxml");
}
});
you can pass fxml name into function...
public void openNewWindow(String FXMLFile)
{
//ChildNode child;
try {
URL url = getClass().getResource(FXMLFile);
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(url);
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
AnchorPane page = (AnchorPane) fxmlLoader.load(url.openStream());
anchor.getChildren().clear();///name of pane where you want to put the fxml.
anchor.getChildren().add(page);
}
catch (IOException e) {
e.printStackTrace();
}
}

Resources