Prednáška č. 12
- Zložitejšie ovládacie prvky a aplikácie s viacerými oknami: jednoduchý textový editor
- Základ aplikácie
- Ovládací prvok
TextArea - Vlastnosti a spracovanie ich zmeny
- Hlavné ponuky (
MenuItem,MenuaMenuBar) - Kontextové ponuky (
ContextMenu) - Priradenie udalostí k jednotlivým položkám ponúk
- Previazanie vlastností
- Jednoduché dialógy (
Alert) - Ďalšie typy jednoduchých dialógov
- Zatvorenie hlavného okna aplikácie „krížikom”
Zložitejšie ovládacie prvky a aplikácie s viacerými oknami: jednoduchý textový editor
Cieľom tejto a nasledujúcej prednášky je demonštrovať použitie
niektorých zložitejších ovládacích prvkov v JavaFX (ako napríklad
Menu,
RadioButton,
či
ListView<T>)
a štandardných dialógov
(Alert
resp.
FileChooser),
tvorbu aplikácií s viacerými oknami, ako aj mechanizmus tzv. vlastností.
Urobíme tak na ukážkovej aplikácii jednoduchého textového editora zvládajúceho nasledujúce úkony:
- Vytvorenie prázdneho textového dokumentu a jeho následná modifikácia.
- Vytvorenie textového dokumentu pozostávajúceho z nejakého fixného
počtu náhodných cifier (nezmyselná funkcionalita slúžiaca len na
ukážku možností triedy
Menu). - Načítanie textu z používateľom zvoleného textového súboru (v našom prípade budeme predpokladať kódovanie UTF-8).
- Uloženie textu do súboru.
- V prípade požiadavky na zatvorenie neuloženého súboru výzva na jeho uloženie.
- Do určitej miery aj zmena fontu, ktorým sa text vypisuje.
Základ aplikácie
Ako koreňovú oblasť hlavného okna aplikácie zvolíme inštanciu triedy
BorderPane,
s ktorou sme sa stretli už minule. Vzhľadom na o niečo väčší rozsah
našej aplikácie sa navyše zdá rozumné nepracovať výhradne s lokálnymi
premennými metódy start, ale dôležitejšie ovládacie prvky uchovávať
ako premenné inštancie samotnej hlavnej triedy Editor, čo umožňí ich
neskoršiu modifikáciu z rôznych pomocných metód. Takto si okrem iného
budeme uchovávať aj referenciu primaryStage na hlavné okno aplikácie.
Základ programu tak môže vyzerať napríklad nasledovne.
package editor;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
public class Editor extends Application {
private Stage primaryStage;
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
BorderPane borderPane = new BorderPane();
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Predpokladajme, že titulok hlavného okna má obsahovať text Textový
editor, za ktorým v zátvorke nasleduje názov momentálne otvoreného
súboru (alebo informácia o tom, že dokument nie je uložený v žiadnom
súbore). Za zátvorkou sa navyše bude zobrazovať znak * v prípade, že
sa obsah dokumentu od jeho posledného uloženia zmenil.
V danom momente otvorený súbor si budeme pamätať v premennej
openedFile; v prípade, že nie je otvorený žiaden súbor, bude obsahom
tejto premennej referencia null. Premenná openedFileChanged bude
rovná true práve vtedy, keď od posledného uloženia dokumentu došlo k
jeho zmene alebo keď dokument nie je uložený v žiadnom súbore. Metóda
updateOpenedFileInformation dostane dvojicu premenných s rovnakým
významom a nastaví podľa nich premenné openedFile a
openedFileChanged; vhodným spôsobom pritom upraví aj titulok hlavného
okna. Z metódy start budeme volať updateOpenedFileInformation(null,
true), keďže po spustení aplikácie nebude dokument uložený v žiadnom
súbore.
// ...
import java.io.*;
// ...
public class Editor extends Application {
private File openedFile;
private boolean openedFileChanged;
// ...
private void updateOpenedFileInformation(File file, boolean hasChanged) {
openedFile = file;
openedFileChanged = hasChanged;
updatePrimaryStageTitle();
}
private void updatePrimaryStageTitle() {
StringBuilder title = new StringBuilder("Textový editor (");
if (openedFile == null) {
title.append("neuložené v žiadnom súbore");
} else {
title.append(openedFile.getName());
}
title.append(")");
if (openedFileChanged) {
title.append("*");
}
primaryStage.setTitle(title.toString());
}
@Override
public void start(Stage primaryStage) {
// ...
updateOpenedFileInformation(null, true);
// ...
}
}
Ovládací prvok TextArea
Môžeme pokračovať pridaním kľúčového ovládacieho prvku našej aplikácie –
priestoru na písanie samotného textu. Takýto ovládací prvok má v JavaFX
názov
TextArea
a my inštanciu tejto triedy zvolíme za stredovú časť koreňovej oblasti
border.
Podobne ako vyššie budeme referenciu textArea na prvok typu TextArea
uchovávať ako premennú inštancie triedy Editor. Navyše si v premenných
inštancie triedy Editor budeme pamätať aj kľúčové atribúty fontu,
ktoré vhodne inicializujeme. Na font použitý v priestore textArea
tieto atribúty aplikujeme v pomocnej metóde applyFont, ktorú zavoláme
hneď po inicializácii premennej textArea.
// ...
import javafx.scene.text.*;
import javafx.geometry.*;
// ...
public class Editor extends Application {
// ...
private TextArea textArea;
private String fontFamily = "Tahoma";
private FontWeight fontWeight = FontWeight.NORMAL;
private FontPosture fontPosture = FontPosture.REGULAR;
private double fontSize = 16;
// ...
private void applyFont() {
textArea.setFont(Font.font(fontFamily, fontWeight, fontPosture, fontSize));
}
// ...
@Override
public void start(Stage primaryStage) {
// ...
textArea = new TextArea();
borderPane.setCenter(textArea);
textArea.setPadding(new Insets(5, 5, 5, 5));
textArea.setPrefWidth(1000);
textArea.setPrefHeight(700);
applyFont();
// ...
}
}
Vlastnosti a spracovanie ich zmeny
Chceli by sme teraz pomocou metódy updateOpenedFileInformation
prestaviť premennú openedFileChanged na true zakaždým, keď sa v
textovom poli udeje nejaká zmena (viditeľný efekt to bude mať až po
implementácii ukladania do súboru; po vhodných dočasných zmenách v
našom programe ale môžeme funkčnosť nasledujúceho kódu testovať už
teraz).
To znamená: zakaždým, keď sa zmení obsah priestoru textArea,
potrebujeme vykonať nasledujúcu metódu:
private void handleTextAreaChange() {
if (!openedFileChanged) {
updateOpenedFileInformation(openedFile, true);
}
}
Aby sme takúto akciu vedeli vykonať po každej zmene textového obsahu
priestoru textArea, využijeme mechanizmus takzvaných vlastností. Pod
vlastnosťou sa v JavaFX rozumie trieda implementujúca generické
rozhranie
Property<T>
a možno si ju predstaviť ako „značne pokročilý obal pre nejakú hodnotu
typu T”. Rozhranie Property<T> je rozšírením rozhrania
ObservableValue<T>
reprezentujúceho hodnoty typu T, ktorých zmeny možno v určitom presne
definovanom zmysle sledovať.
Podobne ako sme k ovládacím prvkom pridávali spracúvateľov udalostí,
možno k vlastnostiam a ďalším inštanciám rozhrania ObservableValue<T>
pridávať „spracúvateľov zmien” volaných zakaždým, keď sa zmení nimi
„obalená” hodnota. Týmito spracúvateľmi však teraz nebudú inštancie
tried implementujúcich rozhranie EventHandler<T>, ale inštancie tried
implementujúcich rozhranie
ChangeListener<T>.
To vyžaduje implementáciu jedinej metódy
void changed(ObservableValue<? extends T> observable, T oldValue, T newValue)
ktorá sa vykoná pri zmene hodnoty „obalenej” inštanciou observable z
oldValue na newValue. Ide pritom o funkcionálne rozhranie, takže na
jeho implementáciu možno použiť aj lambda výrazy. Registráciu inštancie
rozhrania ChangeListener<T> ako „spracúvateľa zmeny” pre inštanciu
observable rozhrania ObservableValue<T> vykonáme pomocou metódy
observable.addListener.
Vráťme sa teraz k nášmu textovému editoru: textový obsah priestoru
textArea je reprezentovaný ako vlastnosť, ktorú môžeme získať volaním
metódy textArea.textProperty(). Ide tu o inštanciu triedy
StringProperty
implementujúcej rozhranie Property<String>. Môžeme tak pre ňu
zaregistrovať „spracúvateľa zmien” pomocou metódy addListener, ktorej
jediným argumentom bude inštancia takéhoto spracúvateľa. To možno
realizovať pomocou anonymnej triedy
import javafx.beans.value.*;
// ...
public void start(Stage primaryStage) {
// ...
textArea.textProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
handleTextAreaChange();
}
});
// ...
}
alebo alternatívne prostredníctvom lambda výrazu
public void start(Stage primaryStage) {
// ...
textArea.textProperty().addListener((observable, oldValue, newValue) -> handleTextAreaChange());
// ...
}
Poznámky:
- Textový obsah priestoru typu
TextAreaje v JavaFX iba jednou z obrovského množstva vlastností, na ktorých zmenu možno reagovať. Ovládacie prvky typicky ponúkajú veľké množstvo vlastností, o ktorých sa možno dočítať v dokumentácii (ako príklady uveďme napríklad text alebo font tlačidla resp. textového popisku, rozmery okna, atď.). - V prípade, že nejaký ovládací prvok ponúka vlastnosť, ku ktorej sa
pristupuje metódou
cokolvekProperty, typicky ponúka aj metódugetCokolvek, ktorá vráti hodnotu obalenú danou vlastnosťou. V prípade, že možno meniť hodnotu danej vlastnosti, môže byť k dispozícii aj metódasetCokolvek. - Vlastnosti navyše možno medzi sebou aj vzájomne previazať (napríklad veľkosť kruhu vykresleného na scéne možno previazať s veľkosťou okna tak, aby bol polomer kruhu rovný tretine menšieho z rozmerov okna). S príkladom previazania vlastností sa stretneme nižšie.
Hlavné ponuky (MenuItem, Menu a MenuBar)
Kľúčovou súčasťou mnohých aplikácií bývajú hlavné ponuky (menu). V JavaFX ich do aplikácie možno pridať nasledujúcim spôsobom:
- Do hlavného okna aplikácie sa umiestní ovládací prvok typu
MenuBar, ktorý reprezentuje priestor, v ktorom sa budú jednotlivé ponuky zobrazovať. KaždýMenuBarsi udržiava zoznam ponúk v ňom umiestnených. - Každá ponuka (ako napríklad
Súbor,Formát, …) je reprezentovaná inštanciou triedyMenu, ktorá si okrem iného pamätá zoznam všetkých položiek danej ponuky. - Položka ponuky je reprezentovaná inštanciou triedy
MenuItem. Každej položke možno napríklad pomocou metódysetOnActionpriradiť akciu, ktorá sa má vykonať po jej zvolení používateľom. - Trieda
Menuje podtriedou triedyMenuItem, z čoho okrem iného vyplýva, že položkou ponuky môže byť aj ďalšia podponuka. - Špeciálne položky ponúk sú reprezentované inštanciami tried
CheckMenuItem(takúto položku ponuky možno zvolením zaškrtnúť resp. odškrtnúť) aSeparatorMenuItem(reprezentuje vodorovnú čiaru na vizuálne oddelenie častí ponuky).
V našej aplikácii teraz vytvoríme MenuBar s dvojicou ponúk Súbor a
Formát s nasledujúcou štruktúrou.
Súbor (Menu) Formát (Menu)
| |
|- Nový (Menu) --- Prázdny súbor (MenuItem) |- Písmo... (MenuItem)
| | |
| |- Náhodné cifry (MenuItem) |- Zalamovať riadky (CheckMenuItem)
|
|- Otvoriť... (MenuItem)
|
|- Uložiť (MenuItem)
|
|- Uložiť ako... (MenuItem)
|
|--------------- (SeparatorMenuItem)
|
|- Koniec (MenuItem)
Vytvorenie takýchto ponúk realizujeme nasledujúcim kódom (v ktorom
ponuky a ich položky reprezentujeme ako premenné inštancie triedy
Editor, kým MenuBar vytvárame iba lokálne v metóde start).
// ...
public class Editor extends Application {
// ...
private Menu mFile;
private Menu mFileNew;
private MenuItem miFileNewEmpty;
private MenuItem miFileNewRandom;
private MenuItem miFileOpen;
private MenuItem miFileSave;
private MenuItem miFileSaveAs;
private MenuItem miFileExit;
private Menu mFormat;
private MenuItem miFormatFont;
private CheckMenuItem miFormatWrap;
// ...
@Override
public void start(Stage primaryStage) {
// ...
MenuBar menuBar = new MenuBar();
borderPane.setTop(menuBar);
mFile = new Menu("Súbor");
mFileNew = new Menu("Nový");
miFileNewEmpty = new MenuItem("Prázdny súbor");
miFileNewRandom = new MenuItem("Náhodné cifry");
mFileNew.getItems().addAll(miFileNewEmpty, miFileNewRandom);
miFileOpen = new MenuItem("Otvoriť...");
miFileSave = new MenuItem("Uložiť");
miFileSaveAs = new MenuItem("Uložiť ako...");
miFileExit = new MenuItem("Koniec");
mFile.getItems().addAll(mFileNew, miFileOpen, miFileSave, miFileSaveAs, new SeparatorMenuItem(), miFileExit);
mFormat = new Menu("Formát");
miFormatFont = new MenuItem("Písmo...");
miFormatWrap = new CheckMenuItem("Zalamovať riadky");
miFormatWrap.setSelected(false); // Nie je nutne, kedze false je tu vychodzia hodnota
mFormat.getItems().addAll(miFormatFont, miFormatWrap);
menuBar.getMenus().addAll(mFile, mFormat);
// ...
}
}
K dôležitejším položkám môžeme priradiť aj klávesové skratky.
// ...
import javafx.scene.input.*;
// ...
@Override
public void start(Stage primaryStage) {
// ...
miFileNewEmpty.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN)); // Ctrl + N
miFileOpen.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCombination.CONTROL_DOWN)); // Ctrl + O
miFileSave.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)); // Ctrl + S
// ...
}
V rámci metódy updateOpenedFileInformation ešte môžeme zabezpečiť, aby
položka miFileSave bola aktívna práve vtedy, keď má argument
hasChanged hodnotu true (v opačnom prípade nie je čo ukladať).
private void updateOpenedFileInformation(File file, boolean hasChanged) {
// ...
miFileSave.setDisable(!hasChanged);
// ...
}
Výsledný vzhľad aplikácie je na obrázku vpravo.
Kontextové ponuky (ContextMenu)
Ďalším užitočným typom ponúk sú kontextové (resp. vyskakovacie) ponuky,
ktoré sa zobrazia po kliknutí na nejaký ovládací prvok pravou myšou.
Všimnime si, že TextArea už prichádza s prednastavenou kontextovou
ponukou. Chceli by sme teraz túto ponuku nahradiť vlastnou, obsahujúcou
rovnaké dve položky ako ponuka mFormat (budeme však musieť tieto
položky vytvárať nanovo, pretože každá položka môže patriť iba do
jedinej ponuky).
Jediný rozdiel oproti tvorbe hlavnej ponuky bude spočívať v použití
inštancie triedy
ContextMenu.
Tú následne pomocou metódy setContextMenu priradíme ako kontextovú
ponuku ovládaciemu prvku textArea. Výsledný vzhľad kontextovej ponuky
je na obrázku vpravo.
public class Editor extends Application {
// ...
private ContextMenu contextMenu;
private MenuItem cmiFormatFont;
private CheckMenuItem cmiFormatWrap;
// ...
@Override
public void start(Stage primaryStage) {
// ...
contextMenu = new ContextMenu();
textArea.setContextMenu(contextMenu);
cmiFormatFont = new MenuItem("Formát písma...");
cmiFormatWrap = new CheckMenuItem("Zalamovať riadky");
cmiFormatWrap.setSelected(false);
contextMenu.getItems().addAll(cmiFormatFont, cmiFormatWrap);
// ...
}
}
Priradenie udalostí k jednotlivým položkám ponúk
Môžeme teraz k jednotlivým položkám ponúk (okrem položiek typu
CheckMenuItem) priradiť ich funkcionalitu, ktorá bude zatiaľ
pozostávať z volania metód s takmer prázdnym telom. Všetky tieto
metódy budú mať návratový typ boolean, pričom výstupná hodnota bude
hovoriť o tom, či sa zamýšľaná akcia podarila alebo nie – táto črta sa
nám zíde neskôr.
public class Editor extends Application {
// ...
private boolean newEmptyAction() {
return true; // Neskor nahradime zmysluplnym telom metody
}
private boolean newRandomAction() {
return true;
}
private boolean openAction() {
return true;
}
private boolean saveAction() {
return true;
}
private boolean saveAsAction() {
return true;
}
private boolean exitAction() {
return true;
}
private boolean fontAction() {
return true;
}
// ...
@Override
public void start(Stage primaryStage) {
// ...
miFileNewEmpty.setOnAction(event -> newEmptyAction());
miFileNewRandom.setOnAction(event -> newRandomAction());
miFileOpen.setOnAction(event -> openAction());
miFileSave.setOnAction(event -> saveAction());
miFileSaveAs.setOnAction(event -> saveAsAction());
miFileExit.setOnAction(event -> exitAction());
miFormatFont.setOnAction(event -> fontAction());
// ...
cmiFormatFont.setOnAction(event -> fontAction());
// ...
}
}
Previazanie vlastností
Na implementáciu funkcionality položiek miFormatWrap a cmiFormatWrap
použijeme ďalšiu črtu vlastností – možnosť ich (obojstranného)
previazania. Pri zmene niektorej z vlastností sa potom automaticky
zmenia aj všetky vlastnosti s ňou previazané. V našom prípade navzájom
previažeme vlastnosti hovoriace o zaškrtnutí položiek miFormatWrap a
cmiFormatWrap a tiež vlastnosť hovoriacu o zalamovaní riadkov v
textovom priestore textArea.
@Override
public void start(Stage primaryStage) {
// ...
textArea.wrapTextProperty().bindBidirectional(miFormatWrap.selectedProperty());
textArea.wrapTextProperty().bindBidirectional(cmiFormatWrap.selectedProperty());
// ...
}
- Kým obojstranné previazanie vlastností realizujeme metódou
bindBidirectional, jednostranné previazanie možno realizovať metódoubind. Vlastnosť typu implementujúceho rozhranieProperty<T>pritom možno jednostranne previazať nielen s inou vlastnosťou typu implementujúceho toto rozhranie, ale možno ju metódoubindnaviazať aj na ľubovoľnú hodnotu typu implementujúceho rozhranieObservableValue<T>. - Previazanie vlastností možno využiť aj v rôzličných ďalších
situáciách. Užitočným cvičením môže byť napísať aplikáciu, v
ktorej hlavnom okne je vykreslený kruh, ktorého polomer ostáva rovný
jednej tretine menšieho z rozmerov okna (a to aj v prípade, že sa
rozmery okna zmenia). Pri tejto úlohe sa zídu metódy triedy
Bindings. - Viac sa o vlastnostiach a ich previazaní možno dočítať napríklad v tomto tutoriáli.
Jednoduché dialógy (Alert)
Naším najbližším cieľom teraz bude implementácia metód newEmptyAction,
newRandomAction a exitAction. Spoločnou črtou týchto akcií je, že
vyžadujú zatvorenie práve otvoreného dokumentu. V takom prípade by sme
ale chceli – pokiaľ boli v práve otvorenom dokumente vykonané nejaké
neuložené zmeny – zobraziť výzvu na uloženie dokumentu ako na obrázku
nižšie.
Na zobrazenie takejto výzvy využijeme jeden z jednoduchých štandardných
dialógov – inštanciu triedy
Alert.
// ...
import java.util.*; // Kvoli triede Optional
// ...
public class Editor extends Application {
// ...
private boolean saveBeforeClosingAlert() {
if (openedFileChanged) { // Ak dokument nie je ulozeny
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); // Vytvor dialog typu Alert, variant CONFIRMATION
alert.setTitle("Uložiť súbor?"); // Nastav titulok dialogu
alert.setHeaderText(null); // Dialog nebude mat ziaden "nadpis"
alert.setContentText("Uložiť zmeny v súbore?"); // Nastav text dialogu
ButtonType buttonTypeYes = new ButtonType("Áno"); // Vytvor instancie reprezentujuce typy tlacidiel
ButtonType buttonTypeNo = new ButtonType("Nie");
// Vdaka druhemu argumentu sa bude ako tlacidlo "Zrusit" spravat aj "krizik" v dialogovom okne vpravo hore:
ButtonType buttonTypeCancel = new ButtonType("Zrušiť", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeNo, buttonTypeCancel); // Nastav typy tlacidiel
Optional<ButtonType> result = alert.showAndWait(); // Zobraz dialog a cakaj, kym sa zavrie
if (result.get() == buttonTypeYes) { // Pokracuj podla typu stlaceneho tlacidla
return saveAction();
} else if (result.get() == buttonTypeNo) {
return true;
} else {
return false;
}
} else { // Ak je dokument ulozeny, netreba robit nic
return true;
}
}
// ...
}
Dialóg typu Alert by ideálne mal obsahovať práve jedno tlačidlo,
ktorého „dáta” sú nastavené na CANCEL_CLOSE. V opačnom prípade sa
„krížik” v pravom hornom rohu dialógového okna nemusí správať
korektne (v niektorých prípadoch sa dialógové okno po kliknutí naň
dokonca ani nemusí zavrieť). Toto obmedzenie sa ale dá eliminovať
ďalšími nastaveniami dialógu.
Môžeme teraz pristúpiť k implementácii spomínaných troch akcií.
public class Editor extends Application {
// ...
private static final int N = 1000; // V metode newRandomAction budeme generovat N riadkov o N nahodnych cifrach
// ...
private boolean newEmptyAction() {
if (saveBeforeClosingAlert()) { // Pokracuj len ak sa podarilo zavriet dokument
updateOpenedFileInformation(null, true); // Nebude teraz otvoreny ziaden subor...
textArea.clear(); // Zmazeme obsah textoveho priestoru textArea
return true;
} else {
return false;
}
}
private boolean newRandomAction() {
if (newEmptyAction()) { // Skus vytvorit novy subor a pokracuj len, ak sa to podarilo
StringBuilder builder = new StringBuilder(); // Vygeneruj retazec o N x N nahodnych cifrach
Random random = new Random();
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
builder.append(random.nextInt(10));
}
builder.append(System.lineSeparator());
}
textArea.setText(builder.toString()); // Vypis vygenerovany retazec do textoveho priestoru textArea
return true;
} else {
return false;
}
}
private boolean exitAction() {
if (saveBeforeClosingAlert()) { // Pokracuj len ak sa podarilo zavriet dokument
Platform.exit(); // Ukonci aplikaciu
return true;
} else {
return false;
}
}
}
Ďalšie typy jednoduchých dialógov
V JavaFX možno využívať aj ďalšie preddefinované jednoduché dialógy – od
dialógu Alert s odlišným AlertType až po dialógy ako
TextInputDialog
alebo
ChoiceDialog.
- Viac sa o preddefinovaných jednoduchých dialógoch v JavaFX možno dočítať napríklad tu.
Zatvorenie hlavného okna aplikácie „krížikom”
Metódu exitAction(), ktorá sa vykoná zakaždým, keď používateľ zvolí v
hlavnej ponuke možnosť Súbor --> Koniec, sme implementovali tak, aby
sa najprv zobrazila prípadná výzva na uloženie súboru. Táto výzva pritom
v niektorých prípadoch môže ukončeniu aplikácie aj zamedziť (napríklad
keď používateľ klikne na tlačidlo Zrušiť alebo keď bude tlačidlom
Zrušiť ukončený ukladací dialóg).
Ak ale používateľ aplikáciu zavrie kliknutím na „krížik” v pravom hornom rohu okna, aplikácia sa zavrie bez akejkoľvek ďalšej akcie. Chceli by sme pritom, aby sa vykonali rovnaké operácie, ako pri ukončení aplikácie pomocou položky hlavnej ponuky. To môžeme zariadiť nastavením spracúvateľa udalosti, ktorá vznikne pri požiadavke na zatvorenie okna. Ak túto udalosť v rámci jej spracovania skonzumujeme, zatvoreniu okna sa v konečnom dôsledku zamedzí.
public class Editor extends Application {
// ...
private boolean handleStageCloseRequest(WindowEvent event) {
if (saveBeforeClosingAlert()) { // Ak sa podarilo zavriet subor
return true;
} else { // Ak sa nepodarilo zavriet subor ...
event.consume(); // ... nechceme ani zavriet okno
return false;
}
}
// ...
@Override
public void start(Stage primaryStage) {
// ...
primaryStage.setOnCloseRequest(event -> handleStageCloseRequest(event));
// ...
}
}
Zvyšnú funkcionalitu aplikácie implementujeme nabudúce.