Updating TextField - JavaFx - javafx

I have 1 "ViewElements"-Class, 1 Controller and 1 FXML-file.
The ViewElements-Class contains the elements of the FXML like Buttons and textfields.
The Controller-Class contains the Business logic.
I try to update the TextField "textfieldDateiAuswaehlen", I want to set the path of the File into the TextField but my method does not work.
ViewElements:
public class ViewElements {
#FXML private TextField textfieldDateiAuswaehlen;
#FXML private TextArea textareaXmlContent;
#FXML private Button buttonXmlBearbeiten;
#FXML private Button buttonXmlLaden;
#FXML private Button buttonXmlOeffnen;
public ViewElements() {
this.textfieldDateiAuswaehlen= new TextField();
this.textareaXmlContent = new TextArea();
this.buttonXmlBearbeiten = new Button();
this.buttonXmlLaden = new Button();
this.buttonXmlOeffnen = new Button();
}
public TextField getTextfieldDateiAuswaehlen() {
return textfieldDateiAuswaehlen;
}
public void setTextfieldDateiAuswaehlenText(String text) {
this.textfieldDateiAuswaehlen.setText(text);
}
public String getTextfieldDateiAuswaehlenContent() {
return this.textfieldDateiAuswaehlen.getText();
}
public TextArea getTextareaXmlContent() {
return textareaXmlContent;
}
public void setTextareaXmlText(String text) {
this.textareaXmlContent.setText(text);
}
public Button getButtonXmlBearbeiten() {
return buttonXmlBearbeiten;
}
public Button getButtonXmlLaden() {
return buttonXmlLaden;
}
public Button getButtonXmlOeffnen() {
return buttonXmlOeffnen;
}}
Controller:
public class SampleController implements Initializable{
ViewElements viewElems= new ViewElements();
#FXML
private void handleButtonLaden(ActionEvent event){
System.out.println("Klicked");
}
#FXML
private void handleButtonXmlOeffnen(ActionEvent event){
FileChooser filechooser = new FileChooser();
File file = filechooser.showOpenDialog(null);
//Falls eine Datei ausgewaehlt ist
if(file != null){
//Falls TextField leer ist
if(viewElems.getTextfieldDateiAuswaehlenContent().isEmpty()) {
System.out.println(file.getAbsolutePath().toString());
viewElems.getTextfieldDateiAuswaehlen().clear();
String verzeichnis = file.getAbsolutePath().toString();
viewElems.setTextfieldDateiAuswaehlenText(verzeichnis);
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Platform.runLater(() -> viewElems.setTextfieldDateiAuswaehlenText(verzeichnis));
return null;
}
};
}
};
service.start();
System.out.println("PRINT: " + viewElems.getTextfieldDateiAuswaehlenContent());
}
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}}
In the screenshot you see that the path is passed to TextField but the TextField in the UI does not update.
Where is my mistake?

