当前位置: 首页 > 知识库问答 >
问题:

在JavaFx中应用MVC

越霖
2023-03-14

我是GUI世界/OO设计模式的新手,我想在我的GUI应用程序中使用MVC模式,我读过一些关于MVC模式的教程,模型将包含数据,视图将包含可视元素,控制器将连接视图和模型。

我有一个包含ListView节点的视图,ListView将用来自Person类(模型)的名称填充。但有一件事我有点糊涂。

我想知道的是从文件加载数据是控制器的责任还是模型的责任??和名称的ObservableList:它应该存储在控制器中还是存储在模型中?

共有1个答案

公羊子真
2023-03-14

这种模式有许多不同的变体。特别地,web应用程序上下文中的“MVC”与厚客户端(例如桌面)应用程序上下文中的“MVC”的解释有些不同(因为web应用程序必须位于请求-响应循环的顶部)。这只是使用JavaFX在厚客户机应用程序上下文中实现MVC的一种方法。

您的Person类并不是真正的模型,除非您有一个非常简单的应用程序:这通常是我们所说的域对象,模型将包含对它的引用以及其他数据。在狭义的上下文中,例如当您只是考虑ListView时,您可以将Person视为您的数据模型(它对ListView的每个元素中的数据进行建模),但在应用程序的更广泛上下文中,需要考虑的数据和状态更多。

如果您显示的是ListView ,那么您需要的数据至少是ObservableList 。您可能还需要一个属性,如CurrentPerson,它可能表示列表中的选定项。

如果您拥有的唯一视图是ListView,那么创建一个单独的类来存储这个视图将是过分的,但是任何实际的应用程序通常都将以多个视图结束。在这一点上,在模型中共享数据成为不同控制器相互通信的一种非常有用的方式。

例如,您可能有如下内容:

public class DataModel {

    private final ObservableList<Person> personList = FXCollections.observableArrayList();

    private final ObjectProperty<Person> currentPerson = new SimpleObjectPropery<>(null);

    public ObjectProperty<Person> currentPersonProperty() {
        return currentPerson ;
    }

    public final Person getCurrentPerson() {
        return currentPerson().get();
    }

    public final void setCurrentPerson(Person person) {
        currentPerson().set(person);
    }

    public ObservableList<Person> getPersonList() {
        return personList ;
    }
}

现在,您可能有一个用于ListView显示的控制器,该控制器如下所示:

public class ListController {

    @FXML
    private ListView<Person> listView ;

    private DataModel model ;

    public void initModel(DataModel model) {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        this.model = model ;
        listView.setItems(model.getPersonList());

        listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> 
            model.setCurrentPerson(newSelection));

        model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
            if (newPerson == null) {
                listView.getSelectionModel().clearSelection();
            } else {
                listView.getSelectionModel().select(newPerson);
            }
        });
    }
}

该控制器基本上只是将列表中显示的数据绑定到模型中的数据,并确保模型的CurrentPerson始终是列表视图中的选定项。

现在,您可能有另一个视图,例如编辑器,其中有三个文本字段,分别用于人员的第一名最后名电子邮件属性。它的控制器可能看起来像:

public class EditorController {

    @FXML
    private TextField firstNameField ;
    @FXML
    private TextField lastNameField ;
    @FXML
    private TextField emailField ;

    private DataModel model ;

    public void initModel(DataModel model) {
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.model = model ;
        model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
            if (oldPerson != null) {
                firstNameField.textProperty().unbindBidirectional(oldPerson.firstNameProperty());
                lastNameField.textProperty().unbindBidirectional(oldPerson.lastNameProperty());
                emailField.textProperty().unbindBidirectional(oldPerson.emailProperty());
            }
            if (newPerson == null) {
                firstNameField.setText("");
                lastNameField.setText("");
                emailField.setText("");
            } else {
                firstNameField.textProperty().bindBidirectional(newPerson.firstNameProperty());
                lastNameField.textProperty().bindBidirectional(newPerson.lastNameProperty());
                emailField.textProperty().bindBidirectional(newPerson.emailProperty());
            }
        });
    }
}

现在,如果您设置这些东西,使这两个控制器共享相同的模型,编辑器将编辑列表中当前选定的项。

