如何在javafx中传递参数到辅助窗口?是否有与相应控制器通信的方法?

例如: 用户从TableView中选择一个客户,然后打开一个新窗口,显示该客户的信息。

Stage newStage = new Stage();
try 
{
    AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
    Scene scene = new Scene(page);
    newStage.setScene(scene);
    newStage.setTitle(windowTitle);
    newStage.setResizable(isResizable);
    if(showRightAway) 
    {
        newStage.show();
    }
}

newStage将是新窗口。问题是,我找不到一种方法告诉控制器在哪里寻找客户的信息(通过传递id作为参数)。

什么好主意吗?


当前回答

使用MVC

这个回答主要集中在将参数从调用类传递给控制器的直接调用上。

如果相反,你想要解耦调用者和控制器,并使用一个更通用的架构,包括一个具有可设置和可侦听属性的模型类来实现控制器之间的通信,请参阅以下基本概述:

使用JavaFx应用MVC

推荐的方法

这个答案列举了向FXML控制器传递参数的不同机制。

对于小型应用程序,我强烈建议直接将参数从调用者传递给控制器——这很简单,直接,不需要额外的框架。

对于更大、更复杂的应用程序,如果您想在应用程序中使用依赖注入或事件总线机制,那么值得研究一下。

直接从调用者向控制器传递参数

通过从FXML加载器实例检索控制器并调用控制器上的方法以使用所需的数据值初始化控制器,将自定义数据传递给FXML控制器。

类似于下面的代码:

public Stage showCustomerDialog(Customer customer) {
  FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
      "customerDialog.fxml"
    )
  );

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(loader.load())
  );

  CustomerDialogController controller = loader.getController();
  controller.initData(customer);

  stage.show();

  return stage;
}

...

class CustomerDialogController {
  @FXML private Label customerName;
  void initialize() {}
  void initData(Customer customer) {
    customerName.setText(customer.getName());
  }
}

一个新的FXMLLoader被构造,如示例代码所示,即new FXMLLoader(location)。该位置是一个URL,您可以通过以下方式从FXML资源生成这样一个URL:

new FXMLLoader(getClass().getResource("sample.fxml"));

注意不要在FXMLLoader上使用静态加载函数,否则您将无法从加载器实例中获得控制器。

FXMLLoader实例本身从来不知道任何关于域对象的信息。你不直接传递应用程序特定的域对象到FXMLLoader构造函数,而是:

在指定位置根据fxml标记构造一个FXMLLoader 从FXMLLoader实例获取一个控制器。 调用检索到的控制器上的方法,为控制器提供对域对象的引用。

这篇博客(由另一位作者撰写)提供了一个类似的替代例子。

在FXMLLoader上设置控制器

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
        "customerDialog.fxml"
    )
);
loader.setController(dialogController);

Pane mainPane = loader.load();

你可以在代码中构造一个新的控制器,把你想从调用者那里得到的任何参数传递给控制器构造函数。一旦构建了控制器,就可以在调用load()实例方法之前在FXMLLoader实例上设置它。

要在加载器上设置控制器(在JavaFX 2.x中),你不能在你的fxml文件中定义fx:controller属性。

由于FXML中fx:controller定义的限制,我个人更喜欢从FXMLLoader中获取控制器,而不是将控制器设置到FXMLLoader中。

让控制器从外部静态方法检索参数

Sergey在Controller.java文件中对Javafx 2.0 How-to Application.getParameters()的回答举例说明了这种方法。

使用依赖注入

FXMLLoader支持依赖注入系统,如Guice, Spring或Java EE CDI,允许你在FXMLLoader上设置自定义控制器工厂。这提供了一个回调,您可以使用该回调创建控制器实例,其中包含由各自的依赖注入系统注入的依赖值。

下面给出了一个JavaFX应用程序和控制器依赖注入Spring的示例:

在JavaFX中添加Spring依赖注入(JPA Repo, Service)

加力是一种非常好的、干净的依赖注入方法。Fx框架与示例air-hacks应用程序使用它。加力燃烧室。fx依赖于JEE6 javax。注入以执行依赖项注入。

使用事件总线

Greg Brown是FXML规范的最初创建者和实现者,他经常建议考虑使用事件总线,例如Guava EventBus,用于FXML实例化控制器和其他应用程序逻辑之间的通信。

