两个 JavaFx 控制器之间的通信

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22178533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:06:49  来源:igfitidea点击:

Communication between two JavaFx controllers

javacontrollerjavafxfxml

提问by jbrtrnd

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.

我制作了一个控制器和视图 (fxml) 的结构来尽可能地分离我的代码,我想知道如何在 2 个控制器之间进行通信。我的意思是,一个控制器必须调用另一个控制器的某些功能才能将其设置为最新。

I think a schema of my current structure will be more explicit:

我认为我当前结构的模式会更明确:

          Controller 1
           /              \
   fx:include    fx:include
       /                     \
Controller2      Controller3

          控制器 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) :

每个控制器都有自己的 fxml 视图。
- 控制器 1:一个容器控制器,它有一个带有 2 个选项卡的 TabPane 元素(每个选项卡对应 1 个控制器)
- 控制器 2:一个列表
- 控制器 3:一个表单

您可能已经猜到我想要我的表单(控制器 3)自动更新我的列表(控制器 2)。目前,表单只是一个“创建表单”,所以我只想在我的列表中添加行。

我已经尝试使用 FXMLoader 获取我的控制器 2 并调用函数来重新加载我的 tableView,但没有成功..

控制器 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) :

控制器 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) :

控制器 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

精度:
我在此基础上制作了我的应用程序:http: //www.zenjava.com/2011/10/25/views-within-views-controllers-within-controllers/我在 Java JDK 7 上使用 JavaFX 2

I feel something missing in global functioning of a JavaFX application.

我觉得 JavaFX 应用程序的全局功能缺少一些东西。

采纳答案by Nikos Paraskevopoulos

Two ways come into my mind:

我想到了两种方法:

  1. Based on the "Nested Controllers" section of "Introduction to FXML" (link), you could inject the children controllers (2 & 3) into the parent (1) and have the parent coordinate their interactions:

    FXML (1):

    <fx:include source="FolderList.fxml" fx:id="list" />
    ...
    <fx:include source="FolderFormAdd.fxml" fx:id="addForm" />
    

    Java (1) (beware the names of the fields; must match <fx:id>Controller, i.e.):

    public class FolderController extends AbstractController {
        @FXML private FolderListController listController;
        @FXML private FolderFormAddController addFormController;
        void initialize() {
            // add code to coordinate them
        }
    }
    

    I do not like this solution much for this case, as it leads to strong coupling between the components.On the other hand, it may be the quickest.

  2. Use an event bus (e.g. from Google Guava). This can actually decouple your logic (e.g. the list component listens to the PersonAddedevent, no matter how it was created; the form generates this event, without caring who is listening - if any). I think I would prefer this solution in your case. The event bus can optionally be retrieved using dependency injection.

  1. 根据“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
        }
    }
    

    我不太喜欢这种情况下的解决方案,因为它会导致组件之间的强耦合。另一方面,它可能是最快的。

  2. 使用事件总线(例如来自Google Guava)。这实际上可以解耦您的逻辑(例如,列表组件监听PersonAdded事件,无论它是如何创建的;表单生成这个事件,而不关心谁在监听——如果有的话)。我想在你的情况下我更喜欢这个解决方案。可以选择使用依赖注入来检索事件总线。

Check out the answer pointed by the comment from jewelsea, it is great - I have already upvoted it myself :)

查看jewelsea评论中指出的答案,这很棒-我自己已经投票了:)

回答by Jim Daehn

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

Nikos 对耦合提出了一个很好的观点(软件工程原理)。但是,有一种方法可以实现第一种(简单)方法的“精神”,而不是通过使用 Mediator 模式来侵犯这一原则。摘自维基百科(参考 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:

现在,由于 Controller1 注入了 Controller2 和 Controller3(如 fxml 文件中所述),您可以在 Controller1::initialize() 方法中执行以下操作:

@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:

现在,在任何需要 Controller2 与 Controller3 通信的地方,您都可以简单地使用中介器:

// ... somewhere in Controller2
ControllerMediator.getInstance().controller3OperateOn("my data");

and Controller 3 can communicate back to Controller2 using the same mediator:

并且控制器 3 可以使用相同的中介与控制器 2 进行通信:

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

当然,这依赖于Controller2实现了doSomething()操作,Controller3实现了operateOn(String data)操作。

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.

重要的是你已经解耦了 Controller2 和 Controller3(他们不知道对方)。我刚刚在我现在正在进行的一个小项目中使用了这个模式(受到 Nikos 的第一个解决方案的启发,但立即想到了 Mediator 模式来消除他(正确)所关注的耦合。

回答by Teaque

I figured out an easy to implement solution for programmers who are confused on how to pass fx:controller="Controller" from their FXML files into the Main Class, and/or Controllers. To allow for an object reference between these Classes.

对于那些对如何将 fx:controller="Controller" 从他们的 FXML 文件传递​​到主类和/或控制器感到困惑的程序员,我想出了一个易于实现的解决方案。允许这些类之间的对象引用。

From Main.java -> start method: (With fx:controller="Controller") in FXML File

从 Main.java -> 启动方法:(使用 fx:controller="Controller")在 FXML 文件中

    FXMLLoader loader = new FXMLLoader();
    Parent root = loader.load(getClass().getResource("Window1.fxml").openStream());
    Controller controller = loader.getController();
    controller.setReferenceToController(controller);

The last line [controller.setReferenceToController(controller);] passes the object "Controller" loaded from the FXML file to itself.

最后一行 [controller.setReferenceToController(controller);] 将从 FXML 文件加载的对象“Controller”传递给自身。

In the Controller Class:

在控制器类中:

    private Controller controller;
    private DataProcess data;

    public void setReferenceToController(Controller controller){
    this.controller = controller;
    data = new DataProcess(controller);
}

Now each time you open or start a new "Window" or create a separate class(such as DataProcess) just pass the controller object references between them. This will allow full communication between controllers, FXML, and Classes.

现在,每次打开或启动一个新的“窗口”或创建一个单独的类(例如 DataProcess)时,只需在它们之间传递控制器对象引用。这将允许控制器、FXML 和类之间的完全通信。

Example of methods:

方法示例:

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

Hope this Helps.

希望这可以帮助。