将参数传递给嵌套控制器

问题描述 投票:0回答:2

我想问一下程序结构。

I have ControllerStatisticFXMLStatistic有关,我在其中定义了TabPane

initializeControllerStatistic,我每个月都会添加标签。每个标签都包含FXMLTableViewMonthlyControllerMonthly。在ControllerMonthly我想用每天的行填充表格。我有来自静态字段的月份信息:

private static int countControllers = 0;

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

我在initialize填写表格。

这有效,但我认为这不是正确的方法。


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

我在这里看到2个选项:

ControllerStatistic我从加载器得到控制器并设置月,然后在ControllerMonthly我不能填充initialize(月场为空)所以我需要在设置月场后填充ControllerStatistic

我也可以从FXML中删除fx:controller,并在@jewelsea的Passing Parameters JavaFX FXML中描述的代码中构建一个新的控制器(他提到他不喜欢那个解决方案)。然后我想我可以在ControllerMonthlyinitialize居住。

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

这该怎么做?

java javafx fxml
2个回答
1
投票

那么没有一般的坏或好。这取决于您的用例/设计和品味。

让我们先看看其他没有fxml的FX - 元素,以及如何填充它们,以便走上正确的轨道。以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创建的某些类中设置数据没有任何问题。是的,在这种情况下,你不会在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,这当然会失败。该设置可能永远不会为空。通常,如果您可以/想要重新设置值,则无论如何都需要处理该情况,因此您的类应该能够使用settings = null进行处理,因为如果您重新设置它可能会发生。所以你必须在某处检查它并确保你没有得到nullpointer。同样适用于大小字段 - 如果之前没有设置它会显示默认值 - 但这可能是你想要的非常好。

有时(取决于)我觉得一个单独的工厂是一个不必要的额外课程,而是希望在一个课程中将所有东西放在一起。

为此,我有一个简单的基类:

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

这里初始化只记住resourcebundle和url,以便我以后可以使用它。没有其他的。

我设置控制器与loader.setControllerFactory(p-> this)而不是loader.setController(this)有一个原因:我可以自动创建/更新控制器的Java代码。如果在fxml中设置了cotroller,则IDE可以从fxml自动创建/更新控制器中的字段。如果在fxml中设置了控制器,则无法在代码中明确地设置它。所以这更方便我的解决方法。

如果不是因为我更喜欢用loader.setController设置控制器(这个);

另外我不检查在p中传递的类:“loader.setControllerFactory(p - > this);” - 您可能希望这样做,因为如果fxml与控制器不匹配(错误的类),它将失败。但我宁愿让它失败,如果出现问题(控制器的错误fxml)而是默默地继续。因此,错误消息告诉我使用错误的控制器对我来说是可以接受的。更多:如果您有嵌套控制器,它也会失败 - 在这种情况下,您当然想要检查类并返回适当的控制器 - 而在这种情况下,imo宁可使用真正的工厂。

现在从该基类我派生出一个特定的控制器类:

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

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

注意传递给构造函数的“Session”对象(无论它可能是什么数据 - 你将拥有你的数据类型)。它作为参考存储在对话框中。因此,来自外部的数据发生的任何变化都会直接反映在对话框中。

例如,如果它是一个Observable,你可以将对话框的元素绑定到它 - 这将总是反映该数据的状态。 如果它是一个ObservableList,你可以在它的对话框中填充ListView,每当更改ObservableList时,ListView将反映列表的状态。对于TableView也是如此(例如,我在HashMaps中填充了在其他地方创建并填充/更新的TableViews。)。因此,模型,视图和控制器的分离成为可能。

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

现在我想用它:

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

或者,如果我不需要该对象:

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

最后的评论:我没有你的控制器,所以我不打算创建现场的例子。我使用舞台因为它很容易重现,但使用Tabs和TableViews并不是完全不同的。此外,我没有试图给你所有的方法 - 你在你的链接问题中有它们。我试着给出一些例子,说明在现实世界的应用程序中可能会出现不同的aproaches和场景,以及示例中可能会发生什么 - 希望触发一些正在发生的事情,并表明存在权衡不止两种方式。祝好运!


1
投票

你也可以为你的ControllerFactory重新定义FXMLLoader。就像是:

    loader.setControllerFactory((Class clazz) -> {
        if (clazz.isAssignableFrom(SomeClass.class)) {
            return new SomeClass(getMonthNumber());
        } else {
            return clazz.newInstance();
        }
    });
© www.soinside.com 2019 - 2024. All rights reserved.