EventBus是一个简单但功能强大的带有注释的发布/订阅API,它允许pojo在JVM中的任何地方相互通信,而不必相互引用。

后续的问答

关于第一种方法,你为什么返回舞台?这个方法也可以是空的,因为你已经给出了show()命令;在返回阶段之前;。如何通过返回Stage来计划使用

It is a functional solution to a problem. A stage is returned from the showCustomerDialog function so that a reference to it can be stored by an external class which may wish to do something, such as hide the stage based on a button click in the main window, at a later time. An alternate, object-oriented solution could encapsulate the functionality and stage reference inside a CustomerDialog object or have a CustomerDialog extend Stage. A full example for an object-oriented interface to a custom dialog encapsulating FXML, controller and model data is beyond the scope of this answer, but may make a worthwhile blog post for anybody inclined to create one.


StackOverflow用户@ zim提供的附加信息

Spring引导依赖注入示例

关于如何做到“Spring Boot Way”的问题,有一个关于JavaFX 2的讨论,我在附件中回答了这个问题。 该方法仍然有效,并在2016年3月Spring Boot v1.3.3.RELEASE上进行了测试: https://stackoverflow.com/a/36310391/1281217


有时,你可能想要将结果返回给调用者,在这种情况下,你可以检查出相关问题的答案:

JavaFX FXML从控制器A传递到控制器B并返回的参数

其他回答

你可以决定使用一个公共可观察对象列表来存储公共数据,或者只是创建一个公共setter方法来存储数据并从相应的控制器中检索

Why answer a 6 year old question ? One the most fundamental concepts working with any programming language is how to navigate from one (window, form or page) to another. Also while doing this navigation the developer often wants to pass data from one (window, form or page) and display or use the data passed While most of the answers here provide good to excellent examples how to accomplish this we thought we would kick it up a notch or two or three We said three because we will navigate between three (window, form or page) and use the concept of static variables to pass data around the (window, form or page) We will also include some decision making code while we navigate

public class Start extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        // This is MAIN Class which runs first
        Parent root = FXMLLoader.load(getClass().getResource("start.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setResizable(false);// This sets the value for all stages
        stage.setTitle("Start Page"); 
        stage.show();
        stage.sizeToScene();
    }

    public static void main(String[] args) {
        launch(args);
    } 
}

启动控制器

public class startController implements Initializable {

@FXML Pane startPane,pageonePane;
@FXML Button btnPageOne;
@FXML TextField txtStartValue;
public Stage stage;
public static int intSETonStartController;
String strSETonStartController;

@FXML
private void toPageOne() throws IOException{

    strSETonStartController = txtStartValue.getText().trim();


        // yourString != null && yourString.trim().length() > 0
        // int L = testText.length();
        // if(L == 0){
        // System.out.println("LENGTH IS "+L);
        // return;
        // }
        /* if (testText.matches("[1-2]") && !testText.matches("^\\s*$")) 
           Second Match is regex for White Space NOT TESTED !
        */

        String testText = txtStartValue.getText().trim();
        // NOTICE IF YOU REMOVE THE * CHARACTER FROM "[1-2]*"
        // NO NEED TO CHECK LENGTH it also permited 12 or 11 as valid entry 
        // =================================================================
        if (testText.matches("[1-2]")) {
            intSETonStartController = Integer.parseInt(strSETonStartController);
        }else{
            txtStartValue.setText("Enter 1 OR 2");
            return;
        }

        System.out.println("You Entered = "+intSETonStartController);
        stage = (Stage)startPane.getScene().getWindow();// pane you are ON
        pageonePane = FXMLLoader.load(getClass().getResource("pageone.fxml"));// pane you are GOING TO
        Scene scene = new Scene(pageonePane);// pane you are GOING TO
        stage.setScene(scene);
        stage.setTitle("Page One"); 
        stage.show();
        stage.sizeToScene();
        stage.centerOnScreen();  
}

private void doGET(){
    // Why this testing ?
    // strSENTbackFROMPageoneController is null because it is set on Pageone
    // =====================================================================
    txtStartValue.setText(strSENTbackFROMPageoneController);
    if(intSETonStartController == 1){
      txtStartValue.setText(str);  
    }
    System.out.println("== doGET WAS RUN ==");
    if(txtStartValue.getText() == null){
       txtStartValue.setText("");   
    }
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    // This Method runs every time startController is LOADED
     doGET();
}    
}

控制器

