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();
}
}
Related
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 unable to access from another class, I have created two pane with different controllers in one FXML File, second pane has to move around the first pane, on Click Action, and I have loaded an FXML file in second pane, also successfully moved the pane one time, but the second time I want to move it from second controller, but it is giving me an NullPointerException.
this is my main controller where I have moved the pane:
public class MainController implements Initializable {
#FXML
private Pane searchPane;
#FXML
private Pane secondPane;
private TranslateTransition nextTransition;
public Pane getSecondPane() {
return secondPane; // Accessing Pane with this getter method
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void nextBtnAction(ActionEvent event) {
try {
Parent pessFxml = FXMLLoader.load(getClass().getResource("firstPane.fxml"));
secondPane.getChildren().add(pessFxml);
nextTransition = new TranslateTransition(Duration.millis(300), secondPane);
nextTransition.setToX(searchPane.getLayoutX()-secondPane.getLayoutX());
nextTransition.play();
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, "Form does not found" ,ex.toString(),0);
}
}
}
this is SecondController where I am accessing the pane to move it back smoothly, but its throwing NullPointerException:
please tell me how to solve this
public class SecondController implements Initializable {
MainController mainControll;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void backBtnPessAction(ActionEvent event) {
//here i am putting the second pane to move back
TranslateTransition back = new TranslateTransition(Duration.millis(300), mainControll.getSecondPane());
back.setToX(mainControll.getSecondPane().getLayoutX()-750);
back.play();
}
}
MainController is never initialized in SecondController. You should pass it when creating second controller in nextBtnAction.
MainController code:
private void nextBtnAction(ActionEvent event) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("firstPane.fxml"));
Parent pessFxml = loader.load();
SecondController controller = (SecondController)loader.getController();
controller.setMainController(this);
secondPane.getChildren().add(pessFxml);
nextTransition = new TranslateTransition(Duration.millis(300), secondPane);
nextTransition.setToX(searchPane.getLayoutX()-secondPane.getLayoutX());
nextTransition.play();
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, "Form does not found" ,ex.toString(),0);
}
}
SecondController:
public void setMainController(MainController controller) {
this.mainControll = controller;
}
I have two FXML documents each one represents a view, let's call them View1 and View2, and I have placed each one in a separate Tab, (Tab1 and Tab2) inside a TabPane.
Now in the Controller1 of View1 I have an event that will switch the selectedItem of my TabPane from Tab1 to Tab2. My question is how can I access my TabPane from Controller1
In general. How do we retrieve a certain Node in Javafx.
Edit
View1
<VBox fx:controller="controllers.Controller1">
<Button onAction="#openView2"/>
</VBox>
Controller1
public class Controller1{
public void openView2(){
//What should I do here
}
}
MainView
<TabPane fx:id="tabPane" fx:controller="controllers.MainController"/>
MainController
public class MainController implements Initializable {
#FXML
public TabPane tabPane;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
try {
tabPane.getTabs().add(createView1Tab());
tabPane.getTabs().add(createView2Tab());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected Tab createView1Tab() throws IOException {
Tab tab = new Tab();
tab.setContent(FXMLLoader.load(getClass().getResource("/views/View1.fxml")));
return tab;
}
protected Tab createView2Tab() throws IOException {
Tab tab = new Tab();
tab.setContent(FXMLLoader.load(getClass().getResource("/views/View2.fxml")));
return tab;
}
}
You should create a "view model" which encapsulates the current state of the view, and share it with each of the controllers. Then observe it from your main controller and respond accordingly.
For example:
public class ApplicationState {
private final StringProperty currentViewName = new SimpleStringProperty();
public StringProperty currentViewNameProperty() {
return currentViewName ;
}
public final String getCurrentViewName() {
return currentViewNameProperty().get();
}
public final void setCurrentViewName(String viewName) {
currentViewNameProperty().set(viewName);
}
}
Now you can do (note I also removed your redundant repetitive code here):
public class MainController implements Initializable {
#FXML
public TabPane tabPane;
private final ApplicationState appState = new ApplicationState();
private final Map<String, Tab> views = new HashMap<>();
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
try {
tabPane.getTabs().add(createViewTab("View1", new Controller1(appState)));
tabPane.getTabs().add(createViewTab("View2", new Controller2(appState)));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
appState.currentViewNameProperty().addListener((obs, oldView, newView) ->
tabPane.getSelectionModel().select(views.get(newView)));
appState.setCurrentViewName("View1");
}
protected Tab createViewTab(String viewName, Object controller) throws IOException {
Tab tab = new Tab();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/"+viewName+".fxml"));
loader.setController(controller);
tab.setContent(loader.load());
views.put(viewName, tab);
return tab;
}
}
Now your controllers just have to do:
public class Controller1{
private final ApplicationState appState ;
public Controller1(ApplicationState appState) {
this.appState = appState ;
}
public void openView2(){
appState.setCurrentViewName("View2");
}
}
Note that since the controllers don't have no-arg constructors, I am setting them in code with loader.setController(...). This means you have to remove the fx:controller attribute from the fxml files for view1 and view2, e.g. your View1.fxml becomes:
<VBox xmlns="..."> <!-- no fx:controller here -->
<Button onAction="#openView2"/>
</VBox>
The advantage of this design is that when your boss walks into your office in 8 months and says "The customer doesn't like the tab pane, replace it with something that only shows one screen at a time", it's very easy to make changes like that as everything is properly decoupled. (You would only have to change the main view and its controller, none of the other views or controllers would change at all.) If you exposed the tab pane to all the other controllers, you would have to find all the places you had accessed it to make changes like that.
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).
I want to change the text of a Label with the controller from another class. I have made a method in the FXMLDocumentController, which sets the text to the label:
public void setLabelText(String text)
{
lbZeit.setText(text);
}
Now I want to change this text from another class like my SerialHandlerClass. First, I need the controller, am I right? So I did this:
FXMLLoader loader = new FXMLLoader(FXMLDocumentController.class.getResource("FXMLDocument.fxml"));
loader.load();
controller = (FXMLDocumentController) loader.getController();
Now I run the "setLabelText" method....
controller.setLabelText("asd");
... and nothing happens...
It's very funny, because when I add System.out.println(text); to the "setLabelText(String text)" method, the program writes the correct text to the console.
But, why?
Sorry for my bad english, it's not my native language :)
Thanks,
Julian
You are not updating the label because you are creating another instance of FXMLDocumentController when you use the FXMLoader.
You should set the controller instance, that contains the label, as a parameter to the other Class.
Below you have the code that could solve your need. Here I set the Controller instance to the Connector class, so you can call the setLabelText method from the other class:
public class Connector {
public static void Connecting(FXMLDocumentController controller) {
try {
System.out.println("Connector.Connecting(): Called");
controller.setLabelText("Bye World");
} catch (IOException ex) {
Logger.getLogger(Connector.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Connector.Connecting(this);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setLabelText(String text)
{
System.out.println("FXMLDocumentController.setLabelText(): Called");
label.setText(text);
}
}
Note:
If your routine is going to take longer to execute whatever it needs to, you might want to use a Task, so you don't freeze your UI. To update the Label, you have to bind the text property and then update the Text value using the updateMessage() method.
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Task<Boolean> connectorTask = new ConnectorTask();
label.textProperty().bind(connectorTask.messageProperty());
connectorTask.setOnSucceeded(e -> {
// this is going to be called if the task ends up without error
label.textProperty().unbind();
});
new Thread(connectorTask).start();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
//public void setLabelText(String text)
//{
// System.out.println("FXMLDocumentController.setLabelText(): Called");
// label.setText(text);
//}
public class ConnectorTask extends Task<Boolean> {
#Override
protected Boolean call() throws Exception {
// ... do whatever you need here
// then you call this method to update the TextProperty from the Label that was bound.
updateMessage("Bye World");
return Boolean.TRUE;
}
}
}
NOTE:
There is a possible duplicate question for this, please see my answer for this question here!