When you load the FXML file the FXMLLoader creates the UI nodes corresponding to the elements in the FXML.
If you declare a controller, give the elements fx:id attributes, and declare #FXML-annotated fields in the controller, the FXMLLoader will set those fields in the controller to the UI nodes created from the FXML.
In your code, your controller contains no #FXML-annotated fields. You create an instance of your ViewElements class, which creates some new instances of TextField and Button:
public ViewElements() {
this.textfieldDateiAuswaehlen= new TextField();
this.textareaXmlContent = new TextArea();
this.buttonXmlBearbeiten = new Button();
this.buttonXmlLaden = new Button();
this.buttonXmlOeffnen = new Button();
}
Obviously these are not the same text fields and buttons created by the FXMLLoader.
Presumably, somewhere, you load the FXML and display the UI created by the FXMLLoader; but you don't display the UI nodes created in your ViewElements instance. So when you modify the nodes in your ViewElements instance, you are not modifying the UI you have displayed, and consequently you don't see anything.
You need to place the UI elements directly in the controller (which is perhaps better thought of as a presenter). The only way the FXMLLoader can assign the objects it creates to fields is if those fields are in the controller, because that is the only other object the controller "knows about".
If you want to separate the logic into a different class from the class that contains the UI elements, then make the "controller" the class that has the UI elements, and create a different class containing the implementation of the logic. Then in the "controller" class, just delegate the user event handling to your new class.
I.e. change the fx:controller attribute to point to ViewElements, and refactor as
public class ViewElements {
#FXML private TextField textfieldDateiAuswaehlen;
#FXML private TextArea textareaXmlContent;
#FXML private Button buttonXmlBearbeiten;
#FXML private Button buttonXmlLaden;
#FXML private Button buttonXmlOeffnen;
private SampleController controller ;
public void initialize() {
controller = new SampleController(this);
}
#FXML
private void handleButtonXmlOeffnen(ActionEvent event){
controller.handleButtonXmlOeffnen();
}
public TextField getTextfieldDateiAuswaehlen() {
return textfieldDateiAuswaehlen;
}
public void setTextfieldDateiAuswaehlenText(String text) {
this.textfieldDateiAuswaehlen.setText(text);
}
public String getTextfieldDateiAuswaehlenContent() {
return this.textfieldDateiAuswaehlen.getText();
}
public TextArea getTextareaXmlContent() {
return textareaXmlContent;
}
public void setTextareaXmlText(String text) {
this.textareaXmlContent.setText(text);
}
public Button getButtonXmlBearbeiten() {
return buttonXmlBearbeiten;
}
public Button getButtonXmlLaden() {
return buttonXmlLaden;
}
public Button getButtonXmlOeffnen() {
return buttonXmlOeffnen;
}
}
public class SampleController {
private final ViewElements viewElems ;
public SampleController(ViewElements viewElems) {
this.viewElems = viewElems ;
}
public void handleButtonXmlOeffnen() {
FileChooser filechooser = new FileChooser();
File file = filechooser.showOpenDialog(null);
//Falls eine Datei ausgewaehlt ist
if(file != null){
//Falls TextField leer ist
if(viewElems.getTextfieldDateiAuswaehlenContent().isEmpty()) {
System.out.println(file.getAbsolutePath().toString());
viewElems.getTextfieldDateiAuswaehlen().clear();
String verzeichnis = file.getAbsolutePath().toString();
viewElems.setTextfieldDateiAuswaehlenText(verzeichnis);
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Platform.runLater(() -> viewElems.setTextfieldDateiAuswaehlenText(verzeichnis));
return null;
}
};
}
};
service.start();
System.out.println("PRINT: " + viewElems.getTextfieldDateiAuswaehlenContent());
}
}
}
}

Related

JavaFX static ObservableList not refreshing ComboBox