public class PageoneController implements Initializable {

@FXML Pane startPane,pageonePane,pagetwoPane;
@FXML Button btnOne,btnTwo;
@FXML TextField txtPageOneValue;
public static String strSENTbackFROMPageoneController;
public Stage stage;

    @FXML
private void onBTNONE() throws IOException{

        stage = (Stage)pageonePane.getScene().getWindow();// pane you are ON
        pagetwoPane = FXMLLoader.load(getClass().getResource("pagetwo.fxml"));// pane you are GOING TO
        Scene scene = new Scene(pagetwoPane);// pane you are GOING TO
        stage.setScene(scene);
        stage.setTitle("Page Two"); 
        stage.show();
        stage.sizeToScene();
        stage.centerOnScreen();
}

@FXML
private void onBTNTWO() throws IOException{
    if(intSETonStartController == 2){
        Alert alert = new Alert(AlertType.CONFIRMATION);
        alert.setTitle("Alert");
        alert.setHeaderText("YES to change Text Sent Back");
        alert.setResizable(false);
        alert.setContentText("Select YES to send 'Alert YES Pressed' Text Back\n"
                + "\nSelect CANCEL send no Text Back\r");// NOTE this is a Carriage return\r
        ButtonType buttonTypeYes = new ButtonType("YES");
        ButtonType buttonTypeCancel = new ButtonType("CANCEL", ButtonData.CANCEL_CLOSE);

        alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);

        Optional<ButtonType> result = alert.showAndWait();
        if (result.get() == buttonTypeYes){
            txtPageOneValue.setText("Alert YES Pressed");
        } else {
            System.out.println("canceled");
            txtPageOneValue.setText("");
            onBack();// Optional
        }
    }
}

@FXML
private void onBack() throws IOException{

    strSENTbackFROMPageoneController = txtPageOneValue.getText();
    System.out.println("Text Returned = "+strSENTbackFROMPageoneController);
    stage = (Stage)pageonePane.getScene().getWindow();
    startPane = FXMLLoader.load(getClass().getResource("start.fxml")); 
    Scene scene = new Scene(startPane);
    stage.setScene(scene);
    stage.setTitle("Start Page"); 
    stage.show();
    stage.sizeToScene();
    stage.centerOnScreen(); 
}

private void doTEST(){
    String fromSTART = String.valueOf(intSETonStartController);
    txtPageOneValue.setText("SENT  "+fromSTART);
    if(intSETonStartController == 1){
       btnOne.setVisible(true);
       btnTwo.setVisible(false);
       System.out.println("INTEGER Value Entered = "+intSETonStartController);  
    }else{
       btnOne.setVisible(false);
       btnTwo.setVisible(true);
       System.out.println("INTEGER Value Entered = "+intSETonStartController); 
    }  
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    doTEST();
}    

}

第二页控制器

public class PagetwoController implements Initializable {

@FXML Pane startPane,pagetwoPane;
public Stage stage;
public static String str;

@FXML
private void toStart() throws IOException{

    str = "You ON Page Two";
    stage = (Stage)pagetwoPane.getScene().getWindow();// pane you are ON
    startPane = FXMLLoader.load(getClass().getResource("start.fxml"));// pane you are GOING TO
    Scene scene = new Scene(startPane);// pane you are GOING TO
    stage.setScene(scene);
    stage.setTitle("Start Page"); 
    stage.show();
    stage.sizeToScene();
    stage.centerOnScreen();  
}

@Override
public void initialize(URL url, ResourceBundle rb) {

}    

}

