Несколько окон
Главное окно — не единственное: приложение может открыть сколько угодно окон 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().