What I'm trying to do is have a single class that maintains a static ObservableList of countries. I want to display these countries in a ComboBox. I've got this part working fine. Now, I also want to enable the user to add new countries to the list. So, there is a button beside the combo box that will show another dialog allowing entry of another country name. After the user enters the country name and clicks save, I would like the single static ObservableList to be updated with the new country and then it show up in the ComboBox. This part is not happening.
I'll show what DOES work, and what does not.
Saving a reference to the static list and updating that works. Like so:
public class CustomerController implements Initializable {
private ObservableList<Country> countryList;
#Override
public void initialize(URL url, ResourceBundle rb) {
countryList = Country.getCountryList();
comboCountry.setItems(countryList);
}
...
// Fired when clicking the "new country" button
#FXML
void handleNewCountry(ActionEvent event) {
Country country = new Country();
country.setCountry("Austria");
countryList.add(country);
}
}
This is what I would like to do, however it does not work:
public class CustomerController implements Initializable {
#FXML
private ComboBox<Country> comboCountry;
#Override
public void initialize(URL url, ResourceBundle rb) {
comboCountry.setItems(Country.getCountryList());
}
#FXML
void handleNewCountry(ActionEvent event) {
showScene("Country.fxml", "dialog.newCountry");
}
private void showScene(String sceneResource, String titleResource) {
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(sceneResource),
resourceBundle
);
Scene scene = new Scene(loader.load());
getNewStage(resourceBundle.getString(titleResource), scene).showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
private Stage getNewStage(String title, Scene scene) {
Stage stage = new Stage();
stage.setTitle(title);
stage.setResizable(false);
stage.setScene(scene);
stage.initOwner(rootPane.getScene().getWindow());
stage.initModality(Modality.APPLICATION_MODAL);
return stage;
}
}
The Country class:
public class Country extends BaseModel {
private int countryID;
private StringProperty country;
private static ObservableList<Country> countryList; // The static observable list
public Country() {
countryList = FXCollections.observableArrayList();
country = new SimpleStringProperty();
}
public int getCountryID() {
return countryID;
}
public void setCountryID(int countryID) {
this.countryID = countryID;
}
public StringProperty countryProperty() {
return this.country;
}
public String getCountry() {
return this.country.get();
}
public void setCountry(String country) {
this.country.set(country);
}
public boolean equals(Country country) {
if (this.getCountry().compareToIgnoreCase(country.getCountry()) != 0) {
return false;
}
return true;
}
public static ObservableList<Country> getCountryList() {
if (countryList.size() < 1) {
updateCountryList();
}
return countryList;
}
public static void updateCountryList() {
countryList.clear();
ArrayList<Country> daoList = CountryDao.listCountries();
for (Country country : daoList) {
countryList.add(country);
}
}
#Override
public String toString() {
return this.getCountry();
}
}
And the dialog for entering a new country:
public class CountryController implements Initializable {
#FXML
private TextField textCountry;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
void handleSave(ActionEvent event) {
Country country = new Country();
country.setCountry(textCountry.getText().trim());
CountryDao.insert(country); // Insert the country into the database
Country.updateCountryList(); // Update the static ObservableList
close();
}
#FXML
void handleCancel() {
close();
}
void close() {
final Stage stage = (Stage) textCountry.getScene().getWindow();
stage.close();
}
}
So, my theory is that somehow the ComboBox is creating a new instance of the ObservableList when setItems is called. I'm really not sure though. A static object should only have one instance, so updating it from anywhere should update that ComboBox. Anyone know what's up with this?
You're creating a new ObservableList instance every time the Country constructor is invoked. This way a list different to the one used with the ComboBox is modified.
If you really need to keep the list of countries in a static field (this is considered bad practice), you should make sure to only create a single ObservableList:
private static final ObservableList<Country> countryList = FXCollections.observableArrayList();
(Remove the assignment of this field from the constructor too.)

JavaFX run a method once a specified key is pressed