Below are all the FXML files <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.text.Font?> <AnchorPane id="AnchorPane" fx:id="pagetwoPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PagetwoController"> <children> <Button layoutX="227.0" layoutY="62.0" mnemonicParsing="false" onAction="#toStart" text="To Start Page"> <font> <Font name="System Bold" size="18.0" /> </font> </Button> </children> </AnchorPane>

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.text.Font?> <AnchorPane id="AnchorPane" fx:id="startPane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.startController"> <children> <Label focusTraversable="false" layoutX="115.0" layoutY="47.0" text="This is the Start Pane"> <font> <Font size="18.0" /> </font> </Label> <Button fx:id="btnPageOne" focusTraversable="false" layoutX="137.0" layoutY="100.0" mnemonicParsing="false" onAction="#toPageOne" text="To Page One"> <font> <Font size="18.0" /> </font> </Button> <Label focusTraversable="false" layoutX="26.0" layoutY="150.0" text="Enter 1 OR 2"> <font> <Font size="18.0" /> </font> </Label> <TextField fx:id="txtStartValue" layoutX="137.0" layoutY="148.0" prefHeight="28.0" prefWidth="150.0" /> </children> </AnchorPane>

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.text.Font?> <AnchorPane id="AnchorPane" fx:id="pageonePane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PageoneController"> <children> <Label focusTraversable="false" layoutX="111.0" layoutY="35.0" text="This is Page One Pane"> <font> <Font size="18.0" /> </font> </Label> <Button focusTraversable="false" layoutX="167.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBack" text="BACK"> <font> <Font size="18.0" /> </font></Button> <Button fx:id="btnOne" focusTraversable="false" layoutX="19.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNONE" text="Button One" visible="false"> <font> <Font size="18.0" /> </font> </Button> <Button fx:id="btnTwo" focusTraversable="false" layoutX="267.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNTWO" text="Button Two"> <font> <Font size="18.0" /> </font> </Button> <Label focusTraversable="false" layoutX="19.0" layoutY="152.0" text="Send Anything BACK"> <font> <Font size="18.0" /> </font> </Label> <TextField fx:id="txtPageOneValue" layoutX="195.0" layoutY="150.0" prefHeight="28.0" prefWidth="150.0" /> </children> </AnchorPane>

下面是一个使用Guice注入的控制器的例子。

/**
 * Loads a FXML file and injects its controller from the given Guice {@code Provider}
 */
public abstract class GuiceFxmlLoader {

   public GuiceFxmlLoader(Stage stage, Provider<?> provider) {
      mStage = Objects.requireNonNull(stage);
      mProvider = Objects.requireNonNull(provider);
   }

   /**
    * @return the FXML file name
    */
   public abstract String getFileName();

   /**
    * Load FXML, set its controller with given {@code Provider}, and add it to {@code Stage}.
    */
   public void loadView() {
      try {
         FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName()));
         loader.setControllerFactory(p -> mProvider.get());
         Node view = loader.load();
         setViewInStage(view);
      }
      catch (IOException ex) {
         LOGGER.error("Failed to load FXML: " + getFileName(), ex);
      }
   }

   private void setViewInStage(Node view) {
      BorderPane pane = (BorderPane)mStage.getScene().getRoot();
      pane.setCenter(view);
   }

   private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class);

   private final Stage mStage;
   private final Provider<?> mProvider;
}

下面是加载器的具体实现:

public class ConcreteViewLoader extends GuiceFxmlLoader {

   @Inject
   public ConcreteViewLoader(Stage stage, Provider<MyController> provider) {
      super(stage, provider);
   }

   @Override
   public String getFileName() {
      return "my_view.fxml";
   }
}

注意这个例子将视图加载到BoarderPane的中心,BoarderPane是Stage中Scene的根。这与示例(我的特定用例的实现细节)无关,但决定保留它,因为有些人可能会发现它有用。

使用MVC

这个回答主要集中在将参数从调用类传递给控制器的直接调用上。

如果相反,你想要解耦调用者和控制器,并使用一个更通用的架构,包括一个具有可设置和可侦听属性的模型类来实现控制器之间的通信,请参阅以下基本概述:

使用JavaFx应用MVC

推荐的方法

这个答案列举了向FXML控制器传递参数的不同机制。

对于小型应用程序,我强烈建议直接将参数从调用者传递给控制器——这很简单,直接,不需要额外的框架。

对于更大、更复杂的应用程序,如果您想在应用程序中使用依赖注入或事件总线机制,那么值得研究一下。

直接从调用者向控制器传递参数

通过从FXML加载器实例检索控制器并调用控制器上的方法以使用所需的数据值初始化控制器,将自定义数据传递给FXML控制器。

类似于下面的代码:

public Stage showCustomerDialog(Customer customer) {
  FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
      "customerDialog.fxml"
    )
  );

  Stage stage = new Stage(StageStyle.DECORATED);
  stage.setScene(
    new Scene(loader.load())
  );

  CustomerDialogController controller = loader.getController();
  controller.initData(customer);

  stage.show();

  return stage;
}

...

class CustomerDialogController {
  @FXML private Label customerName;
  void initialize() {}
  void initData(Customer customer) {
    customerName.setText(customer.getName());
  }
}

