Encodera

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

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

Оглавление

Несколько окон

Главное окно — не единственное: приложение может открыть сколько угодно окон Stage. Для записной книжки сделаем окно редактирования. Двойной щелчок по заметке откроет его, а после «Сохранить» изменённая заметка вернётся в таблицу. Форма главного окна с этого урока служит только для новых заметок.

Разметка окна

<?xml version="1.0" encoding="UTF-8"?>
<!-- src/main/resources/com/example/notes/edit-view.fxml -->
 
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
 
<VBox spacing="8" prefWidth="400" stylesheets="@styles.css"
      xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
      fx:controller="com.example.notes.EditController">
    <padding>
        <Insets top="16" right="16" bottom="16" left="16"/>
    </padding>
    <TextField fx:id="titleField"/>
    <TextArea fx:id="textArea" prefRowCount="6" wrapText="true"/>
    <CheckBox fx:id="importantBox" text="Важная"/>
    <HBox spacing="8" alignment="CENTER_RIGHT">
        <Button text="Сохранить" onAction="#save" defaultButton="true"/>
        <Button text="Отмена" onAction="#cancel" cancelButton="true"/>
    </HBox>
</VBox>

stylesheets="@styles.css" подключает те же стили: у новой сцены своих стилей нет. defaultButton срабатывает по Enter, cancelButton — по Esc.

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

// src/main/java/com/example/notes/EditController.java
package com.example.notes;
 
import java.util.Optional;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
 
public class EditController {
 
    @FXML
    private TextField titleField;
    @FXML
    private TextArea textArea;
    @FXML
    private CheckBox importantBox;
 
    private Note note;
    private Note result;
 
    public void setNote(Note note) {
        this.note = note;
        titleField.setText(note.title());
        textArea.setText(note.text());
        importantBox.setSelected(note.important());
    }
 
    public Optional<Note> getResult() {
        return Optional.ofNullable(result);
    }
 
    @FXML
    private void save() {
        result = new Note(
                titleField.getText().strip(),
                textArea.getText(),
                note.category(),
                importantBox.isSelected());
        close();
    }
 
    @FXML
    private void cancel() {
        close();
    }
 
    private void close() {
        Stage stage = (Stage) titleField.getScene().getWindow();
        stage.close();
    }
}

Данные приходят через обычный публичный метод setNote, а уходят через getResult. Если нажали «Отмена», result остаётся null и результат пуст. Своё окно контроллер находит через любой узел: getScene().getWindow().

Открытие окна

В главном контроллере загружаем разметку, достаём контроллер и показываем окно:

// src/main/java/com/example/notes/MainController.java — новый метод editNote()
private void editNote(int index) {
    try {
        FXMLLoader loader = new FXMLLoader(App.class.getResource("edit-view.fxml"));
        Parent root = loader.load();
        EditController controller = loader.getController();
        controller.setNote(notes.get(index));
 
        Stage stage = new Stage();
        stage.setTitle("Редактирование");
        stage.initOwner(table.getScene().getWindow());
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setScene(new Scene(root));
        stage.showAndWait();
 
        controller.getResult().ifPresent(note -> notes.set(index, note));
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}

loader.getController() возвращает объект, который загрузчик создал по fx:controller. WINDOW_MODAL блокирует окно-владелец, пока открыто это. showAndWait() ждёт закрытия, поэтому следующая строка уже видит результат. notes.set заменяет заметку, и таблица обновляется.

МодальностьЧто блокирует
NONEНичего, окна независимы (по умолчанию)
WINDOW_MODALОкно-владелец из initOwner
APPLICATION_MODALВсе окна приложения

Двойной щелчок по строке

Щелчки ловим на строках таблицы, поэтому задаём фабрику строк в initialize() вместо прежнего слушателя выбора:

// src/main/java/com/example/notes/MainController.java — фрагмент initialize()
table.setRowFactory(view -> {
    TableRow<Note> row = new TableRow<>();
    row.setOnMouseClicked(event -> {
        if (event.getClickCount() == 2 && !row.isEmpty()) {
            editNote(row.getIndex());
        }
    });
    return row;
});

Метод showNote больше не нужен. Вместо выделения новой строки addNote теперь вызывает новый метод clearForm(): он очищает заголовок и текст и снимает флажок.

Проверка

Дважды щёлкните по «Купить молоко». Откроется окно «Редактирование» с заголовком, текстом «2 литра» и отмеченным флажком, справа внизу — синие кнопки «Сохранить» и «Отмена». Пока оно открыто, главное окно не реагирует. Заголовок в поле выделен целиком — введите «Купить молоко и хлеб» и нажмите Enter: окно закроется, а в таблице появится новый заголовок.

Смена сцены

Иногда второе окно не нужно — достаточно заменить содержимое текущего, например показать экран настроек. Для этого вызывают stage.setScene(другаяСцена) или меняют корень: scene.setRoot(новыйКорень). Окно остаётся тем же, меняется только содержимое.

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

Модальность задают после показа окна: stage.show(), а затем stage.initModality(...). Программа падает с java.lang.IllegalStateException: Cannot set modality once stage has been set visible. initOwner и initModality вызывают до show() или showAndWait().

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

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