这种模式有许多不同的变体。特别是,Web 应用程序上下文中的“MVC”与胖客户端(例如桌面)应用程序上下文中的“MVC”的解释有些不同(因为 Web 应用程序必须位于请求-响应周期之上)。这只是使用 JavaFX 在胖客户端应用程序上下文中实现 MVC 的一种方法。
Your Person
类并不是真正的模型,除非您有一个非常简单的应用程序:这通常是我们所说的域对象,模型将包含对其的引用以及其他数据。在狭隘的背景下,例如当你just思考ListView
,你可以想到Person
作为您的数据模型(它对每个元素中的数据进行建模ListView
),但在更广泛的应用程序上下文中,需要考虑更多数据和状态。
如果您正在显示一个ListView<Person>
您需要的数据至少是ObservableList<Person>
。您可能还想要一个属性,例如currentPerson
,这可能代表列表中选定的项目。
If the only您拥有的视图是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
, and 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();
}
}
正如我所说,这种模式有很多变体(这可能更多的是模型-视图-呈现器,或“被动视图”变体),但这是一种方法(我基本上喜欢的一种方法)。通过控制器的构造函数向控制器提供模型会更自然一些,但是用fx:controller
属性。这种模式也非常适合依赖注入框架。
Update:这个例子的完整代码是here https://github.com/james-d/SimpleMVP.
如果您对 JavaFX 中的 MVC 教程感兴趣,请参阅:
- 伊甸园编码教程:如何在JavaFX中应用MVC https://edencoding.com/mvc-in-javafx/