这种模式有许多不同的变体。特别是,在 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) {
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 {
public void loadData(File file) throws IOException {
}
public void saveData(File file) throws IOException {
}
}
然后您可能会有一个控制器,提供对此功能的访问:
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) {
}
}
}
@FXML
public void save() {
}
}
现在,您可以轻松地组装一个应用程序:
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
属性定义控制器类。这种模式也非常适合依赖注入框架。
更新:此示例的完整代码在这里。
如果您对JavaFX中的MVC教程感兴趣,请参见: