使用 JavaFx 应用 MVC [英] Applying MVC With JavaFx

查看:33
本文介绍了使用 JavaFx 应用 MVC的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 GUI 世界/OO 设计模式的新手,我想在我的 GUI 应用程序中使用 MVC 模式,我已经阅读了有关 MVC 模式的小教程,模型将包含数据,视图将包含视觉元素和控制器将连接视图和模型.

I'm new to the GUI world/OO design pattern and I want to use MVC pattern for my GUI application, I have read a little tutorial about MVC pattern, the Model will contain the data, the View will contain the visual element and the Controller will tie between the View and the Model.

我有一个包含 ListView 节点的视图,ListView 将填充来自 Person 类(模型)的名称.但我对一件事有点困惑.

I have a View that contains a ListView node, and the ListView will be filled with names, from a Person Class (Model). But I'm a little confused about one thing.

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

What I want to know is if loading the data from a file is the responsibility of the Controller or the Model?? And the ObservableList of the names: should it be stored in the Controller or the Model?

推荐答案

这种模式有许多不同的变体.特别是,Web 应用程序上下文中的MVC"与胖客户端(例如桌面)应用程序上下文中的MVC"的解释有些不同(因为 Web 应用程序必须处于请求-响应周期之上).这只是使用 JavaFX 在胖客户端应用程序上下文中实现 MVC 的一种方法.

There are many different variations of this pattern. In particular, "MVC" in the context of a web application is interpreted somewhat differently to "MVC" in the context of a thick client (e.g. desktop) application (because a web application has to sit atop the request-response cycle). This is just one approach to implementing MVC in the context of a thick client application, using JavaFX.

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

Your Person class is not really the model, unless you have a very simple application: this is typically what we call a domain object, and the model will contain references to it, along with other data. In a narrow context, such as when you are just thinking about the ListView, you can think of the Person as your data model (it models the data in each element of the ListView), but in the wider context of the application, there is more data and state to consider.

如果您要显示 ListView,您需要的数据至少是 ObservableList.您可能还需要一个属性,例如 currentPerson,它可能代表列表中的所选项目.

If you are displaying a ListView<Person> the data you need, as a minimum, is an ObservableList<Person>. You might also want a property such as currentPerson, that might represent the selected item in the list.

如果您拥有的唯一视图是ListView,那么创建一个单独的类来存储它就有点矫枉过正了,但任何真正的应用程序通常最终都会有多个视图.此时,在模型中共享数据成为不同控制器相互通信的一种非常有用的方式.

If the only view you have is the ListView, then creating a separate class to store this would be overkill, but any real application will usually end up with multiple views. At this point, having the data shared in a model becomes a very useful way for different controllers to communicate with each other.

例如,您可能有这样的情况:

So, for example, you might have something like this:

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 显示的控制器,如下所示:

Now you might have a controller for the ListView display that looks like this:

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始终是列表视图中的选定项.

This controller essentially just binds the data displayed in the list to the data in the model, and ensures the model's currentPerson is always the selected item in the list view.

现在您可能有另一个视图,比如一个编辑器,其中包含三个文本字段,分别用于firstNamelastNameemail 属性人.它的控制器可能看起来像:

Now you might have another view, say an editor, with three text fields for the firstName, lastName, and email properties of a person. It's controller might look like:

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

现在,如果您进行设置,使这两个控制器共享同一个模型,则编辑器将编辑列表中当前选定的项目.

Now if you set things up so both these controllers are sharing the same model, the editor will edit the currently selected item in the list.

加载和保存数据应该通过模型来完成.有时,您甚至会将其分解为模型引用的单独类(例如,允许您轻松地在基于文件的数据加载器和数据库数据加载器或访问 Web 服务的实现之间切换).在简单的情况下,您可能会这样做

Loading and saving data should be done via the model. Sometimes you will even factor this out into a separate class to which the model has a reference (allowing you to easily switch between a file-based data loader and a database data loader, or an implementation that accesses a web service, for example). In the simple case you might do

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

那么你可能有一个控制器来提供对这个功能的访问:

Then you might have a controller that provides access to this functionality:

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...

    }
}

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

Now you can easily assemble an application:

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 属性定义控制器类要困难得多.这种模式也非常适合依赖注入框架.

As I said, there are many variations of this pattern (and this is probably more a model-view-presenter, or "passive view" variation), but that's one approach (one I basically favor). It's a bit more natural to provide the model to the controllers via their constructor, but then it's a lot harder to define the controller class with a fx:controller attribute. This pattern also lends itself strongly to dependency injection frameworks.

更新:此示例的完整代码在此处.

这篇关于使用 JavaFx 应用 MVC的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