应用JavaFx的MVC

我是新的GUI世界/ OOdevise模式,我想为我的GUI应用程序使用MVC模式,我已经阅读了关于MVC模式的小教程,模型将包含数据,视图将包含视觉元素和控制器将视图和模型之间的联系。

我有一个视图包含一个ListView节点,并从List类(Model)填充名称。 但是我对一件事有点困惑。

我想知道的是,如果从文件加载数据是控制器或模型的责任? 而名称的ObservableList:应该存储在Controller还是Model中?

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

您的Person类并不是真正的模型,除非您有一个非常简单的应用程序:这通常是我们所说的一个域对象,并且该模型将包含对它的引用以及其他数据。 在一个狭义的上下文中,比如当你只是在考虑ListView ,你可以把Person看作你的数据模型(它模拟了ListView每个元素中的数据),但是在更广泛的应用环境中,更多的数据和状态来考虑。

如果你正在显示一个ListView<Person> ,你所需要的数据至less是一个ObservableList<Person> 。 您可能还需要一个属性,如currentPerson ,它可能代表列表中的选定项目。

如果你唯一的观点是ListView ,那么创build一个单独的类来存储这个将是矫枉过正,但任何真正的应用程序通常会结束与多个视图。 此时,在模型中共享数据成为不同控制器相互通信的非常有用的方法。

所以,例如,你可能有这样的事情:

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

现在,您可能会看到另一个视图,比如编辑器,它有三个文本字段,分别lastNamefirstNamelastNameemail属性。 它的控制器可能看起来像:

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

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

 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属性来定义控制器类会困难得多。 这种模式也强烈依赖于dependency injection框架。

更新:这个例子的完整代码在这里 。

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

对我来说,这个模型只负责将需要的数据结构代表应用程序的商业逻辑。

加载来自任何源的数据的动作应由控制层完成。 您也可以使用存储库模式 ,它可以帮助您在从视图中访问数据时从源types中进行抽象。 有了这个实现,你不应该在乎Repository实现是否正在从文件,sql,nosql,webservice加载数据…

并且名称的ObservableList将被存储在控制器或模型中?

对于我来说,ObservableList是View的一部分。 这是可以绑定到javafx控件的那种数据结构。 例如,ObservableList可以用模型中的string填充,但ObservableList引用应该是某些View类的属性。 在Javafx中,使用由模型中的域对象支持的Observable属性绑定javafx控件非常令人满意。

你也可以看看viewmodel的概念 。 对于我来说,由POJO支持的JavaFx bean可以被视为一个视图模型,您可以将其视为一个模型对象,随时可以在视图中呈现。 例如,如果您的视图需要显示从2个模型属性计算出来的总值,那么这个总值可能是视图模型的一个属性。 这个属性不会被保留下来,只要你显示视图就可以计算出来。