Keď som ešte blahej pamäti programoval hračičkové aplikácie vo Visual Basicu, jednou z prvých vecí v novom megasoftvéri bolo dialógové okno About Box / O aplikácii. Vôbec neprekážalo, že sa po prvom spustení nedalo nič robiť (akurát pozerať do prázdneho okna), ale hlavné bolo menu s krásnou položkou!
About Box je tiež skvelý príklad modálneho okna (modal window). Rozdiel medzi modálnymi a klasickými oknami raz ktosi veselo popísal heslom
modálne sú tie, kde sa nedá klikať do okna za ním a keď to robím, tak Windows pípa.
A naozaj, modálne okno odpútava pozornosť používateľa od hlavného okna a presmeruje jeho pozornosť na dôležitejšiu úlohu, alebo ho upozorní na Naozaj Veľmi Dôležitý Fakt.
Modálne okná v Jave
Modálne okná v Jave sú pomerne jednoduché. Teda; povedzme. Typickému oknu v swingovej aplikácii zodpovedá trieda JFrame
. Je určená pre hlavné okná, ktoré sú nezávislé od iných okien. Na Windowse ich spoznáme ľahko: dajú sa maximalizovať a minimalizovať.
Alternatívou je JDialog
(sledujte názov, dialóg!). Slúži pre dočasné informácie a veľmi často je otvárané z nejakého iného okna (vlastníka). Najlepšie sa hodí pre… modálne okná! Kým JFrame
neviete otvoriť ako modálne okno, s JDialog
om to ide bez problémov.
Klasický dialóg v duchu About Box môžeme navrhnúť oddedením od JDialogu
. Ak sme si istí, že dialóg bude vždy len modálny, stačí prekryť konštruktor JDialog(Owner)
.
package sk.upjs.ics.novotnyr.modal;
import java.awt.Window;
import javax.swing.*;
import javax.swing.border.*;
public class AboutDialog extends JDialog {
public AboutDialog(Window owner) {
super(owner, "About...", JDialog.DEFAULT_MODALITY_TYPE);
JLabel label = new JLabel("Modal Dialog Demo v 1.0");
add(label);
}
}
Predovšetkým zavoláme rodičovský konštruktor, kam pošleme vlastníka (owner
) a popisný reťazec, ktorý sa objaví v záhlaví okna. Tretí parameter je typ modality: preddefinovaný konštanta DEFAULT_MODALITY_TYPE
zapne štandardné správanie… teda nastaví dialóg ako modálny.
<div class="note">
Typ modality je k dispozícii od JDK 1.6. Doposiaľ sa využívali konštruktory, kde modalita mohla byť len booleovsky zapnutá alebo vypnutá. Nové nastavenie modality berie do úvahy nuansy novších správcov okien. Detaily o type modality možno nájsť v <a href="http://docs.oracle.com/javase/7/docs/api/java/awt/Dialog.ModalityType.html">dokumentácii</a>.
</div>
Ak chceme našu triedu vidieť v akcii, môžeme si do nej dodať dočasný main()
:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AboutDialog aboutDialog = new AboutDialog(null);
aboutDialog.setVisible(true);
aboutDialog.dispose();
}
});
}
}
Ak to spustíme, čo presne sa stane? Budeme vidieť škaredé mikrookno, zrejme v ľavom hornom rohu. Natiahnuť si ho budeme musieť sami. Upravíme to neskôr.
Zobrazovanie a skrývanie modálnych okien
Vytvorenie okna zodpovedá vytvoreniu inštancie. Keďže ide o biedny testovací príklad, vlastníka sme dočasne nastavili na null
— dialóg je prvé okno v systéme.
setVisible(true)
blokuje
Ďalej celé okno zobrazíme cez setVisible()
, ale pozor! V prípade modálnych okien volanie tejto metódy s true
blokuje ďalšie vykonávanie metódy dovtedy, dokiaľ sa okno nezavrie. To je prirodzené správanie. Sme uprostred nejakej operácie, vyhodíme upozornenie a pokiaľ ho používateľ nezoberie na vedomie zatvorením, tak nepokračujeme v programe.
Ak používateľ zatvorí okno cez systémové zatváradlo (na Windowse červené X), vykonávanie pokračuje.
Zatvorené okno treba odpratať
Na rozdiel od bežných objektov, ktoré v Jave nemusíme upratovať, musíme zatvorené okno upratať alebo dispose()
núť. Dispose()nuté okno uvoľní systémové prostriedky súvisiace s réžiou a vykresľovaním grafiky okna a vráti ich späť systému.
aboutDialog.dispose();
Ak tento riadok vynecháme, dialóg sa síce schová, ale stále bude prítomné v aplikácii a bude žrať systémové prostriedky. Dôsledok pre používateľa je pomerne nečakaný: používateľ zo svojho pohľadu zatvorí dialóg, ale v skutočnosti ho len schová. V tomto príklade je to ešte záludnejšie: ak dialóg zavriete, aplikácia stále pobeží, ale nebudete s ňou môcť interagovať.
Šikovníkom môže napadnúť jeden trik: nastaviť dialógu správanie sa v prípade, že je okno zavreté iksnutím:
aboutDialog.setDefaultOnCloseOperation(DISPOSE_ON_CLOSE);
To znamená, že okno sa po zatvorení cez systémové X
automaticky dispose()
ne. Je to lákavé riešenie, a má svoju logiku (okno sa samo postará o svoje upratanie po zatvorení), ale niektorým vývojárom je pohodlnejšie spárovať upratovanie okna s vytváraním jeho inštancie práve tak, ako sa otváranie súboru páruje s jeho zatváraním. (= ku každému new
patrí dispose()
).
Zestetičňovanie dialógu
Zmena veľkosti
Predovšetkým zavolajme metódu pack()
, ktorá prispôsobí rozmery okna rozmerom komponentov v ňom, čím predídeme stavu „vidím len hlavičku a obsah musím zobraziť ťahaním”.
Vycentrovanie
Centrovanie dialógu v strede obrazovky sa dosahuje cheatom. Metóda umožňuje relatívne umiestnenie vzhľadom k inému komponentu (popravde, z dokumentácie som správanie nepobral), ale ak uvedieme null
ový komponent, dialóg sa vycentruje. Pračudesné, ale je to i v dokumentácii.
setLocationRelativeTo(Component c)
Zákaz zväčšovania
Načo zväčšovať About Box?
setResizable(false);
Viac luftu
Label s textom by si zaslúžil viac luftu na okolí. Pridajme okolo neho 15pixelový vzduch, teda okraj, teda border.
Border border = BorderFactory.createEmptyBorder(15, 15, 15, 15);
// ... //
label.setBorder(border);
Výsledný konštruktor
public AboutDialog(Window owner) {
super(owner, "About...", JDialog.DEFAULT_MODALITY_TYPE);
Border border = BorderFactory.createEmptyBorder(15, 15, 15, 15);
JLabel label = new JLabel("Modal Dialog Demo v 1.0");
label.setBorder(border);
add(label);
setLocationRelativeTo(null);
setResizable(false);
pack();
}
Volanie modálneho okna z JFrame
Dodajme si teraz do projektu skutočné hlavné okno. Oddedíme od JFrame
klasickým spôsobom, dodáme jeden JButton
a celé to následne sfunčkníme a zestetičníme. Dodáme si ešte aj metódu main()
, kde náš frame zobrazíme bežným spôsobom.
package sk.upjs.ics.novotnyr.modal;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MainForm extends JFrame {
public MainForm() {
JButton button = new JButton("About...");
add(button, BorderLayout.PAGE_START);
setExtendedState(MainForm.MAXIMIZED_BOTH);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainForm mainForm = new MainForm();
mainForm.setVisible(true);
}
});
}
}
Na tomto nie je nič výnimočné: tlačidlo pridáme na vrch okna (používa sa BorderLayout
, konštanta PAGE_START
je ekvivalentná starej konštantne pre sever, teda NORTH
) a okno maximalizujeme (len tak pre zábavu).
Spustíme si a poobdivujeme…
…a nerobí to nič.
Dodajme k tlačidlu jeden action listener, ktorý obslúži klik.
JButton button = new JButton("About...");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
AboutDialog aboutDialog = new AboutDialog(MainForm.this);
aboutDialog.setVisible(true);
aboutDialog.dispose();
}
});
Všimnime si filozofiu prevzatú z main()
vo vedľajšej triede. Rozdiel je v
AboutDialog aboutDialog = new AboutDialog(MainForm.this);
V tomto prípade potrebujeme do dialógu dodať vlastníka dialógu, teda „to okno na pozadí, do ktorého sa nebude dať klikať”. Vlastníkom je MainForm
, na ktorý sa odkážeme cez this
, ale… obslužný kód pre tlačidlo je vo vnútri ActionListenera
, čo je vnútorná trieda v triede MainForm
. Použitie this
by sa odkazovalo na objekt typu ActionListener
, čo je blbosť. Potrebujeme sa odkázať na „mňa”, čím sa myslí inštancia MainForm
u, čo dosiahneme cez
MainForm.this
Hľa, hotovo
Poskúšajte si to. Klikajte na tlačidlo About…, otvorí sa náš známy dialóg About Box a klikať do hlavného okna nebudete až dovtedy, kým About Box nezavriete.
Toť modálne okno v akcii!