Часть 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-й части учебника мы научим наше приложение добавлять, редактировать и удалять информацию в адресной книге.