I made a structure to of Controllers and Views (fxml) to separate my code as much as I could, and I'm wondering how to communicate between 2 controllers. I mean, a controller have to call some functions of another controller to set it up to date.
I think a schema of my current structure will be more explicit:
Controller 1
/ \
fx:include fx:include
/ \
Controller2 Controller3
Each controller has is own fxml view.
- Controller 1 : a container controller which has a TabPane element with 2 tabs (each tab correspond to 1 controller)
- Controller 2 : a list
- Controller 3 : a form
You've probably guessed that I want my form (controller 3) to automatically update my list (controller 2).
For the moment, the form is only a "creation form", so I just want to add row in my list.
I've already tried to get my Controller 2 with FXMLoader and call the functions to relaod my tableView, no success..
Controller 1 (.java + .fxml) :
package pappu.controllers;
import pappu.core.controller.AbstractController;
public class FolderController extends AbstractController
{
}
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="view" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="pappu.controllers.FolderController">
<TabPane>
<tabs>
<Tab text="RECHERCHE">
<content>
<AnchorPane id="Content">
<children>
<fx:include source="FolderList.fxml" />
</children>
</AnchorPane>
</content>
</Tab>
<Tab text="DOSSIER">
<content>
<AnchorPane id="Content">
<children>
<fx:include source="FolderFormAdd.fxml" />
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
</VBox>
Controller 2 (.java + .fxml) :
package pappu.controllers;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;
import org.hibernate.Session;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;
import pappu.core.controller.AbstractController;
import pappu.entities.Folder;
public class FolderListController extends AbstractController implements Initializable
{
/**
* TableView object
*/
@FXML private TableView<Folder> foldersTableView;
/**
* FolderNumber column object
*/
@FXML private TableColumn<Folder, String> colFolderNumber;
/**
* Person column object
*/
@FXML private TableColumn<Folder, String> colPerson;
/**
* Birthday date column object
*/
@FXML private TableColumn<Folder, Date> colBirthdayDate;
/**
* List of folders
*/
private static List<Folder> foldersList;
/**
* Constructor
* Will make a call to initializeFoldersList()
*/
public FolderListController()
{
initializeFoldersList();
}
/**
* Initialize implementation of the Initializable interface
*
* @param location
* @param resources
*/
@Override
public void initialize(URL location, ResourceBundle resources)
{
initializeTableColumns();
loadData();
}
/**
* Query the database to retrieve the folder list
*/
@SuppressWarnings("unchecked")
public void initializeFoldersList()
{
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
foldersList = session.createQuery("from Folder").list();
session.close();
}
/**
* Initialize columns binding to folders properties
*/
public void initializeTableColumns()
{
colFolderNumber.setCellValueFactory(
new PropertyValueFactory<Folder,String>("folderNumber")
);
colPerson.setCellValueFactory(
new Callback<CellDataFeatures<Folder, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<Folder, String> p) {
return new SimpleStringProperty(p.getValue().getFirstName() + " " + p.getValue().getLastName());
}}
);
colBirthdayDate.setCellValueFactory(
new PropertyValueFactory<Folder,Date>("birthdayDate")
);
}
/**
* Put the folders list in the TableView object
*/
public void loadData()
{
ObservableList<Folder> listFold = FXCollections.observableArrayList(foldersList);
foldersTableView.setItems(listFold);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Label?>
<VBox fx:id="view" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="pappu.controllers.FolderListController">
<Label fx:id="lblTest"></Label>
<TableView fx:id="foldersTableView">
<columns>
<TableColumn prefWidth="75.0" text="N°" fx:id="colFolderNumber">
</TableColumn>
<TableColumn prefWidth="75.0" text="Personne" fx:id="colPerson">
</TableColumn>
<TableColumn prefWidth="75.0" text="Date de naissance" fx:id="colBirthdayDate">
</TableColumn>
</columns>
</TableView>
</VBox>
Controller 3 (.java + .fxml) :
package pappu.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import org.hibernate.Session;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import pappu.core.AppFactory;
import pappu.core.controller.AbstractController;
import pappu.entities.Folder;
import pappu.entities.Gender;
public class FolderFormAddController extends AbstractController
{
@FXML TextField folderNumber;
@FXML TextField firstName;
@FXML TextField lastName;
public void submitForm() throws IOException
{
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Folder folder = new Folder();
folder.setFolderNumber(folderNumber.getText());
folder.setFirstName(firstName.getText());
folder.setLastName(lastName.getText());
folder.setGender(Gender.m);
session.save(folder);
session.getTransaction().commit();
// This doesn't work.. even tried with a simple Label
AppFactory app = new AppFactory();
FolderListController flc = app.folderListController();
flc.initializeFoldersList();
flc.loadData();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="view" prefHeight="216.0" prefWidth="421.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="pappu.controllers.FolderFormAddController">
<children>
<Label prefHeight="26.0" prefWidth="102.0" text="Numéro de dossier" />
<TextField prefWidth="200.0" fx:id="folderNumber"/>
<Label text="Prénom" />
<TextField prefWidth="200.0" fx:id="firstName"/>
<Label text="Nom" />
<TextField prefWidth="200.0" fx:id="lastName"/>
<Button mnemonicParsing="false" onAction="#submitForm" text="Enregistrer" />
</children>
</VBox>
Precisions:
I made my application on this base:
http://www.zenjava.com/2011/10/25/views-within-views-controllers-within-controllers/ and I use JavaFX 2 on Java JDK 7
I feel something missing in global functioning of a JavaFX application.
Nikos makes a good point (Software Engineering principle) about coupling. There is one way though, to accomplish the "spirit" of the first (simple) approach and not encroach upon this principle by using the Mediator pattern. As taken from wikipedia (which is referencing the GoF):
"The essence of the Mediator Pattern is to "define an object that encapsulates how a set of objects interact". It promotes loose coupling by keeping objects from referring to each other explicitly, and it allows their interaction to be varied independently. Client classes can use the mediator to send messages to other clients, and can receive messages from other clients via an event on the mediator class."
Here you can consider your Controllers as the clients. What you need to have happen then is use the mediator to mediate "conversations" between each other.
First create a mediator interface:
public interface IMediateControllers {
void registerController2(Controller2 controller);
void registerController3(Controller3 controller);
void controller2DoSomething();
void controller3OperateOn(String data);
}
And then a concrete mediator (as a Singleton)
public class ControllerMediator implements IMediateControllers {
private Controller2 controller2;
private Controller3 controller3;
@Override
void registerController2(Controller2 controller) {
controller2 = controller;
}
@Override
void registerController3(Controller3 controller) {
controller3 = controller;
}
@Override
void controller2DoSomething() {
controller2.doSomething();
}
void controller3OperateOn(String data) {
controller3.operateOn(data);
}
/**
* Everything below here is in support of Singleton pattern
*/
private ControllerMediator() {}
public static ControllerMediator getInstance() {
return ControllerMediatorHolder.INSTANCE;
}
private static class ControllerMediatorHolder {
private static final ControllerMediator INSTANCE = new ControllerMediator();
}
}
Now, since Controller1 has Controller2 and Controller3 injected (as noted in the fxml file), you could do the following in Controller1::initialize() method:
@Override
public void initialize(Url url, ResourceBundle resource) {
ControllerMediator.getInstance().registerController2(controller2Controller);
ControllerMediator.getInstance().registerController3(controller3Controller);
}
Now, anywhere you need Controller2 to communicate with Controller3, you simple use the mediator:
// ... somewhere in Controller2
ControllerMediator.getInstance().controller3OperateOn("my data");
and Controller 3 can communicate back to Controller2 using the same mediator:
// ... somewhere in Controller3
ControllerMediator.getInstance().controller2DoSomething();
Of course, this relies on Controller2 having implemented the doSomething()
operation and Controller3 having implemented the operateOn(String data)
operation.
The important thing is that you've decoupled Controller2 and Controller3 (they don't know about each other). I just used this pattern in a little project I'm working on right now (inspired by Nikos' first solution, but thinking immediately of the Mediator pattern to remove the coupling he (properly) griped about.