加载和保存数据应该通过模型完成。有时,您甚至会将此因素考虑到一个模型有引用的单独类中(例如,允许您轻松地在基于文件的数据加载器和数据库数据加载器之间切换,或者在访问web服务的实现之间切换)。在简单的情况下,你可以这样做

public class DataModel {

    // other code as before...

    public void loadData(File file) throws IOException {

        // load data from file and store in personList...

    }

    public void saveData(File file) throws IOException {

        // save contents of personList to file ...
    }
}

那么您可能有一个控制器提供对此功能的访问:

public class MenuController {

    private DataModel model ;

    @FXML
    private MenuBar menuBar ;

    public void initModel(DataModel model) {
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.model = model ;
    }

    @FXML
    public void load() {
        FileChooser chooser = new FileChooser();
        File file = chooser.showOpenDialog(menuBar.getScene().getWindow());
        if (file != null) {
            try {
                model.loadData(file);
            } catch (IOException exc) {
                // handle exception...
            }
        }
    }

    @FXML
    public void save() {

        // similar to load...

    }
}

现在您可以轻松组装应用程序:

public class ContactApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        BorderPane root = new BorderPane();
        FXMLLoader listLoader = new FXMLLoader(getClass().getResource("list.fxml"));
        root.setCenter(listLoader.load());
        ListController listController = listLoader.getController();

        FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("editor.fxml"));
        root.setRight(editorLoader.load());
        EditorController editorController = editorLoader.getController();

        FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("menu.fxml"));
        root.setTop(menuLoader.load());
        MenuController menuController = menuLoader.getController();

        DataModel model = new DataModel();
        listController.initModel(model);
        editorController.initModel(model);
        menuController.initModel(model);

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

正如我所说的,这种模式有许多变体(这可能更多地是一种模型-视图-演示器,或“被动视图”变体),但这是一种方法(我基本上赞成这种方法)。通过控制器的构造函数向控制器提供模型会更自然一些,但使用fx:controller属性定义控制器类会困难得多。这种模式也非常适合于依赖注入框架。

更新:这里是此示例的完整代码。

 类似资料:
  • 我有一个很大的swing应用程序,我想把javafx嵌入其中。我多次尝试这样做(通过遵循oracle教程等),但只有在声明一个新的JFrame以使用JFXPanel组件时才成功。但是,我不想使用新的框架,我想将我的Javafx代码合并到swing应用程序的根JFrame中。 我们可以将javaFX组件嵌入到JPanel而不是JFrame中吗?如果答案是肯定的,为什么我没有成功?

  • 我一直试图弄清楚如何在一个程序中同时使用两个FXML文件及其控制器,但发现很难找到一个简单的例子。请有人同时演示sample.fxml和sample1.fxml的用法,并将它们显示在其中。如果您能够以最简单、最容易的方式演示这一点,让一个新的Java和JavaFX程序员能够理解,我将非常高兴。多谢了。

  • 问题内容: 我有一个传统的Java swing应用程序(扩展了JFrame并具有一个主类),该应用程序使用JFreeCharts来实现某些图表功能。我最近看过JavaFX,并认为这些图表看上去很新鲜,可以为我的用户带来更好的体验。我希望将JavaFX图表场景嵌入到jInternalFrame中(依次从我的jDesktopPane中调用它)。 我遵循了有关如何创建javafx应用程序和javafx图

  • 我最近将Eclipse更新为2019-12版,将JDK更新为JavaSE13版,之后我了解到,这个JSE不再将JavaFX作为核心库。因此,我查找了与JSE13兼容的新JavaFX库的Maven依赖项,并选择了版本11。我将它们添加到我的文件中,如下所示: 但是,现在我的源文件中的一些导入无法解析。例如: 我已经检查了javadocs的类,似乎它们应该包含在、和模块中的类文件中,我在文件中作为依赖

  • 问题内容: 我试图混淆我的JavaFX应用程序,但失败了。生成的结果不起作用,我不明白为什么。最终的jar失败了,因为fxml文件无法再加载所有导入(ClassNotFoundException)。 部署工作流程: 生成可运行的jar(在IntelliJ知识中作为工件) 用ProGuard混淆那个罐子 修复ProGuard无法执行的那个jar中的一些问题 1)最小示例应用 示例应用程序“ Guar