Java (26) - tvorba GUI
Při návrhu a implementaci grafického uživatelského rozhraní se lze vydat
dvěma hlavními cestami: buď tvořit ručně (přímým psaním kódu), nebo využít
grafické návrhové prostředí. Na obě se nyní podíváme, samozřejmě i včetně
kombinace obou přístupů. Při tom se dostanou ke slovu některé více či méně
zajímavé objektové třídy.
24.7.2006 09:00 |
Lukáš Jelínek
| Články autora
| přečteno 56198×
Okna, dialogy, rámy
Prvky grafického rozhraní obvykle neplavou "ve vzduchu" (tedy jen tak na ploše
obrazovky), i když to v některých případech bývá. Mnohem častěji je
umísťujeme do různých oken. Před návrhem GUI nějaké aplikace se vyplatí dobře
znát vlastnosti základních druhů oken, se kterými se ve frameworku Swing
pracuje.
Okno
Základním prvkem je "obyčejné" okno, reprezentované třídou JWindow . Nemá žádný
rámeček, titulkovou lištu ani ovládací tlačítka. Přímo ho využijeme málokdy,
uvádím ho hlavně proto, že má většinu vlastností, které souvisejí s vkládáním
komponent GUI. Okno je přímo navázáno na nativní objekt (např. v X Window
Systemu).
Každé okno obsahuje určité plochy, které mají svůj význam pro fungování okna
a s každou z nich se pracuje specifickým způsobem:
- Kořenová plocha (
rootPane ) - instance třídy JRootPane , obsahuje další
plochy, žádný zvláštní význam nemá.
- "Skleněná" plocha (
glassPane ) - překrývá shora celou oblast okna,
umožňuje sledovat pohyb kurzoru myši přes okno. Může být tvořena prakticky
libovolnou grafickou komponentou, lze do ní tedy i kreslit. Za normálních
okolností je skrytá.
- "Vrstvená" plocha (
layeredPane ) - reprezentována instancí JLayeredPane .
Má důležitý význam, a to hloubkové (souřadnice z ) uspořádání komponent
v různých situacích. Týká se to např. plovoucích panelů, vyskakovacích oken
nebo přetahování komponent.
- Obsahová plocha (
contentPane ) - sem se vkládají běžné komponenty GUI.
S touto plochou jsme se již setkali v příkladu na jednoduchý program s GUI.
Tato plocha je vložena do vrstvené plochy a opět ji může tvořit jakákoli komponenta.
- Nabídková lišta (
menuBar ) - tvořena instancí JMenuBar . Používá se jen
v případě potřeby. Je součástí vrstvené plochy.
Zdaleka nejčastěji pracujeme s obsahovou plochou. Buď použijeme tu, která
je v okně již obsažena (ale pak předem neznáme její vlastnosti, resp. ani
třídu), nebo nastavíme komponentu vlastní. Vhodnými kandidáty bývají třídy
JPanel (pro okna pevné velikosti), JScrollPane ,
JSplitPane apod.
Někdy využijeme také nabídkovou lištu. Práce s ní je triviální, ještě o tom
bude řeč.
Dialog
Smysl této komponenty (představované třídou JDialog ) je jasný - používá se pro
různé dialogy a pevná okna. Dialog má titulkovou lištu a ovládací tlačítka,
může být modální. Podobně jako okno, i dialog je na obrazovce tvořen
nativním grafickým objektem.
Rám
Pod tímto nepříliš výstižným termínem se skrývá okno s proměnnou velikostí.
Opět je to nativní objekt okenního systému. Ve Swingu ho reprezentuje třída
JFrame .
Vnitřní rám
Od předchozího se liší tím, že je to čistě objekt Swingu (není reprezentován
nativně) a může existovat jen uvnitř jiné swingové komponenty. Typicky se
používá pro dokumentová okna v MDI programech. Jedná se o třídu
JInternalFrame .
Applet
Uvádím ho jen pro úplnost. Applet bývá umístěn na webové stránce a plní
podobné úkoly jako běžné okno. Třída má název JApplet .
Sestavení GUI aplikace
Vytváříme-li GUI ručně (bez pomoci grafického návrháře ve vývojovém prostředí),
je dobré si vše předem nakreslit na papír, nejlépe milimetrový. Takto si
připravíme rozvržení aplikace a pak už jen implementujeme chování grafických
komponent. Pokud nepotřebujeme pracovat s pevným rozmístěním komponent
(a používáme správce rozložení, layout managery - bude o nich řeč v jednom
z příštích dílů), je to ještě jednodušší a ruční práce je velice efektivní.
Můžeme přímo používat již existující třídy nebo si od nich vytvářet potomky
- druhá možnost je lepší v případech, kdy potřebujeme nějak zásadněji změnit
chování některé komponenty nebo tehdy, chceme-li nějakou upravenou komponentu
používat opakovaně.
Příklad ruční tvorby
Pusťme se nyní do tvorby aplikace. Na příkladu bude nejlépe vidět, jak se
dá s GUI pracovat a jak to celé funguje. Budeme vytvářet jednoduchý textový
editor - bude umět založit nový text, otevřít existující soubor a uložit data
do zvoleného souboru. Kdo by chtěl nějakou funkci navíc (např. udržování
názvu souboru v programu, automatické ukládání, dotaz na zahození neuložených
dat, automatické zalamování řádků a podobně), jistě snadno přijde na to, jak
to udělat.
Třída editoru bude odvozena od třídy JFrame , o níž byla řeč výše. Lze to
samozřejmě udělat i v samostatné třídě a do JFrame nesahat. Začněme tedy:
public class Editor extends JFrame implements ActionListener {
private JScrollPane sp = null;
private JTextArea ta = null;
private JMenuBar mb = null;
private String sep = null;
Deklarujeme členské proměnné hlavních komponent. Není to nutné, ale pro
pozdější přístup se to hodí. Některé můžeme rovnou plně inicializovat, ovšem
pro lepší orientaci to ponechám na později. Ještě upozorním na proměnnou sep ,
která bude obsahovat oddělovač řádků (brzy vysvětlím).
Chvíli bych se zdržel u třídy JTextArea . Protože je ve Swingu důkladně využita
hierarchie tříd, projevuje se to i zde. Máme abstraktní třídu JTextComponent ,
která obsahuje základní funkcionalitu pro práci s textem. Neřeší však
implementační detaily, zejména způsob komunikace s uživatelem. Umožňuje jak
primitivní práci s textem (jako je to i v tomto příkladu), tak možnost použít
dokumentový aparát Swingu s mnohem rozsáhlejšími možnosti (složitější editace,
undo/redo, logické členění apod.). JTextArea je jednou z konkrétních implementací
JTextComponent , další je např. třída JTextField (jednořádkové textové pole),
která má sama o sobě ještě další potomky (např. JPasswordField pro zadávání
hesel).
public Editor() {
super();
init();
}
Konstruktor je jednoduchý a volá nejprve konstruktor předka a potom
inicializační metodu. Do té je vhodné umístit všechno, co se týká inicializace
komponenty. Můžeme pak mít více konstruktorů a z každého tuto metodu volat.
public void init() {
sep = System.getProperty("line.separator");
ta = new JTextArea();
Font f = Font.decode("Monospaced");
if (f != null)
ta.setFont(f);
sp = new JScrollPane(ta,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
setContentPane(sp);
První část inicializační metody nejprve nastaví již zmíněný oddělovač řádků
do podoby, jaká odpovídá platformě (tedy na GNU/Linuxu to bude "\n ") - oddělovač
budeme potřebovat při načítání souboru. Pak se vytvoří komponenta pro editaci
textu. Protože je vhodnější pracovat s neproporcionálním písmem, zkusíme ho
nastavit (pokud se to nepovede, zůstává původní písmo). A konečně poslední
část kódu vytvoří plochu s posuvníky (budou zobrazovány jen v případě potřeby)
a textovou oblast do ní vloží. Plocha se pak nastaví jako obsahová plocha
rámu. Protože nezasahujeme do nastavení správců rozložení, uplatní se výchozí
stav, který nám zajistí, že velikost textové oblasti bude odpovídat textu
uvnitř.
JMenu menu= new JMenu("Soubor");
menu.setMnemonicv(KeyEvent.VK_S);
JMenuItem mi = new JMenuItem("Nový", KeyEvent.VK_N);
mi.setActionCommand("new");
mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));
mi.addActionListener(this);
menu.add(mi);
mi = new JMenuItem("Otevřít...", KeyEvent.VK_O);
vmi.setActionCommand("open");
mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
mi.addActionListener(this);
menu.add(mi);
mi = new JMenuItem("Uložit...", KeyEvent.VK_U);
mi.setActionCommand("save");
mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
mi.addActionListener(this);
menu.add(mi);
mi = new JMenuItem("Konec", KeyEvent.VK_K);
mi.setActionCommand("quit");
mi.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
mi.addActionListener(this);
menu.add(mi);
mb = new JMenuBar();
mb.add(menu);
setJMenuBar(mb);
Tento poněkud delší úsek inicializátoru se zabývá přípravou nabídkové lišty.
Vytvoříme nabídku "Soubor" (žádné jiné nebudou), přidáme do ní potřebné položky
pro různé operace, a na závěr vytvoříme samotnou lištu a vložíme do ní menu.
Všimněte si několika věcí. Nabídka se bude otvírat zvolenou klávesou
(samozřejmě v kombinaci s Alt ), každá položka nabídky má svoji klávesu (která
se použije, je-li nabídka otevřená) a také klávesovou zkratku použitelnou
kdykoli. Optimální je, pokud si klávesa položky a aplikační klávesová zkratka
odpovídají (pokud to lze), ale často je to docela problém (máme zažité zkratky,
např. Ctrl-S pro uložení, a to se slovem "Uložit" moc dohromady nejde). Mohli
bychom ještě např. nastavit bublinovou nápovědu položek (setTooltipText() )
apod.
Opět malé zdržení - a to u třídy JMenuItem a jejích potomků. Tato třída
reprezentuje položku v menu a je rozšířením třídy AbstractButton , podobně
jako třeba JButton . Může mít pouhý text nebo i ikonu, ostatně jako
každá implementace abstraktního tlačíka (AbstractButton ). Potomkem třídy JMenuItem
je i třída JMenu , což znamená, že pokud se do menu vloží jiné menu (namísto
obyčejné položky), prostě se tím vytvoří další úroveň. Dále jsou tu také
potomci JCheckBoxMenuItem a JRadioButtonMenuItem , představující zaškrtávátko,
resp. přepínač v menu. Nejsme tedy omezeni na obyčejné položky, ale lze
pracovat i s tímto. Občas je v menu potřeba oddělovač - můžeme buď vložit instanci
třídy JSeparator nebo zavolat addSeparator() , obě cesty jsou rovnocenné.
Zvolené řešení reakce na výběr položek menu je jen jedno z mnoha. Kromě
rozlišení příkazu (action command) můžeme operace rozlišovat také podle
zdroje události. Jinou možností je vytvořit ke každé položce anonymní třídu
(implementující ActionListener ) a odtud pak volat metody operací. Každé řešení
má své pro i proti, u jednoduchých GUI na zvoleném postupu ale víceméně
nezáleží.
setTitle("Editor");
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocation(100, 100);
setSize(400, 300);
}
Tyto příkazy by měl již každý znát. Nastavují titulek, výchozí
zavírací operaci, polohu a velikost okna. Tím je inicializace dokončena.
public void actionPerformed(ActionEvent e) {
String s = e.getActionCommand();
if (s.equals("new"))
clear();
else if (s.equals("open"))
load();
else if (s.equals("save"))
save();
else if (s.equals("quit"))
dispose();
}
Obsluha událostí od položek menu. Je to snad zřejmé na první pohled, porovnává
se řetězec příkazu s definovanými hodnotami. Pro delší seznam by to bylo
operačně náročné (a bylo by lepší použít jiný způsob rozlišení), zde nám to
ale problémy nedělá.
public void clear() {
ta.setText("");
}
Vytvoření "nového souboru". Spočívá prostě v tom, že se textová oblast
vyprázdní.
public void load() {
JFileChooser fc = new JFileChooser();
fc.setDialogType(JFileChooser.OPEN_DIALOG);
if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File f = fc.getSelectedFile();
ta.setText("");
try {
BufferedReader br = new BufferedReader(new FileReader(f));
StringBuilder sb = new StringBuilder();
String s = "";
boolean fl = false;
while ((s = br.readLine()) != null) {
if (!fl)
sb.append(sep);
else
fl = true;
sb.append(s);
}
br.close();
ta.setText(sb.toString());
ta.setCaretPosition(0);
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "Soubor nelze otevřít.",
"Chyba", JOptionPane.ERROR_MESSAGE);
}
}
}
Načtení textu ze souboru. Swing má svoji implementaci dialogu pro práci se
soubory. Dialog toho umí mnohem víc, než se zde používá (např. filtraci
souborů, vícečetné výběry), ale nám stačí základní operace. Pokud výběr
souboru proběhl správně (nedošlo k žádné chybě a uživatel potvrdil výběr),
načteme soubor. Možností je opět více. Zvolil jsem čtení pomocí textového
bufferovaného streamu po řádcích. Protože metoda readLine() konce řádků
odřezává, opět je přidáme - vedlejším efektem (někdy vítaným, někdy ne) bude,
že se všechny konce řádků změní tak, že to odpovídá platformě. K sestavení
textu se použije třída StringBuilder . Po vložení řetězce do textové oblasti
se přesune kurzor na začátek (jinak by zůstal na konci).
Nyní nastal čas upozornit na velice zajímavou a důležitou třídu - JOptionPane .
Ta slouží pro práci s jednoduchými dialogy. Kromě toho, že s ní lze pracovat
obvyklým způsobem a vytvářet si dialogy podle potřeby, má také řadu statických
metod pro zobrazování primitivních informativních a potvrzovacích dialogů.
To je užitečné právě v takových případech, jako je tento - k oznamování chyb,
informování o ukončení časově náročných operací, dotazům typu ano/ne(/zrušit),
vložení jediné hodnoty atd. Doporučuji vydatně používat.
public void save() {
JFileChooser fc = new JFileChooser();
fc.setDialogType(JFileChooser.SAVE_DIALOG);
if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
File f = fc.getSelectedFile();
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
bw.write(ta.getText());
bw.close();
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "Soubor nelze uložit.",
"Chyba", JOptionPane.ERROR_MESSAGE);
}
}
}
K tomu snad není potřeba nic dodat. Od metody k načtení dat se liší prakticky
pouze tím, že se celý textový obsah uloží zavoláním jediné metody. Nyní už
chybí pouze hlavní metoda pro spuštění programu:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Editor m = new Editor();
m.setVisible(true);
}
});
}
Importy jsem jako obvykle vynechal. Po kompilaci a spuštění by se mělo objevit
okno, které bude mít nahoře nabídkovou lištu. Ta bude fungovat obvyklým
způsobem, pro ovládání půjde používat i klávesové zkratky.
Tvoříme s pomocníkem
Toto byla ukázka kompletně ruční tvorby aplikace. Hlavně u větších programů
s GUI je mnohdy efektivnější aspoň částečně využít grafické prostředí, ve
kterém si GUI vytvoříme "klikacím způsobem". V následujících odstavcích budu
hovořit o tvorbě téže aplikace v IDE
NetBeans verze 5, jiná prostředí se
používají podobně. Popis nebude ve stylu, aby to podle toho udělala cvičená
opice - spíše vyzdvihnu důležité body.
Předpokládám již založený projekt a v něm nějaký balík. Začneme vytvořením
potomka JFrame - např. z kontextové nabídky balíku:
New -> JFrame Form...
IDE se automaticky přepne do vizuálního návrháře GUI a můžeme začít "klikat".
Na plochu rámu vložíme komponentu JScrollPane a do ní pak
JTextFrame . Pro každý objekt nastavíme potřebné vlastnosti na kartě
vlastností (Properties).
Pak se přepneme do zobrazení zdrojového kódu (Source). Vygenerovaný kód je
barevně označen, je sbalený a nelze do něj přímo zasahovat. Do souboru můžeme nyní vložit
metody clear() , load() a save() . Není to ovšem úplně nutné, hned uvedu proč.
Ve vizuálním editoru pak přidáme nabídkovou lištu, nabídku a její položky
(stejné jako ručně psaného editoru). Klávesy položek a klávesové zkratky
se dají nastavit přímo na kartě vlastností. Následující obrázky ukazují
výslednou logickou strukturu GUI a panel s kartou vlastností:
Zbývá už jen nastavit spouštění jednotlivých operací. Nejjednodušší je
pro každou položku přes kontextovou nabídku zvolit Events -> Action
-> actionPerformed . Tím se do kódu vygeneruje kostra metody, kam se zapíše
reakce na danou událost (může to být přímo i výkonný kód, proto se můžeme
obejít bez samostatných metod pro jednotlivé operace). Kdo si rozbalí
generovaný blok kódu, zjistí, jak je to řešeno. IDE pro každý zdroj událostí
vytvoří anonymní třídu, v níž implementuje příslušné rozhraní. Odtud pak
volá metodu v naší hlavní třídě. Je to sice jednoduché a elegantní, ale u
větších GUI budeme mít úplnou záplavu anonymních tříd, což není úplně
nejvhodnější řešení. Pak je lepší postupovat jinak (např. si obsluhu událostí
napsat ručně), ale zde se s tím plně spokojíme.
Po dopsání implementace vygenerovaných metod je aplikace hotova (metoda
main() se generuje automaticky). Bylo-li vše provedeno správně, měla by se
navenek chovat úplně stejně, jako ta ručně
napsaná. Jak je vidět, psaní kódu (souvisejícího přímo s GUI) lze omezit na
minimum a současně se k fungující aplikaci dopracujeme velice rychle. Na druhou
stranu, výhodou je možnost obě cesty kombinovat a využívat je podle potřeby.
Seznamy, tabulky a stromy
Dosud jsme při tvorbě GUI využívali pouze jednoduché grafické komponenty.
Mnoho programů ale vyžaduje používání různých seznamů (ať už obyčejných či
rozbalovacích), stromů a tabulek. Zde se opět pořádně projeví výhody frameworku
Swing, protože nám poskytuje velmi příjemné mechanismy pro práci s těmito
grafickými komponentami a s daty, nad nimiž pracují. Příští díl seriálu bude
kompletně věnován této oblasti, protože ta si takovou pozornost jednoznačně
zaslouží.
Verze pro tisk
|
Příspívat do diskuze mohou pouze registrovaní uživatelé.
|
|

Vyhledávání software

Vyhledávání článků
28.11.2018 23:56 /František Kučera Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1
12.11.2018 21:28 /Redakce Linuxsoft.cz 22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář
6.11.2018 2:04 /František Kučera Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
4.10.2018 21:30 /Ondřej Čečák LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář
18.9.2018 23:30 /František Kučera Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
9.9.2018 14:15 /Redakce Linuxsoft.cz 20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business.
Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář
12.8.2018 16:58 /František Kučera Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář
16.7.2018 1:05 /František Kučera Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář
Více ...
Přidat zprávičku
 Poslední diskuze
31.7.2023 14:13 /
Linda Graham iPhone Services
30.11.2022 9:32 /
Kyle McDermott Hosting download unavailable
13.12.2018 10:57 /
Jan Mareš Re: zavináč
2.12.2018 23:56 /
František Kučera Sraz
5.10.2018 17:12 /
Jakub Kuljovsky Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?
Více ...
|