Ответ 1
Существует много разных вариантов этого шаблона. В частности, "MVC" в контексте веб-приложения интерпретируется несколько иначе, чем "MVC" в контексте приложения с толстым клиентом (например, настольного) (поскольку веб-приложение должно сидеть поверх цикла запроса-ответа). Это всего лишь один подход к реализации MVC в контексте толстого клиентского приложения с использованием JavaFX.
Ваш класс 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());
}
});
}
}
Теперь, если вы настроите все так, чтобы оба этих контроллера использовали одну и ту же модель, редактор отредактирует текущий выбранный элемент в списке.
Загрузка и сохранение данных должны выполняться с помощью модели. Иногда вы даже учитываете это в отдельном классе, к которому у модели есть ссылка (что позволяет легко переключаться между файловым загрузчиком на основе файлов и загрузчиком данных базы данных или, например, реализацией, которая обращается к веб-службе). В простом случае вы можете сделать
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
. Этот шаблон также сильно подкрепляет рамки внедрения инъекций.
Обновление: полный код для этого примера здесь.