我是GUI世界/ OO设计模式的新手,我想将MVC模式用于我的GUI应用程序,我已经阅读了一些关于MVC模式的教程,模型将包含数据,View将包含视觉元素和控制器将在视图和模型之间建立联系。
我有一个包含ListView节点的View,ListView将使用Person Class(Model)填充名称。但我对一件事情有点困惑。
我想知道的是,从文件加载数据是由Controller还是Model负责?名称的ObservableList:它应该存储在Controller还是Model?
中答案 0 :(得分:64)
这种模式有许多不同的变体。特别是" MVC"在Web应用程序的上下文中,对于" MVC"在胖客户端(例如桌面)应用程序的上下文中(因为Web应用程序必须位于请求 - 响应周期的顶部)。这只是使用JavaFX在胖客户端应用程序的上下文中实现MVC的一种方法。
你的Person
类实际上不是模型,除非你有一个非常简单的应用程序:这通常是我们所谓的域对象,模型将包含对它的引用以及其他数据。在狭隘的上下文中,例如当您只是考虑ListView
时,您可以将Person
视为您的数据模型(它会模拟每个元素中的数据) ListView
),但在更广泛的应用程序环境中,需要考虑更多的数据和状态。
如果您显示ListView<Person>
所需的数据,则至少为ObservableList<Person>
。您可能还需要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
始终是列表视图中的选定项。
现在,您可能有另一个视图,例如编辑器,其中包含一个人的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 ...
}
}
然后您可能拥有一个控制器,可以访问此功能:
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();
}
}
正如我所说,这种模式有很多变化(这可能更像是模型 - 视图 - 演示者,或者被动视图&#34;变体),但这是一种方法(一种)我基本上喜欢)。通过构造函数向控制器提供模型更为自然,但是使用fx:controller
属性定义控制器类要困难得多。这种模式也非常适合依赖注入框架。
更新:此示例的完整代码为here。
答案 1 :(得分:2)
我想知道的是,如果从文件加载数据是Controller或模型的责任吗?
对我来说,模型只负责提供代表应用程序业务逻辑的必需数据结构。
从任何来源加载数据的操作应由控制器层完成。您还可以使用repository pattern,它可以帮助您在从视图中访问数据时从源类型中抽象出来。有了这个实现,你不应该关心Repository实现是从文件,sql,nosql,webservice加载数据......
名称的ObservableList将存储在控制器或模型中吗?
对我来说,ObservableList是View的一部分。它是可以绑定到javafx控件的数据结构。因此,例如,可以使用模型中的字符串填充ObservableList,但ObservableList引用应该是某些View类的属性。 在Javafx中,非常喜欢将javafx控件与由模型中的域对象支持的Observable Properties绑定。
您还可以查看viewmodel concept。对我来说,由POJO支持的JavaFx bean可以被视为视图模型,您可以将其视为准备在视图中呈现的模型对象。因此,例如,如果您的视图需要显示从2个模型属性计算的总值,则此总值可以是视图模型的属性。此属性不会保留,只要您显示视图就会计算出来。