
Часть 2: Содержание
- Создание класса-модели;
- Использование класса-модели в коллекции ObservableList;
- Отображение данных в компоненте TableView с помощью Контроллеров.
Создание класса-модели
Класс-модель необходим для хранения в нашей будущей адресной книге информации об адресатах. Добавьте класс Person.java в пакет ch.makery.address.model. В нём будет несколько переменных для хранения информации об имени, адресе и дне рождения. Добавьте в этот класс следующий код.
Person.java
package ch.makery.address.model;
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Класс-модель для адресата (Person).
*
* @author Marco Jakob
*/
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty street;
private final IntegerProperty postalCode;
private final StringProperty city;
private final ObjectProperty<LocalDate> birthday;
/**
* Конструктор по умолчанию.
*/
public Person() {
this(null, null);
}
/**
* Конструктор с некоторыми начальными данными.
*
* @param firstName
* @param lastName
*/
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
// Какие-то фиктивные начальные данные для удобства тестирования.
this.street = new SimpleStringProperty("какая-то улица");
this.postalCode = new SimpleIntegerProperty(1234);
this.city = new SimpleStringProperty("какой-то город");
this.birthday = new SimpleObjectProperty<LocalDate>(LocalDate.of(1999, 2, 21));
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getStreet() {
return street.get();
}
public void setStreet(String street) {
this.street.set(street);
}
public StringProperty streetProperty() {
return street;
}
public int getPostalCode() {
return postalCode.get();
}
public void setPostalCode(int postalCode) {
this.postalCode.set(postalCode);
}
public IntegerProperty postalCodeProperty() {
return postalCode;
}
public String getCity() {
return city.get();
}
public void setCity(String city) {
this.city.set(city);
}
public StringProperty cityProperty() {
return city;
}
public LocalDate getBirthday() {
return birthday.get();
}
public void setBirthday(LocalDate birthday) {
this.birthday.set(birthday);
}
public ObjectProperty<LocalDate> birthdayProperty() {
return birthday;
}
}
Объяснение
- В JavaFX для всех полей класса-модели предпочтительно использовать [
Properties](http://docs.oracle.com/javase/8/javafx/api/javafx/beans/property/Property.html.Propertyпозволяет нам получать автоматические уведомления при любых изменениях переменных, таких какlastNameили любых других. Это позволяет поддерживать синхронность представления и данных. Для более детального изученияPropertiesможно прочесть статью Using JavaFX Properties and Binding;
- Класс
LocalDate, тип которого мы выбрали для нашей переменнойbirthday, это часть нового Date and Time API для JDK 8.
Список людей
Основные данные, которыми оперирует наше приложение - это группа экземпляров класса Person. Давайте создадим в классе MainApp.java список объектов класса Person. Все остальные классы-контроллеры позже получат доступ к этому центральному списку внутри этого класса.
Список ObservableList
Мы работаем с классами-представлениями JavaFX, которые необходимо информировать при любых изменениях в списке адресатов. Это важно, потому что, не будь этого, мы бы не смогли синхронизировать представление данных с самими данными. Для этой цели в JavaFX были введены некоторые новые классы коллекций.
Из этих классов нам понадобится класс ObservableList. Для создания экземпляра данного класса добавьте приведённый код в начало MainApp.java. Мы так же добавим в код конструктор, который будет создавать некоторые демонстрационный данные и метод-геттер с публичным модификатором доступа:
MainApp.java
// ... ПОСЛЕ ДРУГИХ ПЕРЕМЕННЫХ ...
/**
* Данные, в виде наблюдаемого списка адресатов.
*/
private ObservableList<Person> personData = FXCollections.observableArrayList();
/**
* Конструктор
*/
public MainApp() {
// В качестве образца добавляем некоторые данные
personData.add(new Person("Hans", "Muster"));
personData.add(new Person("Ruth", "Mueller"));
personData.add(new Person("Heinz", "Kurz"));
personData.add(new Person("Cornelia", "Meier"));
personData.add(new Person("Werner", "Meyer"));
personData.add(new Person("Lydia", "Kunz"));
personData.add(new Person("Anna", "Best"));
personData.add(new Person("Stefan", "Meier"));
personData.add(new Person("Martin", "Mueller"));
}
/**
* Возвращает данные в виде наблюдаемого списка адресатов.
* @return
*/
public ObservableList<Person> getPersonData() {
return personData;
}
// ... ОСТАЛЬНАЯ ЧАСТЬ КЛАССА ...
Класс PersonOverviewController
Теперь мы отобразим в нашей таблице некоторые данные. Для этого необходимо создать класс-контроллер для представления PersonOverview.fxml.
- Создайте новый класс внутри пакета
viewи назовите егоPersonOverviewController.java. (Мы должны разместить этот класс-контроллер в том же пакете, где находится файл разметкиPersonOverview.fxml, иначе Scene Builder не сможет его найти.) - Для того, чтобы получить доступ к таблице и меткам представления, мы определим некоторые переменные. Эти переменные и некоторые методы имеют специальную аннотацию
@FXML. Она необходима для того, чтобы fxml-файл имел доступ к приватным полям и методам. После этого мы настроим наш fxml-файл так, что при его загрузке приложение автоматически заполняло эти переменные данными. Итак, давайте добавим следующий код в наш класс:
PersonOverviewController.java
package ch.makery.address.view;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import ch.makery.address.MainApp;
import ch.makery.address.model.Person;
public class PersonOverviewController {
@FXML
private TableView<Person> personTable;
@FXML
private TableColumn<Person, String> firstNameColumn;
@FXML
private TableColumn<Person, String> lastNameColumn;
@FXML
private Label firstNameLabel;
@FXML
private Label lastNameLabel;
@FXML
private Label streetLabel;
@FXML
private Label postalCodeLabel;
@FXML
private Label cityLabel;
@FXML
private Label birthdayLabel;
// Ссылка на главное приложение.
private MainApp mainApp;
/**
* Конструктор.
* Конструктор вызывается раньше метода initialize().
*/
public PersonOverviewController() {
}
/**
* Инициализация класса-контроллера. Этот метод вызывается автоматически
* после того, как fxml-файл будет загружен.
*/
@FXML
private void initialize() {
// Инициализация таблицы адресатов с двумя столбцами.
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
}
/**
* Вызывается главным приложением, которое даёт на себя ссылку.
*
* @param mainApp
*/
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
// Добавление в таблицу данных из наблюдаемого списка
personTable.setItems(mainApp.getPersonData());
}
}
Этот код требует некоторых разъяснений:
- Все поля и методы, к которым fxml-файлу потребуется доступ, должны быть отмечены аннотацией
@FXML. Несмотря на то, что это требование предъявляется только для полей и методов с модификатором private, лучше оставить их закрытыми и помечать аннотацией, чем делать публичными! - После загрузки fxml-файла автоматически вызывается метод
initialize(). На этот момент все FXML-поля должны быть инициализированы; - Метод
setCellValueFactory(...)определяет, какое поле внутри классаPersonбудут использоваться для конкретного столбца в таблице. Стрелка->означает, что мы использовали лямбда-выражение из Java 8. (Есть вариант сделать то же самое через PropertyValueFactory, но этот способ нарушает безопасность типов).
В нашем примере для столбцов таблицы мы использовали только значения StringProperty. Если нам понадобится использовать IntegerProperty или DoubleProperty, то setCellValueFactory(...) должен иметь дополнительный метод asObject():
myIntegerColumn.setCellValueFactory(cellData ->
cellData.getValue().myIntegerProperty().asObject());
Это добавление необходимо сделать из-за неудачного решения при проектировании JavaFX (для подробностей см. это обсуждение).
Соединение класса MainApp с классом PersonOverviewController
Метод setMainApp(...) должен быть вызван из класса MainApp. Это даст нашему контроллеру доступ к экземпляру MainApp, к коллекции записей personList внутри него и к другим элементам класса. Добавьте в метод showPersonOverview() две дополнительные строки:
MainApp.java - метод showPersonOverview()
/**
* Показывает в корневом макете сведения об адресатах.
*/
public void showPersonOverview() {
try {
// Загружаем сведения об адресатах.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = (AnchorPane) loader.load();
// Помещаем сведения об адресатах в центр корневого макета.
rootLayout.setCenter(personOverview);
// Даём контроллеру доступ к главному приложению.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
Привязка класса-контроллера к fxml-файлу
Данная часть учебника близится к своему завершению, однако мы пропустили одну маленькую деталь! Мы не сказали файлу PersonOverview.fxml, какой контроллер он должен использовать, а так же не указали соответствие между элементами представления и полями внутри класса-контроллера. Для этого:
-
Откройте файл
PersonOverview.fxmlв приложении Scene Builder. -
Откройте вкладку Controller слева на панели Document и выберите класс
PersonControllerOverviewв качестве класса-контроллера.

-
Выберите компонент
TableViewна вкладке Hierarchy, перейдите на вкладку Code и в поле fx:id установите значениеpersonTable.

-
Сделайте то же самое для колонок таблицы и установите значения свойства fx:id
firstNameColumnиlastNameColumnсоответственно. -
Для каждой метки во второй колонке компонента GridPane также установите соответствующие значения fx:id.

-
Важно: сохраните файл
PersonOverview.fxml, вернитесь в среду разработки Eclipse и обновите весь проект AdressApp (F5). Это необходимо для того, чтобы приложение Eclipse “увидело” те изменения, которые мы сделали в приложении Scene Builder.
Запуск приложения
После запуска приложения мы должны увидеть что-то похожее на то, что изображено на картинке в начале данной статьи.
Поздравляю!
Примечание: пока ещё при выборе конкретного адресата у нас не обновляются метки. Взаимодействие с пользователем мы будем программировать в следующей части учебника.
Что дальше?
В 3-й части учебника мы научим наше приложение добавлять, редактировать и удалять информацию в адресной книге.