传递参数JavaFX FXML

问题描述 投票:169回答:10

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

有任何想法吗?

javafx parameters dependency-injection parameter-passing fxml
10个回答
245
投票

推荐方法

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

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

对于更大,更复杂的应用程序,如果您想在应用程序中使用Dependency InjectionEvent Bus机制,那么值得研究一下。

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

通过从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构造函数,而是:

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

这个博客(由另一位作家提供)提供了另一种类似的example

在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。

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

这种方法的例子是谢尔盖对Javafx 2.0 How-to Application.getParameters() in a Controller.java file的回答。

使用依赖注入

FXMLLoader支持依赖注入系统,如Guice,Spring或Java EE CDI,允许您在FXMLLoader上设置自定义控制器工厂。这提供了一个回调,您可以使用该回调来创建具有相应依赖注入系统注入的依赖值的控制器实例。有一个集成FXML with the Spring dependency injection system的样本(不幸的是链接已经死了,内容已经消失,如果有人知道类似的例子,请编辑这个问题以引用它),虽然它比使用新的自定义控制器有点笨拙工厂功能在JavaFX 2.2中提供。

一个非常好,干净的依赖注入方法的例子是afterburner.fx framework与使用它的样本air-hacks application。 afterburner.fx依赖于JEE6 javax.inject来执行依赖注入。

使用事件总线

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

EventBus是一个简单但功能强大的发布/订阅API,带有注释,允许POJO在JVM中的任何位置相互通信,而无需相互引用。

后续问答

在第一种方法上,你为什么要回归舞台?该方法也可以是空的,因为你已经给出了命令show();就在返回阶段之前;你如何通过返回舞台来规划用法

它是解决问题的功能性解决方案。从showCustomerDialog函数返回一个阶段,以便可以由外部类存储对它的引用,该外部类可能希望执行某些操作,例如在稍后的时间基于主窗口中的按钮单击来隐藏阶段。另一种面向对象的解决方案可以将功能和阶段引用封装在CustomerDialog对象中,或者具有CustomerDialog扩展阶段。封装FXML,控制器和模型数据的自定义对话框的面向对象接口的完整示例超出了本答案的范围,但可能会为任何倾向于创建一个的人发布一篇有价值的博客文章。


StackOverflow用户提供的附加信息,名为@dzim

Spring Boot依赖注入的示例

关于如何做到这一点的问题“春季引导方式”,有一个关于JavaFX 2的讨论,我在附加的永久链接中对此进行了回答。该方法仍然有效,并于2016年3月在Spring Boot v1.3.3上进行了测试.RELEASE:https://stackoverflow.com/a/36310391/1281217


有时,您可能希望将结果传递回调用者,在这种情况下,您可以查看相关问题的答案:


-1
投票

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


9
投票

我意识到这是一篇非常古老的帖子,并且已经有了一些很好的答案,但我想制作一个简单的MCVE来演示一种这样的方法,并让新编码员能够快速看到这个概念。

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

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

所有文件都在本文的底部完整列出。

目标:展示从Controller1Controller2的传球价值,反之亦然。

计划流程:

  • 第一个场景包含TextFieldButtonLabel。单击Button时,将加载并显示第二个窗口,包括在TextField中输入的文本。
  • 在第二个场景中,还有一个TextField,一个Button和一个LabelLabel将在第一个场景中显示在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>

8
投票

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

您可以使用它将您的信息添加到节点。

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

如果设置了信息,控制器可以检查。此外,如果需要,您可以使用ObjectProperty进行后向数据传输。

请在此处观察文档:http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html在短语“在第一个版本中,handleButtonAction()标记有@FXML以允许在控制器文档中定义的标记来调用它。在第二个示例中,按钮字段被注释为允许加载器设置它的值.initialize()方法同样被注释。“

因此,您需要将控制器与节点相关联,并将用户数据设置为节点。


7
投票

以下是通过命名空间将参数传递给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>

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

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

2
投票

这个工作..

记住第一次打印传递值时,您将获得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());

    }

1
投票

您必须创建一个Context类。

public class Context {
    private final static Context instance = new Context();
    public static Context getInstance() {
        return instance;
    }

    private Connection con;
    public void setConnection(Connection con)
    {
        this.con=con;
    }
    public Connection getConnection() {
        return con;
    }

    private TabRoughController tabRough;
    public void setTabRough(TabRoughController tabRough) {
        this.tabRough=tabRough;
    }

    public TabRoughController getTabRough() {
        return tabRough;
    }
}

您必须在初始化时使用设置控制器的实例

Context.getInstance().setTabRough(this);

你只需使用就可以在整个应用程序中使用它

TabRoughController cont=Context.getInstance().getTabRough();

现在您可以将参数传递给整个应用程序的任何控制器。


0
投票

以下是使用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是舞台中场景的根。这与示例(我的具体用例的实现细节)无关,但决定将其保留,因为有些人可能觉得它很有用。


0
投票

是的,您可以在第一个控制器中添加

    YourController controller = loader.getController();     
    controller.setclient(client);

然后在第二个声明一个客户端,然后在你的控制器的底部:

   public void setclien(Client c) {
    this.client = c;
    }

0
投票

我有一个更简单的方法,使用initialize方法创建类的实例。我在网上搜了几天,直到找到这个非常简单的方法。

声明您的类的实例:

//设置用于主类实例的变量

private static FXMLController instance;

按如下方式声明您的类:

public class FXMLController implements Initializable {

在您的类中设置一个方法如下:

public void mainController() {
    instance = this;
}

然后在initialize方法中调用该方法:

@Override // this method is ran when first showing the main window
public void initialize(URL location, ResourceBundle resources) {
     // initialize the instance of this class so it can be shared across controllers
     mainController();
}
© www.soinside.com 2019 - 2024. All rights reserved.