两个JavaFx控制器之间的通信

时间:2014-03-04 17:14:43

标签: java controller javafx fxml

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

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

控制器1
/ \
FX:包括FX:包括
/ \
控制器2 Controller3

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

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

我已经尝试使用FXMLoader获取我的Controller 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>

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

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

精密工业:
我在这个基础上提出了我的申请: http://www.zenjava.com/2011/10/25/views-within-views-controllers-within-controllers/我在Java JDK 7上使用JavaFX 2

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

3 个答案:

答案 0 :(得分:7)

我想到了两种方式:

  1. 基于“FXML简介”(link)的“嵌套控制器”部分,您可以将子控制器(2&amp; 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事件,无论它是如何创建的;表单生成此事件,而不关心谁在监听 - 如果有的话)。我想在你的情况下我更喜欢这个解决方案。可以选择使用依赖注入来检索事件总线。

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

答案 1 :(得分:7)

Nikos提出了关于耦合的一个好点(软件工程原理)。有一种方法可以实现第一种(简单)方法的“精神”,而不是通过使用Mediator模式来蚕食这一原则。取自维基百科(引用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");

并且Controller 3可以使用相同的介体与Controller2进行通信:

// ... somewhere in Controller3
ControllerMediator.getInstance().controller2DoSomething();

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

重要的是你已经将Controller2和Controller3分离(他们彼此不了解)。我刚刚在一个我正在研究的小项目中使用了这个模式(受到Nikos的第一个解决方案的启发,但是立即考虑Mediator模式以消除他(正确)抱怨的耦合。

答案 2 :(得分:0)

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

来自Main.java - &gt;启动方法:FXML文件中的(使用fx:controller =“Controller”)

    private Controller controller;
    private DataProcess data;

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

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

在Controller类中:

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

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

方法示例:

{{1}}

希望这有助于。