将参数传递给嵌套控制器

Passing parameter to nested controller

我想问一下程序结构

I have ControllerStatisticFXMLStatistic 相关,我在其中定义 TabPane.

initializeControllerStatistic 我每个月都添加标签。每个选项卡都包含 FXMLTableViewMonthlyControllerMonthly。在 ControllerMonthly 中,我想用每个月的每一天的行填充 table。我有来自静态字段的月份信息:

private static int countControllers = 0;

public ControllerUdostepnianieNaZewnatrz() {
    countControllers++;
    monthNumber = countControllers;
}

我在 initialize 中填充 table。

这可行,但我认为这不是正确的方法。


我想将 ControllerStatistic 中的月份参数传递给 ControllerMonthly

我在这里看到 2 个选项:

ControllerStatistic 中我从加载器获取控制器并设置月份,然后在 ControllerMonthly 中我无法在 initialize 中填充(月份字段为空)所以我需要在 ControllerStatistic 设置月份字段后。

我还可以从 FXML 中删除 fx:controller 并按照@jewelsea Passing Parameters JavaFX FXML 中所述在代码中构建一个新控制器(他提到他不喜欢该解决方案)。然后我想我可以填充 ControllerMonthly in initialize.

我选择使用第二种方法。首先对我来说很糟糕(在设置月份后填充 - 解决方案看起来会导致很多错误)。

如何操作?

您还可以为 FXMLLoader 重新定义 ControllerFactory。类似于:

    loader.setControllerFactory((Class clazz) -> {
        if (clazz.isAssignableFrom(SomeClass.class)) {
            return new SomeClass(getMonthNumber());
        } else {
            return clazz.newInstance();
        }
    });

嗯,没有一般的好坏之分。这取决于您的 usecase/design 和品味。

让我们先看看其他 FX - 没有 fxml 的元素,以及如何填充它们,以走上正确的轨道。以 AnchorPane 为例。首先创建它,创建后用其他元素填充它。当你完成后,你展示了整个事情。您不要覆盖 AnchorPane 中的某些 initialize() 方法:

public void createAStage(String foo){ 
     AnchorPane pane = new AnchorPane();
     Stage stage = new Stage();
     Scene scene = new Scene(pane);
     stage.setScene(scene);
     //here we populate the pane with a Label
     //and set that Label again to some value that was passed to this method(foo):
     pane.getChildren().add(new Label(foo));
     stage.show();
}

这样做没有错。因此,在调用 initialize() 后从 fxml 创建的某些 class 中设置数据没有任何问题。是的,在这种情况下,您不会在 initialize() 中填充,而是从工厂外部填充 - 但那又怎样?

有时我需要在创建对话框后不时地(重新)设置值。所以我为此创建了一个方法。有了那个方法我用它来填充它:

public class DialogController implements Initializable {     
    @FXML
    private AnchorPane dialog;
    @FXML
    private Label lb_size;
    private Setting settings = null;
    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {}

    public void setSettings(Settings settings, int size) {
        this.settings = settings;
        this.lb_size.setText("" + size);
    }
}

然后我构造它:

public DialogController createDialog(Settings settings, int size){
      final FXMLLoader loader = new FXMLLoader(FXMLLoader.class.getResource("/fxml/afxm.fxm"));
      try {
            final Stage stage = new Stage();
            stage.setScene(new Scene(loader.load()));
            final DialogController controller = loader.getController();
            controller.setSettings(settings,size);
            stage.show();
            return controller;
       } catch (IOException ex) {
            throw new InternalApplicationError("Resource missing", ex);
       }
  }

现在,每当我需要将设置设置为其他内容时,我都会调用:

controller.setSettings(settings,size);

如果有约束 rg,这当然会失败。该设置可能永远不会为空。通常,如果您 can/want 重新分配值,您无论如何都需要处理这种情况,因此您的 class 应该能够处理使用 settings=null 构建,因为如果您重新设置它可能会发生这种情况。所以你必须在某个地方检查它并确保你没有空指针。大小字段也是如此 - 如果在显示之前未设置它,它将显示默认值 - 但这可能正是你想要的。

