拆分JavaFX控制器类

3
我没有MCV代码示例,因为问题更加全局化,我认为具体的代码实际上并没有太大的区别。我的GUI应用程序有几个元素(日期选择器、下拉菜单、输入字段、表格树视图),依据JavaFX有一个控制器类,随着时间的推移变得越来越大。
因此,我正在寻找一种方法来拆分它。在我的研究中,我发现了两种主要的方法:
将更多的程序逻辑从UI部分中取出,这当然总是有意义的,我已经为某些部分做到了这一点,但对于许多部分来说,这似乎相当不方便,例如如果我有一个使用六个不同的GUI元素的函数,将该方法从控制器类中提取出来似乎很奇怪,因为我需要在函数调用中给它所有这些GUI元素的引用。我是否遗漏了一些更好的默认方式?像通过设置/获取器填充控制器类并将每个方法作为参数传递给this,以便可以进行this.dropbox1
第二种方法是将GUI本身分成几个场景。就像这个问题一样,但我不知道如何将一个GUI分成几个场景,仍然在一个窗口中。我确实意识到可以在主FXML文件中加载其他FXML文件,但我不确定如何将它们组合成一个窗口的GUI。如果有人有这方面的小示例代码,我会很感激。

思考“模型”而非“函数”或“UI”:用户操作的数据具有固有的逻辑/关系,必须在一个或多个数据类中进行建模,这些类可以移动,视图只需监听数据属性的变化或通过数据对象上的专用API更改它们。 - kleopatra
MVC概念对于非高级程序员来说可能是具有挑战性的。我仍在努力掌握它的过程中。@James_D提供的这个例子是一个很好的例子,它给出了一个简单的示例,帮助开发人员理解MVC的一种形式。https://dev59.com/MFwY5IYBdhLWcg3wq5bk - SedJ601
假设您有一个包含10个选项卡的TabbedPane。每个选项卡都包含不同的UI。在这种情况下,您不必通过一个控制器来控制几个选项卡下的每个UI元素。您将创建10个单独的fxml文件,每个文件都有一个控制器类,然后在用户更改选项卡时加载每个fxml文件。通过这种方式,您不会得到一个巨大的控制器。 - Umer Farooq
2个回答

1
欢迎来到Stackoverflow Labib,关于拆分问题:
您可以有一个根FXML,其中包含例如仅GridPane(或其他内容,但我将继续使用GridPane前提),现在您在该根FXML文件中设置网格(无内容),如果您已经有一个完成的GUI,则应该清楚地知道网格单元格需要多大。 在您的主类中,您有: 扩展Application并启动GUI的主类
public class GUI extends Application {

    private Stage stage;
    private GridPane rootGrid;

    @Override
    public void start(Stage stage) {

        this.stage = stage;

        // Load root layout from fxml file.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(WindowRoot.class.getResource("RootGrid.fxml"));
        rootGrid = (GridPane) loader.load();

        // Show the scene containing the root layout.
        Scene scene = new Scene(rootGrid);
        stage.setTitle("Test");
        stage.show();
        //fill the Grid
        setupGrid();
    }

现在你有一个名为rootGrid的类属性,它只是一个带有空行/列的网格,如在RootGrid.fxml中设置,可以非常简单,例如:

FXML示例,两行两列,没有内容

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>


<GridPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141">
   <rowConstraints>
      <RowConstraints minHeight="10.0" prefHeight="30.0" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" />
   </rowConstraints>
   <columnConstraints>
      <ColumnConstraints minWidth="10.0" prefWidth="100.0" />
      <ColumnConstraints minWidth="10.0" prefWidth="100.0" />
   </columnConstraints>
</GridPane>

现在,您需要为每个要填充的单元格创建FXML文档,在该FXML文档中,您需要使用一个AnchorPane来包含该单元格的内容。然后,将其作为start方法的一部分添加到Grid中。假设您想要在这个简单的例子中添加一个菜单栏。请创建一个用于菜单的FXML文件:

简单菜单FXML示例:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141">
   <children>
      <MenuBar>
        <menus>
          <Menu mnemonicParsing="false" text="File">
            <items>
              <MenuItem mnemonicParsing="false" text="Close" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
   </children>
</AnchorPane>

将其作为 start 方法的一部分添加到网格中(但可能需要将其提取为一个方法,您在 start 中调用它,例如 setupGrid()):

设置网格,在 start(Stage stage) 中调用

private void setupGrid() {
    // Add Menubar
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(WindowRoot.class.getResource("menubar.fxml"));
    AnchorPane anchor = (AnchorPane) loader.load();
    //put it in first column, first row
    GridPane.setConstraints(anchor, 0, 0);
    //optional, let it span both/all columns
    GridPane.setRowSpan(anchor, 2);
    rootGrid.getChildren().add(anchor);

您需要使用try/except IO块,为了更好的可读性,可以将它们省略。对于其他的"Root-Panes",您需要使用其他的设置方法,例如使用borderPane时,您需要类似rootGrid.setTop(anchor)这样的语句。
希望这有所帮助,祝您愉快。

谢谢您提供这么详细的答案,很好地解释了FXML之间的分离,我已经按照这种方式完成了。只是在它们之间的通信方面遇到了一些问题。 - Labib Kalil Shamon

1
如果您使用FXML,简单的方法是为每个FXML创建一个控制器(一对一)。我个人还会添加CSS,这是一种模式。
然后您需要将屏幕分成小部分:有两种方法:
- 使用fx:include:您可以包含一个声明了控制器的FXML。 - 使用组件:您可以包含您组件的标记,它本身可以加载FXML和控制器。
接下来,您需要在控制器之间进行通信。最好的方法是拥有一个模型层,并通过singleton共享它,例如使用afterburnerfx(一个非常小的注入器)。
当使用fx:include时,您还可以使用“嵌套控制器”从父级获取子级控制器的引用。
最后,您的对象可能是具有observable的JavaFX Bean,并且您仅向其他控制器公开observable而不是可视组件:这样您就可以在组件之间实现松散耦合。

单例模式的建议听起来像我曾经想过的一个主意,即将控制器引用放在静态类中(尽管我实际上没有考虑过单例模板)。我研究了afterburnerfx框架,但不确定它会如何帮助。我的当前结构类似于@Bernhard提出的建议。 - Labib Kalil Shamon
AfterburnerFx非常简单,只需在控制器属性上使用@Inject注释,指向您的单例,并在控制器和服务的构造函数中调用Injector的方法,例如对于服务构造函数使用Injector.registerExistingAndInject(this),对于控制器构造函数使用Injector.injectMembers(getClass(),this)。优点是自动化您的服务实例化和递归实例化(如果一个服务使用另一个服务)。 - pdem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接