传递参数JavaFXFXML

javafx parameters dependency-injection parameter-passing fxml


如何在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作为参数传递)。

有什么想法吗?




Answer 1 jewelsea


推荐方法

本回答列举了向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(
      (Pane) loader.load()
    )
  );

  CustomerDialogController controller = 
    loader.<CustomerDialogController>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构造函数中,相反,你可以将域对象直接传递给FXMLLoader。

  1. 在指定的位置构建一个基于fxml标记的FXMLLoader。
  2. 从FXMLLoader实例中获取一个控制器。
  3. 在检索到的控制器上调用方法,为控制器提供域对象的引用。

该博客(由另一位作者撰写)提供了一个替代示例,但类似。

在FXMLLoader上设置控制器

CustomerDialogController dialogController = 
    new CustomerDialogController(param1, param2);

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

Pane mainPane = (Pane) 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应用程序和控制器依赖注入的例子。

afterburner.fx框架示例了一个很好的干净的依赖项注入方法,并使用了一个示例空袭应用程序。afterburner.fx依靠JEE6 javax.inject执行依赖项注入。

使用事件总线

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

EventBus是一个简单但功能强大的发布订阅API,它允许POJOs在JVM中的任何地方相互通信,而不需要相互引用。

后续问答

在第一个方法中,为什么要返回Stage?这个方法也可以是空的,因为你在返回Stage;之前已经给了show();命令。你如何通过返回Stage

这是一个解决问题的功能。从 showCustomerDialog 函数返回一个阶段,以便以后可能希望做一些事情的外部类可以存储对它的引用,例如,根据在主窗口中单击按钮隐藏该阶段。另一种面向对象的解决方案可以将功能和阶段引用封装在CustomerDialog对象中,或者具有CustomerDialog扩展阶段。一个自定义对话框的面向对象的界面的完整示例,该示例对话框封装了FXML,控制器和模型数据,超出了此答案的范围,但对于那些愿意创建一个的人来说,这可能是一个有价值的博客文章。


由StackOverflow用户@dzim提供的其他信息

Spring Boot依赖注入示例

关于如何做的问题“ Spring Boot Way”,有一个关于JavaFX 2的讨论,我在所附的永久链接中对此进行了解答。该方法仍然有效并已在2016年3月的Spring Boot v1.3.3。上进行了测试。发布:https ://stackoverflow.com/a/36310391/1281217


有时,你可能想把结果传回给来电者,这种情况下,你可以查看相关问题的答案。




Answer 2 Zephyr


我知道这是一个很老的帖子,已经有了一些很好的答案,但我想做一个简单的MCVE来演示一个这样的方法,让新的编纂人员能够快速看到这个概念的实际操作。

在这个例子中,我们将使用5个文件。

  1. Main.java-仅用于启动应用程序并调用第一个控制器。
  2. Controller1.java-第一个FXML布局的控制器。
  3. Controller2.java-第二个FXML布局的控制器。
  4. Layout1.fxml-第一个场景的FXML布局。
  5. Layout2.fxml-第二个场景的FXML布局。

所有文件均在此文章底部完整列出。

目标:演示将值从 Controller1 传递到 Controller2 ,反之亦然。

程序流程:

  • 第一个场景包含一个 TextField ,一个 Button 和一个 Label 。单击“ Button ,将加载并显示第二个窗口,包括在 TextField 中输入的文本。
  • 在第二个场景中,还有一个 TextField ,一个 Button 和一个 Label 。该 Label 将显示在输入文本 TextField 上的第一个场景。
  • 在第二个场景的 TextField 中输入文本并单击其 Button 时,第一个场景的 Label 将更新以显示输入的文本。

这是一个非常简单的演示,肯定可以站在一些改进,但应该让概念非常清晰。

代码本身也注释了一些细节的内容和方法。

编码

Main.java:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        // Create the first controller, which loads Layout1.fxml within its own constructor
        Controller1 controller1 = new Controller1();

        // Show the new stage
        controller1.showStage();

    }
}

