Связь между двумя контроллерами JavaFx
Я сделал структуру контроллеров и представлений (fxml), чтобы отделить мой код как можно больше, и мне интересно, как общаться между 2 контроллерами. Я имею в виду, что контроллер должен вызвать некоторые функции другого контроллера, чтобы настроить его на сегодняшний день.
Я думаю, что схема моей текущей структуры будет более явной:
Один/
форекс:Форекс включают в себя:включают в себя
/
Controller2 Controller3
каждый контроллер имеет собственный вид fxml.
- Контроллер 1: контроллер контейнера, который имеет элемент TabPane с 2 вкладками (каждая вкладка соответствует 1 контроллеру)
- Контроллер 2 : Список
- Регулятор 3 : Форма
Вы, наверное, догадались, что я хочу, чтобы моя форма (контроллер 3) автоматическое обновление списка (контроллер 2).
На данный момент форма является только "формой создания", поэтому я просто хочу добавить строку в свой список.
Я уже пытался получить мой контроллер 2 с FXMLoader и вызвать функции для перезапуска моего tableView, без успеха..
1 (.ява. + 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>
2 (.ява. + 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>
3 (.ява. + 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>
уточнения:
Я сделал заявление на этой базе:
http://www.zenjava.com/2011/10/25/views-within-views-controllers-within-controllers/ и я использую JavaFX 2 на Java JDK 7
Я чувствую, что чего-то не хватает в глобальном функционировании приложение JavaFX.
3 ответов
два способа приходят мне на ум:
-
на основе раздела "вложенные контроллеры" раздела "введение в FXML" (ссылке), вы можете ввести дочерние контроллеры (2 & 3) в родительский (1) и иметь родительскую координацию их взаимодействий:
FXML (1):
<fx:include source="FolderList.fxml" fx:id="list" /> ... <fx:include source="FolderFormAdd.fxml" fx:id="addForm" />
Java (1) (остерегайтесь имен полей; должны соответствовать
<fx:id>Controller
, т. е.):public class FolderController extends AbstractController { @FXML private FolderListController listController; @FXML private FolderFormAddController addFormController; void initialize() { // add code to coordinate them } }
мне не нравится это решение для этого случая, так как это приводит к сильной связи между компонентами. С другой стороны, это может быть самый быстрый.
используйте шину событий (например, от Google Guava). Это может фактически развязать вашу логику (например, компонент list слушает
PersonAdded
событие, независимо от того, как он был создан; форма генерирует это событие, не заботясь, кто слушает - если таковые имеются). Я думаю, что предпочел бы это решение в вашем случае. Шину событий можно дополнительно получить с помощью инъекция зависимости.
Проверьте ответ, указанный комментарием от jewelsea, это здорово - я уже upvoted его сам:)
Никос делает хороший пункт (принцип инженерства программного обеспечения) о соединении. Однако есть один способ-достичь "духа" первого (простого) подхода и не посягать на этот принцип, используя шаблон посредника. Как взято из Википедии (которая ссылается на GoF):
"суть шаблона посредника состоит в том, чтобы"определить объект, который инкапсулирует, как взаимодействует набор объектов". Оно повышает свободное соединение путем держать предметы от ссылаться к каждому другие явно, и это позволяет их взаимодействие варьироваться независимо. Клиентские классы могут использовать медиатор для отправки сообщений другим клиентам и получать сообщения от других клиентов через событие в классе медиатора."
здесь вы можете рассматривать ваши контроллеры в качестве клиентов. То, что вам нужно, - это использовать посредника для посредничества "разговоров" между собой.
сначала создайте интерфейс посредника:
public interface IMediateControllers {
void registerController2(Controller2 controller);
void registerController3(Controller3 controller);
void controller2DoSomething();
void controller3OperateOn(String data);
}
и тогда конкретный посредник (как синглтон)
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();
}
}
теперь, поскольку Controller1 имеет Controller2 и Controller3 (как отмечено в файле fxml), вы можете сделать следующее В Controller1::initialize() метод:
@Override
public void initialize(Url url, ResourceBundle resource) {
ControllerMediator.getInstance().registerController2(controller2Controller);
ControllerMediator.getInstance().registerController3(controller3Controller);
}
теперь, везде, где вам нужен Controller2 для связи с Controller3, вы просто используете посредника:
// ... somewhere in Controller2
ControllerMediator.getInstance().controller3OperateOn("my data");
и регулятор 3 может связывать назад к Controller2 используя такой же посредник:
// ... somewhere in Controller3
ControllerMediator.getInstance().controller2DoSomething();
конечно, это полагается на Controller2, реализовав doSomething()
операция и Controller3 реализовав operateOn(String data)
операции.
важно то, что вы отделили Controller2 и Controller3 (они не знают друг о друге). Я просто использовал этот шаблон в небольшом проекте, над которым работаю прямо сейчас (вдохновленный первым решением Никоса, но сразу же подумав о шаблоне медиатора, чтобы удалить соединение, которое он (правильно) сжал.
Я придумал простое в реализации решение для программистов, которые путаются в том, как передать fx:controller="Controller" из своих файлов FXML в основной класс и/или контроллеры. Для ссылки на объект между этими классами.
От Main.java - > метод запуска: (с fx:controller="Контроллер") в файле FXML
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("Window1.fxml").openStream());
Controller controller = loader.getController();
controller.setReferenceToController(controller);
последняя строка [контроллер.setReferenceToController (controller);] передает объект "контроллер", загруженный из файла FXML в себя.
в классе контроллера:
private Controller controller;
private DataProcess data;
public void setReferenceToController(Controller controller){
this.controller = controller;
data = new DataProcess(controller);
}
Теперь каждый раз, когда вы открываете или запускаете новое "окно" или создаете отдельный класс(например, DataProcess), просто передайте ссылки на объект контроллера между ними. Это позволит обеспечить полную связь между контроллерами, FXML и классами.
методы:
public DataProcess(Controller controller) {
this.controller = controller;
}
//Call this method from Controller
public void handleSaveClick(){
if(file != null){
//save a bunch of data
controller.setSaveStatus(true);
}
else
controller.setSaveStatus(false);
надеюсь, это поможет.