应用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
始终是列表视图中的选定项目。
现在,您可能会看到另一个视图,比如编辑器,它有三个文本字段,分别lastName
的firstName
, lastName
和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()); } }); } }
现在,如果你设置了这两个控制器共享相同的模型,编辑器将编辑列表中当前选定的项目。
加载和保存数据应该通过模型完成。 有时候甚至会把这个分解成一个单独的类,模型有一个引用(例如,允许您轻松地在基于文件的数据加载器和数据库数据加载器或访问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个模型属性计算出来的总值,那么这个总值可能是视图模型的一个属性。 这个属性不会被保留下来,只要你显示视图就可以计算出来。