有时(视情况而定)我觉得一个单独的工厂是一个不必要的附加 class 而我更想把东西放在一起 class.

为此我有一个简单的基础class:

public class FXMLStage extends Stage implements Initializable {

    protected URL url = null;
    protected ResourceBundle resourceBundle = null;

    @SuppressWarnings("LeakingThisInConstructor")
    public FXMLStage(String filename) {
        final FXMLLoader loader = new FXMLLoader(FXMLLoader.class.getResource(filename));
        try {
            loader.setControllerFactory(p -> this);
            this.setScene(new Scene(loader.load()));
        } catch (IOException ex) {
            throw new InternalApplicationError("Resource missing", ex);
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.url = url;
        this.resourceBundle = rb;
    }
}

此处初始化仅记住资源包和 url,以便稍后使用。没有别的。

我用 loader.setControllerFactory(p->this) 而不是 loader.setController(this) 设置控制器,原因只有一个:我可以 create/update Java 代码自动为控制器。如果在 fxml 中设置了控制器,IDE 能够从 fxml 自动从控制器中 create/update 字段。如果在 fxml 中设置了控制器,则无法在代码中明确设置。因此,为了方便起见,这更像是一种解决方法。

如果不是这样的话,我宁愿使用 loader.setController(this) 来简单地设置控制器;

此外,我不检查在 p: "loader.setControllerFactory(p -> this);" 中传递的 class - 你可能想要这样做,因为如果 fxml 与控制器不匹配,它当然会失败(错误 class)。但我宁愿希望它在出现问题时失败(控制器的 fxml 错误)而不是默默地继续。因此,一条错误消息告诉我我使用了错误的控制器对我来说是可以接受的。 更重要的是:如果你有嵌套控制器,它也会失败 - 在这种情况下你当然想检查 class 和 return 适当的控制器 - 在这种情况下我宁愿使用一个真正的工厂。

现在从那个基础class我得到一个特定的控制器class:

public class SampleDialog extends FXMLStage {
     @FXML
     private AnchorPane dialog;
     @FXML
     private Label lb_size; 
     //....
     //some additional fields to initialize... 
     private final Session session; 
     public  SampleDialog(Session session, int size) {
        super("/fxml/SampleDialog.fxml");
        //initialize aditional fields:
        this.session=session;
        lb_size.setText("" + size);
    }
}

所以我可以直接在构造函数中进行初始化 - 我认为这是初始化 class 字段的好地方。所以它根本不会覆盖 initialize() 。

注意传递给构造函数的 "Session" 对象(不管它是什么数据 - 您将拥有您的数据类型)。它作为引用存储在对话框中。因此,无论外部数据发生什么变化,都会直接反映在对话框中。

例如,如果它是一个 Observable,您可以将对话框的元素绑定到它 - 这将始终反映该数据的状态。
如果它是一个 ObservableList,您可以用它在对话框中填充一个 ListView,并且每当 ObservableList 发生更改时,ListView 都会反映列表的状态。与 TableView 相同(例如,我从创建的 HashMap 和其他地方的 populated/updated 填充 TableView。)。 所以模型、视图和控制器的分离成为可能。

只记住 initialize() 的特殊用途。这是施工过程的一部分!因此,如果您覆盖它,则在调用它时可能并非所有字段都已初始化,因此如果您尝试使用这些未初始化的字段之一,它可能会失败。这就是 inizialize() 方法的全部内容:初始化未初始化的字段及其名称应该给你公平的警告。

现在我想使用它:

SampleDialog dialog = new SampleDialog(session,5);
dialog.show();

或者如果我不需要对象:

new SampleDialog(session,5).show();

最后的评论:我没有你的控制器,所以我无法创建现场的例子。我使用了一个舞台,因为它很容易重现,但使用 Tabs 和 TableViews 并没有根本上的不同。此外,我没有尝试为您提供所有类型的方法 - 您在链接的问题中有它们。我试图给出一些例子,说明不同的方法和场景在现实世界的应用程序中可能看起来如何,以及示例中可能发生的事情——希望引发一些关于正在发生的事情的想法,并表明存在一个权衡不止两种方式。祝你好运!