将参数传递给嵌套控制器

时间:2019-03-07 08:55:47

标签: java javafx fxml

我想问一下程序的结构。

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

initialize的{​​{1}}中,我为每个月添加标签。每个标签都包含ControllerStatisticFXMLTableViewMonthly。在ControllerMonthly中,我想用每月每个月的行填充表。我有来自静态字段的月份信息:

ControllerMonthly

我在private static int countControllers = 0; public ControllerUdostepnianieNaZewnatrz() { countControllers++; monthNumber = countControllers; } 中填充表格。

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


我想将initialize中的month参数传递给ControllerStatistic

我在这里看到2个选项:

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

我还可以从FXML中删除initialize并按照@jewelsea的Passing Parameters JavaFX FXML中所述的代码构造新的控制器(他提到他不喜欢这种解决方案)。然后,我认为可以在ControllerStatistic的{​​{1}}中填充。

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

该怎么做?

2 个答案:

答案 0 :(得分:1)

您也可以将ControllerFactory重新定义为FXMLLoader。像这样:

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

答案 1 :(得分:1)

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

让我们看看其他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创建的某个类中设置数据没有任何问题。是的,在那种情况下,您不需要在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进行构造,因为如果您租赁它,可能会发生这种情况。因此,您必须在某处进行检查并确保没有空指针。相同的适用于size字段-如果未在显示之前设置它,它将显示默认值-但这可能是您想要的。

有时(取决于)我觉得一个单独的工厂是不必要的附加类,而是想将所有东西放在一个类中。

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

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(this);设置控制器简单。

我也不检查在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()。

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

例如,如果它是一个Observable,则可以将对话框的元素绑定到它-始终会反映该数据的状态。
如果它是一个ObservableList,则可以使用它来填充对话框中的ListView,并且每当ObservableList更改时,ListView就会反映列表的状态。与TableView相同(例如,我从HashMaps填充TableViews,这些HashMaps是在其他地方创建和填充/更新的。)。 这样就可以实现模型,视图和控制器的分离。

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

现在我要使用它:

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

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

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

最后一句话:我没有您的控制人,所以我不能创建一些例子。我使用了舞台,因为它很容易复制,但是使用Tabs和TableViews并没有什么不同。另外,我也没有尝试提供所有方法-在链接的问题中都包含了这些方法。我试图给出一些示例,说明在现实世界中的应用中不同的方式和场景是什么样的,以及示例中可能发生的情况-希望引发对正在发生的事情的一些了解,并表明存在权衡取舍远远超过两种方式。祝好运!