I am trying to run a method in a controller class specified to a particular task, once a specified key is pressed using KeyListener. But i'm unable to detect the keypress and invoke the java.awt.event keyPressed method. My code is as follows :
public class POSController implements KeyListener {
#Override
public void keyPressed(java.awt.event.KeyEvent e) {
if (e.getKeyCode() == com.sun.glass.events.KeyEvent.VK_F1) {
try {
paymentAction();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
What could have gone wrong? Thanks in advance.
Here is the minimal executable example of the problem.
public class POSController implements KeyListener {
#FXML
private TableView<Product> productTableView;
#FXML
private TableView<Item> listTableView;
#FXML
private MenuItem logoutItem, profile;
#FXML
private javafx.scene.image.ImageView backImage;
#FXML
private MenuButton menuButton;
#FXML
private TableColumn<Item, String> itemColumn;
#FXML
private ComboBox<String> clientId, paymentMethod;
#FXML
private TableColumn<Item, Double> priceColumn, totalColumn, discountPercentageColumn, amountColumn;
#FXML
private TableColumn<Item, Integer> quantityColumn;
#FXML
private TableColumn<Product, String> productColumn;
#FXML
private TextField searchField,discountPercentage,productField,priceField,quantityField,vatPercentage,subTotalField,discountField,totalVatField,vatField,netPayableField,totalDiscountField;
#FXML
private TextField ;
#FXML
private TextField ;
#FXML
private TextField ;
#FXML
private TextField ;
#FXML
private TextArea descriptionArea;
#FXML
private Button addButton, removeButton, paymentButton, resetTableButton, resetButton;
#FXML
private Label quantityLabel, errorLabel, userName, backLabel;
#FXML
private ObservableList<Item> ITEMLIST;
public static Scene paymentScene;
private double xOffset = 0;
private double yOffset = 0;
public static double finalNetPayablePrice = 0.0;
public static double finalSubTotalPrice = 0.0;
public static double finalVat = 0.0;
public static double finalDiscount = 0.0;
public static String clientName = null;
public static String selectedPaymentMethod = null;
public static List<String> itemNames = new ArrayList<>();
public static List<Double> itemDiscounts = new ArrayList<>();
public static List<String> prices = new ArrayList<>();
public static List<String> quantities = new ArrayList<>();
public static List<String> subTotals = new ArrayList<>();
public static ObservableList<Item> itemList;
public static List<String> columnItemData = new ArrayList<>();
public static List<String> columnQuantityData = new ArrayList<>();
#FXML
private void initialize() throws SQLException, ClassNotFoundException, IOException {
ObservableList<Product> productsData = ProductDAO.searchGoodProducts(app.values.getProperty("STATUS_TYPE1"));
populateProducts(productsData);
}
#FXML
private void populateProducts(ObservableList<Product> productData) throws ClassNotFoundException {
productTableView.setItems(productData);
}
#Override
public void keyTyped(java.awt.event.KeyEvent e) {
}
#Override
public void keyPressed(java.awt.event.KeyEvent e) {
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_F1) {
try {
paymentAction();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
#Override
public void keyReleased(java.awt.event.KeyEvent e) {
}
#FXML
public void paymentAction() throws Exception {
if (validateInputsForPayment()) {
Payment payment = new Payment();
FXMLLoader loader = new FXMLLoader((getClass().getResource(app.values.getProperty("INVOICE_VIEW_LOCATION"))));
Parent root = loader.load();
Stage stage = new Stage();
root.setOnMousePressed((MouseEvent e) -> {
xOffset = e.getSceneX();
yOffset = e.getSceneY();
});
root.setOnMouseDragged((MouseEvent e) -> {
stage.setX(e.getScreenX() - xOffset);
stage.setY(e.getScreenY() - yOffset);
});
Scene scene = new Scene(root);
stage.initModality(Modality.APPLICATION_MODAL);
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
this.paymentScene = scene;
stage.showAndWait();
}
}
You shouldn't be using java.awt.event.KeyListener for a JavaFX application. JavaFX has its own set of event API.
Assuming that POSController is a controller class for a particular FXML:
public class POSController {
#FXML private BorderPane root; // Or any other Node from FXML file
#FXML private void initialize() {
javafx.event.EventHandler<javafx.scene.input.KeyEvent> handler = event -> {
if (event.getCode() == javafx.scene.input.KeyCode.F1) {
try {
paymentAction();
} catch (Exception e1) {
e1.printStackTrace();
}
}
};
// I'm using root to get scene, but any node would be fine
if (root.getScene() != null) {
root.getScene().addEventHandler(javafx.scene.input.KeyEvent.KEY_PRESSED, handler);
}
else {
root.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
root.getScene().addEventHandler(javafx.scene.input.KeyEvent.KEY_PRESSED, handler);
}
});
}
}
}
This will add the key event to the Scene. If you do not need to apply this event scene-wide, then you can add the event handler at other appropriate nodes.
Update
If there are any input controls in the scene, then you may need to use setEventFilter() instead of setEventHandler(). This is because those controls are probably going to consume the key event during the event bubbling phase.

Modify an object in a parent controller in JavaFX

here is my problem :
I have 3 FXML files with each controller assigned.
ExplorerPage.FXML : layout used to show a gridpane with undefined object number inside.
ExplorerNoteController.FXML : the object (StackPane) inserted in the gridpane.
NoteHoverMenuController.FXML : When the user moves the mouse on the previous object it shows options to update or delete the object.
To resume :
ExplorerPage.FXML -> ExplorerNoteController.FXML -> NoteHoverMenuController.FXML
Gridpane -> Stackpane -> Borderpane(2)
What I want to do is when I click on the "delete" button in the NoteHoverMenuController.FXML, this remove the current object from the gridpane (located in ExplorerPage.FXML). But I have no clue to do this. I tried binding with properties, static methods, .. but no satisfaying results.
This is my code :
ExplorerPage controller
public class ExplorerPage implements Initializable {
private Connection conn;
// LOAD ALL NOTES
private List<Note> gridNotes = new LinkedList<Note>();
JFXRadioButton alertButton = new JFXRadioButton(); // TEMP
StackPane spaneExpNote; // StackPane used to switch front/back pane (overlay)
private byte j = 0;
private byte k = 0;
#FXML
private GridPane FXMLNotesPane = new GridPane();
#Override
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
try {
// LOAD DATAS FROM DB W/ PARAM FOLDER
conn = DBInitialize.getInstance();
DAO<Note> findAllNotes = new NoteDAO(conn);
gridNotes = findAllNotes.findAll("Orange");
// STACKPANES CONSTRUCTION
for(int i = 0; i < gridNotes.size(); i++)
{
if (j>2) {
j=0;
k++;
}
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fr/cryption/view/ExplorerNote.fxml"));
spaneExpNote = (StackPane) loader.load();
ExplorerNoteController controller = loader.getController();
controller.setData(gridNotes.get(i).getTitleNote(), gridNotes.get(i).getContentNote(), gridNotes.get(i).getDateNote());
FXMLNotesPane.add(spaneExpNote,j,k);
j++;
}
ExplorerNoteController
public class ExplorerNoteController implements Initializable {
#FXML
StackPane rootNotePane;
#FXML
private Label labPaneTitle1;
#FXML
private Label labPaneDate1;
#FXML
private Label labPaneContent1;
BorderPane pane = null;
String ok = "ok";
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void setData(String title, String content, String date) {
labPaneTitle1.setText(title);
labPaneContent1.setText(content);
labPaneDate1.setText(date);
}
public void showHoverMenu() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fr/cryption/view/NoteHoverMenu.fxml"));
pane = (BorderPane) loader.load();
NoteHoverMenuController controller = loader.getController();
rootNotePane.getChildren().add(pane);
}
public void hideHoverMenu() throws IOException {
rootNotePane.getChildren().remove(pane);
}
....
NoteHoverMenuController
public class NoteHoverMenuController implements Initializable {
String idNote = "";
#FXML
BorderPane hovernode;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setID(String id) {
idNote = id;
}
public void deleteNoteExplorer(ActionEvent event) {
System.out.println(hovernode.getParent().lookup("stckpane")); // DELETE THE NOTE
}
}
I will update this post if you want FXML(s) as well.
Thanks.
SCREENS :
Screen Gridpane
Screen Hover StackPane

How to prepare FXML Controller to be GC

I have an issue in my application. Any Controllers are garbage collected.
I was prepared one, pretty simple with clear function, but it is no removed from memory.
#Log4j2
public class DialogOpenProjectController extends DialogPane implements Initializable, FXMLController, FXMLPane, FXMLDialogController {
#FXML
private ObjectProperty<ResourceBundle> languageBundle = new SimpleObjectProperty<>();
#FXML
private JFXTabPane TAB_PANE_OPEN_PROJECT;
#FXML
private JFXButton BUTTON_CONFIRM;
private Tab tabOpenNewProject;
private Tab tabOpenExistingProject;
private Stage stage;
private ChangeListener<? super ResourceBundle> languageListener = this::languageChange;
private ChangeListener<? super Tab> selectedTabListener = this::selectedTabChanged;
{
tabOpenExistingProject = new Tab();
tabOpenNewProject = new Tab();
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
log.trace(LogMessages.MSG_CTRL_INITIALIZATION);
this.languageBundle.setValue(rb);
initTabs();
addSelectedTabListener();
addDisableButtonListener();
addLanguageListener();
log.trace(LogMessages.MSG_CTRL_INITIALIZED);
}
#FXML
public void cancel() {
this.stage.close();
clean();
}
#FXML
public void confirm() {
if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenNewProject)) {
actionNewProject();
} else if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenExistingProject)) {
actionOpenProject();
} else {
//To to show error
}
this.stage.close();
clean();
}
private void initTabs() {
TAB_PANE_OPEN_PROJECT.getSelectionModel().select(tabOpenNewProject);
}
private void addSelectedTabListener() {
TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().addListener(selectedTabListener);
}
private void addDisableButtonListener() {
//nothing to do temporary
}
private void clean() {
this.languageBundle.removeListener(languageListener);
languageBundle.unbind();
languageBundle.setValue(null);
TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().removeListener(selectedTabListener);
TAB_PANE_OPEN_PROJECT.getSelectionModel().clearSelection();
TAB_PANE_OPEN_PROJECT.getTabs().clear();
BUTTON_CONFIRM.disableProperty().unbind();
selectedTabListener = null;
languageListener = null;
tabOpenNewProject = null;
tabOpenExistingProject = null;
stage = null;
getChildren().clear();
}
private void addLanguageListener() {
this.languageBundle.addListener(languageListener);
}
private void languageChange(ObservableValue<? extends ResourceBundle> observable, ResourceBundle oldValue, ResourceBundle newValue) {
reloadElements();
}
private String getValueFromKey(String key) {
return this.languageBundle.getValue().getString(key);
}
private void reloadElements() {
// Nothing to do
}
public void setStage(Stage stage) {
this.stage = stage;
}
private void selectedTabChanged(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
if (newValue.equals(tabOpenNewProject)) {
BUTTON_CONFIRM.setText(getValueFromKey(Keys.CREATE));
} else if (newValue.equals(tabOpenExistingProject)) {
BUTTON_CONFIRM.setText(getValueFromKey(Keys.OPEN));
}
}
}
For loading FXML files I use Singleton Class ScreenManager. This method is called to load this Dialog :
public void showNewDialog( FilesFXML fxml) {
FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml.toString()), context.getBundleValue());
try {
Stage dialogStage = new Stage();
AnchorPane dialogwindow = (AnchorPane) loader.load();
FXMLDialogController controller = loader.getController();
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(this.getStage());
Scene scene = new Scene(dialogwindow);
dialogStage.setScene(scene);
dialogStage.setResizable(false);
controller.setStage(dialogStage);
dialogStage.showAndWait();
} catch (Exception ex) {
log.error(ex.getMessage());
log.error(ex.getCause());
ex.printStackTrace();
}
}
I was checked it in VisualVM and I see one 1 of this controller and 2 lambdas ( I suppose it consiste initialization of 2 listener)
But event clear function is called this dialog is still in memory and cannot be garbage collected. I have no more idea how to force removing it. It is reallegrave, because it consist all of my Controllers.
I'm pretty confused what you are asking and what you are talking about. Nevertheless, if that dialog window is open, there is no way it can be garbage collected.
The JavaFX main API is holding a strong reference of the stage (maybe the scene too).
The stage/scene is holding strong reference of all the nodes specified in your FXML file.
Some of those nodes are holding strong reference of the controller because you have specified action event handlers in the controller.
In order to make the controller eligible for garbage collection, you need to break this chain somewhere.
The easiest way is to close the dialog window. When the window is closed, because the JavaFX engine does not need to manage render pulses anymore, it will release its reference to the stage. At that point, the whole thing under that stage is eligible for garbage collection, if nothing else (that itself is not eligible for garbage collection) is holding strong references of them.

