Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Контроллер FXML

FXML описывает, как окно выглядит, а контроллер — что оно делает. Контроллер — обычный Java-класс, который FXMLLoader создаёт сам при загрузке разметки. Загрузчик подставляет в поля контроллера узлы с совпадающими fx:id и связывает кнопки с методами-обработчиками. В контроллер переедет вся логика, которая раньше была в App.

Связь разметки и класса

В корневом теге указывают класс контроллера, а у кнопок — имя метода после #:

<!-- src/main/resources/com/example/notes/main-view.fxml — изменённые строки -->
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
            fx:controller="com.example.notes.MainController">
 
<Button fx:id="addButton" text="Добавить" onAction="#addNote"/>
<Button fx:id="deleteButton" text="Удалить" onAction="#deleteNote"/>

Остальной файл не меняется.

Класс контроллера

Создайте файл рядом с App:

// src/main/java/com/example/notes/MainController.java
package com.example.notes;
 
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
 
public class MainController {
 
    private final ObservableList<Note> notes = FXCollections.observableArrayList(
            new Note("Купить молоко", "2 литра", "Дом", true),
            new Note("Позвонить Олегу", "Обсудить отпуск", "Работа", false));
 
    @FXML
    private TableView<Note> table;
    @FXML
    private TableColumn<Note, String> titleColumn;
    @FXML
    private TableColumn<Note, String> categoryColumn;
    @FXML
    private TableColumn<Note, String> importantColumn;
    @FXML
    private Label countLabel;
    @FXML
    private TextField titleField;
    @FXML
    private TextArea textArea;
    @FXML
    private ComboBox<String> categoryBox;
    @FXML
    private CheckBox importantBox;
    @FXML
    private Button addButton;
    @FXML
    private Button deleteButton;
 
    @FXML
    private void initialize() {
        titleColumn.setCellValueFactory(cell -> new SimpleStringProperty(cell.getValue().title()));
        categoryColumn.setCellValueFactory(cell -> new SimpleStringProperty(cell.getValue().category()));
        importantColumn.setCellValueFactory(
                cell -> new SimpleStringProperty(cell.getValue().important() ? "★" : ""));
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
        table.setItems(notes);
 
        table.getSelectionModel().selectedItemProperty().addListener(
                (observable, oldNote, note) -> {
                    if (note != null) {
                        showNote(note);
                    }
                });
 
        categoryBox.getItems().addAll("Работа", "Дом", "Идеи");
        categoryBox.setValue("Идеи");
 
        countLabel.textProperty().bind(Bindings.size(notes).asString("Заметок: %d"));
        addButton.disableProperty().bind(titleField.textProperty().isEmpty());
        deleteButton.disableProperty().bind(
                table.getSelectionModel().selectedItemProperty().isNull());
    }
 
    @FXML
    private void addNote() {
        Note note = new Note(
                titleField.getText().strip(),
                textArea.getText(),
                categoryBox.getValue(),
                importantBox.isSelected());
        notes.add(note);
        table.getSelectionModel().select(note);
    }
 
    @FXML
    private void deleteNote() {
        int index = table.getSelectionModel().getSelectedIndex();
        if (index >= 0) {
            notes.remove(index);
        }
    }
 
    private void showNote(Note note) {
        titleField.setText(note.title());
        textArea.setText(note.text());
        categoryBox.setValue(note.category());
        importantBox.setSelected(note.important());
    }
}

App остаётся таким, как в прошлом уроке: он только загружает FXML.

Как это работает

  1. FXMLLoader читает fx:controller и создаёт объект MainController конструктором без параметров.
  2. Строит узлы из разметки и записывает каждый узел с fx:id в поле с тем же именем.
  3. Связывает onAction="#addNote" с методом addNote.
  4. Вызывает initialize() — к этому моменту все поля уже заполнены.
ЭлементЗачем нужен
@FXML на полеРазрешает загрузчику заполнить закрытое поле
@FXML на методеДелает закрытый метод доступным для onAction
initialize()Настройка после загрузки: колонки, привязки, начальные данные

Обработчику можно не объявлять параметр ActionEvent, если событие не нужно. В конструкторе контроллера поля ещё пусты, поэтому вся настройка — в initialize.

Проверка

Запустите mvn javafx:run. Окно такое же, как в уроке о TableView: две заметки в таблице, «Заметок: 2», кнопка «Добавить» неактивна, пока заголовок пуст. Добавление, выбор и удаление работают, но весь код теперь в контроллере, а разметка — в FXML.

Частая ошибка

Имя в onAction должно точно совпадать с именем метода. Если в разметке написано #addNotes, а метод называется addNote, окно не откроется: javafx.fxml.LoadException: Error resolving onAction='#addNotes', either the event handler is not in the Namespace or there is an error in the script. В сообщении есть путь к файлу и номер строки. Если же забыть opens com.example.notes to javafx.fxml в module-info.java, ошибка будет module com.example.notes does not "opens com.example.notes" to module javafx.fxml.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.