Учебник по JavaFX (Русский)

Часть 2: Модель и компонент TableView

Screenshot AddressApp Part 2

Часть 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;
  • Класс 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.

  1. Создайте новый класс внутри пакета view и назовите его PersonOverviewController.java. (Мы должны разместить этот класс-контроллер в том же пакете, где находится файл разметки PersonOverview.fxml, иначе Scene Builder не сможет его найти.)
  2. Для того, чтобы получить доступ к таблице и меткам представления, мы определим некоторые переменные. Эти переменные и некоторые методы имеют специальную аннотацию @FXML. Она необходима для того, чтобы fxml-файл имел доступ к приватным полям и методам. После этого мы настроим наш fxml-файл так, что при его загрузке приложение автоматически заполняло эти переменные данными. Итак, давайте добавим следующий код в наш класс:
Примечание: При импорте пакетов всегда используйте пакет javafx, а НЕ awt или swing!
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, какой контроллер он должен использовать, а так же не указали соответствие между элементами представления и полями внутри класса-контроллера. Для этого:

  1. Откройте файл PersonOverview.fxml в приложении Scene Builder.

  2. Откройте вкладку Controller слева на панели Document и выберите класс PersonControllerOverview в качестве класса-контроллера.
    Set Controller Class

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

  4. Сделайте то же самое для колонок таблицы и установите значения свойства fx:id firstNameColumn и lastNameColumn соответственно.

  5. Для каждой метки во второй колонке компонента GridPane также установите соответствующие значения fx:id.
    Set Label fx:id

  6. Важно: сохраните файл PersonOverview.fxml, вернитесь в среду разработки Eclipse и обновите весь проект AdressApp (F5). Это необходимо для того, чтобы приложение Eclipse “увидело” те изменения, которые мы сделали в приложении Scene Builder.


Запуск приложения

После запуска приложения мы должны увидеть что-то похожее на то, что изображено на картинке в начале данной статьи.

Поздравляю!

Примечание: пока ещё при выборе конкретного адресата у нас не обновляются метки. Взаимодействие с пользователем мы будем программировать в следующей части учебника.

Что дальше?

В 3-й части учебника мы научим наше приложение добавлять, редактировать и удалять информацию в адресной книге.

Вам могут быть интересны также некоторые другие статьи

Comments