How to get reference to another controller - JavaFX

Let's say I've got 3 views and 3 controllers:
LogInController, LogInView
MainMenuController, MainMenuView
ListOfPatientsInternalMedicineController, ListOfPatientsInternalMedicineView.
An internalMedicineButtonClicked method change my scene to another (with some other content) but in this new scene, I want to have a button which allows me to go back to MainMenu (goBacktoMainMenuButtonClicked() method). And here occures my problem. How am I able to get reference to MainMenuController (the one which is corresponding with fxml file, created in LogInController) to fill setController() method.
public class LogInController {
MainMenuController mainMenuController = new MainMenuController();
#FXML
private JFXTextField logInTextField;
#FXML
private JFXButton logInButton;
#FXML
private JFXPasswordField passwordTextField;
#FXML
void logInButtonClicked(ActionEvent event) throws IOException {
LogInDAO logInDAO = new LogInDAO();
if(logInDAO.checkIfLoginAndPasswordIsCorrect(logInTextField.getText(),passwordTextField.getText()))
{
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/MainMenu.fxml"));
Window window = logInButton.getScene().getWindow();
Stage stage = (Stage) window;
loader.setController(mainMenuController); // here i'm passing original controller corresponding with fmxl
stage.setScene(new Scene(loader.load()));
}
else
{
(...)
}
}
}
MainMenuCotroller class:
public class MainMenuController {
ContentOfPatientTableView patientTableViewModel = new ContentOfPatientTableView();
(..)
#FXML
void internalMedicineButtonClicked(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/ListOfPatientsInternalMedicineView.fxml"));
Button button = (Button) event.getSource();
Scene scene = button.getScene();
Stage stage = (Stage) scene.getWindow();
loader.setController(new ListOfPatientsInternalMedicineController(patientTableViewModel));
stage.setScene(new Scene(loader.load()));
}
And ListOfPatientsInternalMedicineController class;
public class ListOfPatientsInternalMedicineController {
IPatientDAO patientDAO = new PatientDAO();
ContentOfPatientTableView patientTableViewModel;
public ListOfPatientsInternalMedicineController(ContentOfPatientTableView content) {
patientTableViewModel=content;
}
#FXML
public void goBacktoMainMenuButtonClicked(ActionEvent event)
{
FXMLLoader loader = new FXMLLoader(MainMenuController.class.getResource("/fxml/MainMenuView.fxml");
loader.setController(?????????); // Here if I will pass new MainController() i will create new instance, not this which is corresponding with fxml file. How am I able to refer to instance MainController created in LogInController ?
}
}
Consider using another model to represent the current view. You could implement this along the following lines:
public class ViewState {
private final ContentOfPatientTableView patientTableViewModel ;
private final ReadOnlyObjectWrapper<Parent> currentView = new ReadOnlyObjectWrapper<>();
private Parent logInView ;
private Parent mainMenuView ;
private Parent listOfPatientsMainMedicineView ;
public ViewState(ContentOfPatientTableView patientTableViewModel) {
this.patientTableViewModel = patientTableViewModel ;
}
public ReadOnlyObjectProperty<Parent> currentViewProperty() {
return currentView.getReadOnlyProperty();
}
public void showLogIn() {
if (logInView == null) {
try {
FXMLLoader loader = new FXMLLoader("/fxml/LogIn.fxml");
loader.setController(new LogInController(this));
logInView = loader.load();
} catch (IOException exc) {
// fatal...
throw new UncheckedIOException(exc);
}
}
currentView.set(logInView);
}
public void showMainMenu() {
// similarly...
}
public void showListOfPatientsMainMedicineView() {
// ...
}
}
Now your LogInController can do:
public class LogInController {
private final ViewState viewState ;
#FXML
private JFXTextField logInTextField;
#FXML
private JFXButton logInButton;
#FXML
private JFXPasswordField passwordTextField;
public LogInController(ViewState viewState) {
this.viewState = viewState ;
}
#FXML
void logInButtonClicked(ActionEvent event) {
LogInDAO logInDAO = new LogInDAO();
if(logInDAO.checkIfLoginAndPasswordIsCorrect(logInTextField.getText(),passwordTextField.getText()))
{
viewState.showMainMenu();
}
else
{
(...)
}
}
}
Similarly,
public class MainMenuController {
private final ViewState viewState ;
public MainMenuController(ViewState viewState) {
this.viewState = viewState ;
}
#FXML
void internalMedicineButtonClicked(ActionEvent event) throws IOException {
viewState.showListOfPatientsMainMedicineView();
}
}
and similarly for the other controller.
Note that you are instantiating each controller in ViewState, so just give that class access to each of the other models it may need.
Finally, you boot all this up with
public class MyApp extends Application {
#Override
public void start(Stage primaryStage) {
ViewState viewState = new ViewState(/* pass models here...*/);
viewState.showLogIn();
Scene scene = new Scene(viewState.currentViewProperty().get());
scene.rootProperty().bind(viewState.currentViewProperty());
primaryStage.setScene(scene);
primaryStage.show();
}
}

Resources