Controller1.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller1 {

    // Holds this controller's Stage
    private final Stage thisStage;

    // Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
    @FXML
    private TextField txtToSecondController;
    @FXML
    private Button btnOpenLayout2;
    @FXML
    private Label lblFromController2;

    public Controller1() {

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout1");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    /**
     * The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
     */
    @FXML
    private void initialize() {

        // Add an action for the "Open Layout2" button
        btnOpenLayout2.setOnAction(event -> openLayout2());
    }

    /**
     * Performs the action of loading and showing Layout2
     */
    private void openLayout2() {

        // Create the second controller, which loads its own FXML file. We pass a reference to this controller
        // using the keyword [this]; that allows the second controller to access the methods contained in here.
        Controller2 controller2 = new Controller2(this);

        // Show the new stage/window
        controller2.showStage();

    }

    /**
     * Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
     */
    public String getEnteredText() {
        return txtToSecondController.getText();
    }

    /**
     * Allows other controllers to set the text of this layout's Label
     */
    public void setTextFromController2(String text) {
        lblFromController2.setText(text);
    }
}

Controller2.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller2 {

    // Holds this controller's Stage
    private Stage thisStage;

    // Will hold a reference to the first controller, allowing us to access the methods found there.
    private final Controller1 controller1;

    // Add references to the controls in Layout2.fxml
    @FXML
    private Label lblFromController1;
    @FXML
    private TextField txtToFirstController;
    @FXML
    private Button btnSetLayout1Text;

    public Controller2(Controller1 controller1) {
        // We received the first controller, now let's make it usable throughout this controller.
        this.controller1 = controller1;

        // Create the new stage
        thisStage = new Stage();

        // Load the FXML file
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));

            // Set this class as the controller
            loader.setController(this);

            // Load the scene
            thisStage.setScene(new Scene(loader.load()));

            // Setup the window/stage
            thisStage.setTitle("Passing Controllers Example - Layout2");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Show the stage that was loaded in the constructor
     */
    public void showStage() {
        thisStage.showAndWait();
    }

    @FXML
    private void initialize() {

        // Set the label to whatever the text entered on Layout1 is
        lblFromController1.setText(controller1.getEnteredText());

        // Set the action for the button
        btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
    }

    /**
     * Calls the "setTextFromController2()" method on the first controller to update its Label
     */
    private void setTextOnLayout1() {
        controller1.setTextFromController2(txtToFirstController.getText());
    }

}

Layout1.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToSecondController"/>
            <Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
        </HBox>
        <VBox alignment="CENTER">
            <Label text="Text From Controller2:"/>
            <Label fx:id="lblFromController2" text="Nothing Yet!"/>
        </VBox>
    </VBox>
</AnchorPane>

Layout2.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" spacing="10.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
        <VBox alignment="CENTER">
            <Label text="Text From Controller1:"/>
            <Label fx:id="lblFromController1" text="Nothing Yet!"/>
        </VBox>
        <HBox alignment="CENTER_LEFT" spacing="10.0">
            <Label text="Enter Text:"/>
            <TextField fx:id="txtToFirstController"/>
            <Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
        </HBox>
    </VBox>
</AnchorPane>



Answer 3 Alexander Kirov


javafx.sene.Node类有一对方法setUserData(Object)和Object getUserData()

你可以用它将你的信息添加到Node中。

所以,你可以调用page.setUserData(info)。

而且控制器可以检查,如果信息被设置了,就可以检查。另外,如果需要的话,你可以使用ObjectProperty进行数据的后向传输。

在此处查看文档:http : //docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html在短语“在第一个版本中,handleButtonAction()标记有@FXML允许在控制器文档中定义的标记调用它。在第二个示例中,对button字段进行了注释,以允许加载程序设置其值。对initialize()方法也进行了注释。

所以,你需要将一个控制器与一个节点关联,并将用户数据设置为节点。




Answer 4 user1503636


下面是一个通过命名空间向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();
    }
}



Answer 5 diego matos - keke


这个工作..

请记住,第一次打印传递的值时,您将获得null,您可以在Windows加载后使用它,对于要为任何其他组件编码的所有内容都一样。

首任主计长

try {
    Stage st = new Stage();
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));

    Parent sceneMain = loader.load();

    MainOnlineController controller = loader.<MainOnlineController>getController();
    controller.initVariable(99L);

    Scene scene = new Scene(sceneMain);
    st.setScene(scene);
    st.setMaximized(true);
    st.setTitle("My App");
    st.show();
} catch (IOException ex) {
    Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}

另一个主计长

public void initVariable(Long id_usuario){
    this.id_usuario = id_usuario;
    label_usuario_nombre.setText(id_usuario.toString());
}