一个新的FXMLLoader被构造,如示例代码所示,即new FXMLLoader(location)。该位置是一个URL,您可以通过以下方式从FXML资源生成这样一个URL:

new FXMLLoader(getClass().getResource("sample.fxml"));

注意不要在FXMLLoader上使用静态加载函数,否则您将无法从加载器实例中获得控制器。

FXMLLoader实例本身从来不知道任何关于域对象的信息。你不直接传递应用程序特定的域对象到FXMLLoader构造函数,而是:

在指定位置根据fxml标记构造一个FXMLLoader 从FXMLLoader实例获取一个控制器。 调用检索到的控制器上的方法,为控制器提供对域对象的引用。

这篇博客(由另一位作者撰写)提供了一个类似的替代例子。

在FXMLLoader上设置控制器

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

FXMLLoader loader = new FXMLLoader(
    getClass().getResource(
        "customerDialog.fxml"
    )
);
loader.setController(dialogController);

Pane mainPane = loader.load();

你可以在代码中构造一个新的控制器,把你想从调用者那里得到的任何参数传递给控制器构造函数。一旦构建了控制器,就可以在调用load()实例方法之前在FXMLLoader实例上设置它。

要在加载器上设置控制器(在JavaFX 2.x中),你不能在你的fxml文件中定义fx:controller属性。

由于FXML中fx:controller定义的限制,我个人更喜欢从FXMLLoader中获取控制器,而不是将控制器设置到FXMLLoader中。

让控制器从外部静态方法检索参数

Sergey在Controller.java文件中对Javafx 2.0 How-to Application.getParameters()的回答举例说明了这种方法。

使用依赖注入

FXMLLoader支持依赖注入系统,如Guice, Spring或Java EE CDI,允许你在FXMLLoader上设置自定义控制器工厂。这提供了一个回调,您可以使用该回调创建控制器实例,其中包含由各自的依赖注入系统注入的依赖值。

下面给出了一个JavaFX应用程序和控制器依赖注入Spring的示例:

在JavaFX中添加Spring依赖注入(JPA Repo, Service)

加力是一种非常好的、干净的依赖注入方法。Fx框架与示例air-hacks应用程序使用它。加力燃烧室。fx依赖于JEE6 javax。注入以执行依赖项注入。

使用事件总线

Greg Brown是FXML规范的最初创建者和实现者,他经常建议考虑使用事件总线,例如Guava EventBus,用于FXML实例化控制器和其他应用程序逻辑之间的通信。

EventBus是一个简单但功能强大的带有注释的发布/订阅API,它允许pojo在JVM中的任何地方相互通信,而不必相互引用。

后续的问答

关于第一种方法,你为什么返回舞台?这个方法也可以是空的,因为你已经给出了show()命令;在返回阶段之前;。如何通过返回Stage来计划使用

It is a functional solution to a problem. A stage is returned from the showCustomerDialog function so that a reference to it can be stored by an external class which may wish to do something, such as hide the stage based on a button click in the main window, at a later time. An alternate, object-oriented solution could encapsulate the functionality and stage reference inside a CustomerDialog object or have a CustomerDialog extend Stage. A full example for an object-oriented interface to a custom dialog encapsulating FXML, controller and model data is beyond the scope of this answer, but may make a worthwhile blog post for anybody inclined to create one.


StackOverflow用户@ zim提供的附加信息

Spring引导依赖注入示例

关于如何做到“Spring Boot Way”的问题,有一个关于JavaFX 2的讨论,我在附件中回答了这个问题。 该方法仍然有效,并在2016年3月Spring Boot v1.3.3.RELEASE上进行了测试: https://stackoverflow.com/a/36310391/1281217


有时,你可能想要将结果返回给调用者,在这种情况下,你可以检查出相关问题的答案:

JavaFX FXML从控制器A传递到控制器B并返回的参数

下面是一个通过命名空间将参数传递给fxml文档的示例。

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
    <BorderPane>
        <center>
            <Label text="$labelText"/>
        </center>
    </BorderPane>
</VBox>

为命名空间变量labelText定义值External Text:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class NamespaceParameterExampleApplication extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));

        fxmlLoader.getNamespace()
                  .put("labelText", "External Text");

        final Parent root = fxmlLoader.load();

        primaryStage.setTitle("Namespace Parameter Example");
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }
}