使用 JavaFx 应用 MVC

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32342864/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:22:13  来源:igfitidea点击:

Applying MVC With JavaFx

javauser-interfacemodel-view-controllerjavafx

提问by karim

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.

我是 GUI 世界/OO 设计模式的新手,我想在我的 GUI 应用程序中使用 MVC 模式,我已经阅读了关于 MVC 模式的一些教程,模型将包含数据,视图将包含视觉元素和控制器将绑定在视图和模型之间。

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.

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

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?

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

回答by James_D

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.

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

Your Personclass 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 justthinking about the ListView, you can think of the Personas 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.

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

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<Person>您需要的数据,至少是一个ObservableList<Person>. 您可能还需要一个属性,例如currentPerson,它可能代表列表中的所选项目。

If the onlyview 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.

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

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

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

现在您可能有一个如下所示的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);
            }
        });
    }
}

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

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

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

现在,你可能有另一种观点,说一个编辑器,用了三个文本字段firstNamelastName以及email一个人的性质。它的控制器可能看起来像:

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.

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

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

加载和保存数据应该通过模型完成。有时,您甚至会将其分解为模型引用的单独类(例如,允许您轻松地在基于文件的数据加载器和数据库数据加载器或访问 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 ...
    }
}

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

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:controllerattribute. This pattern also lends itself strongly to dependency injection frameworks.

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

Update:full code for this example is here.

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

回答by alvaro

What i want to know is that if loading the data from a file is the responsibility of the Controller Or the model?

我想知道的是,从文件加载数据是控制器还是模型的责任?

For me the model is only responsible of bringing the requiered data structures that represent the bussiness logic of the application.

对我来说,模型只负责带来代表应用程序业务逻辑的所需数据结构。

The action of loading that data from any source should be done by the Controller Layer. You could also use the repository pattern, which can help you in abstracting from the type of source when you are acessing the data from the view. With this implemented you should not care if the Repository implentation is loading the data from file, sql, nosql, webservice ...

从任何来源加载该数据的操作应该由控制器层完成。您还可以使用存储库模式,它可以帮助您在从视图访问数据时从源类型中抽象出来。实现此功能后,您无需关心存储库实现是否正在从文件、sql、nosql、webservice 加载数据...

And the ObservableList of the names will be stored in the controller or the model?

而名字的 ObservableList 会存储在控制器还是模型中?

For me the ObservableList is part of the View. It is the kind of data structure you can bind to javafx controls. So for example a ObservableList could be populated with Strings from the model but the ObservableList reference should be an attribute of some View′s class. In Javafx its very pleaseant to bind javafx controls with Observable Properties backed by domain objects from the model.

对我来说 ObservableList 是视图的一部分。它是一种可以绑定到 javafx 控件的数据结构。因此,例如 ObservableList 可以用模型中的字符串填充,但 ObservableList 引用应该是某个 View 类的属性。在 Javafx 中,将 javafx 控件与由模型中的域对象支持的 Observable 属性绑定是非常令人愉快的。

You could also have a look to viewmodel concept. For me a JavaFx bean backed by a POJO could be considered as a view-model, you could see it as a model object ready to be presented in the view. So for example if your view needs to show some total value calculated from 2 model attributes, this total value could be an attribute of the view-model. This attribute would not be persisted and it would be calculated any time you show the view.

你也可以看看viewmodel 的概念。对我来说,由 POJO 支持的 JavaFx bean 可以被视为视图模型,您可以将其视为准备在视图中呈现的模型对象。因此,例如,如果您的视图需要显示从 2 个模型属性计算的某个总值,则该总值可能是视图模型的一个属性。此属性不会被持久化,并且会在您显示视图时计算。