V minulém dílu jsme si řekli, jakým způsobem v Javě vytvoříme jednoduchou grafickou aplikaci. Dnes si ukážeme několik dalších komponent a ukážeme si, jakým způsobem jim lze přiřazovat chování. Hlavně si však předvedeme několik předdefinovaných správců rozmístění (layout managerů), které nám umožní skládat jednotlivé komponenty do smysluplných celků.
Každé uživatelské rozhraní je postaveno na interakci uživatel-aplikace. Uživatel klikne na tlačítko, aplikace zobrazí dialogové okno, uživatel vyplní data a odklepne okno. Otázkou však zůstává, jak tento proces implementovat.
Naivní řešení by bylo, aby se objekt dialogu nejprve periodicky ptal tlačítka, jestli už bylo zmáčknuto, a až by se dozvěděl, že ano, tak by se zobrazil. Pak by se periodicky ptal formuláře, jestli již byl vyplněn a odklepnut, a v okamžiku kdy ano, tak by se dialog opět zrušil. Toto řešení by však s sebou neslo řadu nevýhod – tou největší je stanovení intervalu, ve kterém se mají komponenty dotazovat na stav. Pokud bychom jej stanovili malý (třeba jednou za sekundu), tak by uživatel pozoroval velké prodlevy. Pokud bychom jej stanovili příliš malý (tisíckrát za vteřinu), tak by tyto dotazy spolehlivě zahltily celou aplikaci.
Řešením této zapeklité situace je návrhový vzor observer, který říká, že pozorovatelné komponenty musí fungovat jako registr pozorovatelů. V okamžiku provedení uživatelské akce pak pozorovaná komponenta upozorní všechny registrované odběratele (ve Swingu se pozorovateli říká listener (posluchač)).
Pokud tento vzor vztáhneme na náš příklad s dialogovým oknem, tak se okno nejprve zaregistruje u tlačítka. Tlačítko v okamžiku kliku upozorní okno a toto se zobrazí, aniž by se muselo jakýmkoliv způsobem periodicky dotazovat na stav tlačítka.
Ukažme si výše zmíněný příklad v praxi. Při jeho implementaci využijeme kromě již známé komponenty JFrame také komponentu JButton (tlačítko), JOptionPane (pomocná třída pro tvorbu dialogových oken) a konečně interface ActionListener, pomocí něhož implementujeme akci zobrazení dialogu.
Pro jednoduchost bude dialog obsahovat pouze zprávu obsahující text uvedený na zmáčknutém tlačítku.
V metodě main asynchronně zavoláme metodu createAndShowGUI. Připomeňme si, že Swing není vláknově bezpečný a veškeré akce týkající se rozhraní musí být uskutečněny v event dispatching threadu (což zajišťuje právě výše uvedený kód).
V samotné metodě createAndShowGUI vytvoříme uživatelské rozhraní. Nejprve vytvoříme nové okno – JFrame. Poté do něj vložíme nové tlačítko, kterému napřed přiřadíme text při jeho konstrukci a akci voláním addActionListener. Na závěr pak provedeme povinné operace – nastavíme, že po kliknutí na křížek okna dojde k uzavření celé aplikace, přizpůsobíme velikost okna obsaženým komponentám (tlačítku), posuneme okno na pozici [100, 100] a samotné okno zobrazíme.
Implementace akce je velmi jednoduchá. Pouze zavoláme metodu na pomocné třídě JOptionPane. Tato pak zajistí zobrazení modálního okna s příslušným titulkem, textem, informačním piktogramem a tlačítkem na odklepnutí dialogu (více o této třídě se lze dočíst zde). Veškeré operace v listenerech jsou prováděny event dispatching threadem – můžeme zde proto bez obav provádět změny v uživatelském rozhraní.
Jedním ze základních způsobů, jak vytvořit vlastní komponentu, je ji oddědit od nějaké již existující. Pokud bychom htěli naše tlačítko použít vícekrát, tak bychom jej implementovali pomocí veřejné třídy dědící z JButton:
V tomto případě si vystačíme s překrytím konstruktoru. Nejprve zavoláme konstruktor předka a předáme mu text, který má být vypsán na tlačítku – konstruktor předka bychom v případě Swingu měli volat (téměř) vždy, protože v předkovi obvykle dochází k inicializaci grafického kontextu a datového modelu. Poté přidáme akci, která se provede po kliknutí na tlačítko. Zde si všimněme konstrukce TitleJButton.this, která značí, že se odvoláváme na this kontext obalujícího objektu typu TitleJButton. Prosté volání this by nás odkázalo na kontext objektu listeneru. JPanel Všem třídám, které dědí z třídy Container (dokumetace), lze přiřadit layout manager (správce rozmístění). Správce rozmístění zajistí, že přidané podkomponenty budou vyskládány určitým způsobem (vizte dále).
Typickým příkladem kontejneru je JPanel (dokumentace), jehož využíváme primárně pro tvorbu složených komponent. JPanel sám o sobě nemá žádnou vizuální reprezentaci, stejně tak nepřidává žádné chování.
Nejjednodušším rozmisťovačem je FlowLayout, který umišťuje komponenty do řádku jednu za druhou v pořadí, v němž byly do přidány do daného kontejneru. V případě, že se komponenty nevejdou do jednoho řádku, tak zbylé přetečou do řádku nového.
Ve výše uvedeném kódu je pro nás zajímavá pouze metoda createLayoutPanel, jež vytváří JPanel s příslušným layoutem a několika obsaženými komponentami (tlačítky). V dalších příkladech správců rozmístění si již budeme uvádět pouze ji.
BorderLayout zarovnává komponenty do světových stran a na střed (sever, jih, východ, západ, střed). BorderLayout je jedním z nejpoužitelnějších správců rozmístění, jelikož téměř každá aplikace má nějakou hlavičku (např. menu umístěné na severu), obsah umístěný na středu a patičku na jihu. Na východě a západě se pak často nachází nějaký typ kontextové navigace (obvykle nějaká stromová komponenta).
Všechny umístěné komponenty jsou vždy roztaženy tak, aby zabraly veškeré dostupné místo.
BoxLayout umísťuje komponenty buď do jednoho řádku nebo sloupce podle nastavení, které předáme jeho konstruktoru. Na rozdíl od FlowLayoutu komponenty nikdy nepřetečou do dalšího řádku (sloupce), ale jsou vždy zmenšeny nebo oříznuty tak, aby se vešly do daného prostoru.
Pro tvorbu BoxLayoutu také můžeme použít třídu Box, která nám umožní jednak vytvářet své instance (Box je kontejner, který implicitně obsahuje BoxLayout jako správce rozmístění) a za druhé pak s jeho pomocí můžeme vytvářet neviditelné komponenty tyčí (strut) a tmelu (glue).
Horizontální a vertikální tyče jsou komponenty, které mají buď nulovou výšku, nebo nulovou šířku a zajišťují, že se předchozí a následující komponenty nepřiblíží více, než je délka tyče. Komponenta tmelu se pak snaží zabrat veškerý přebytečný prostor – slouží jako výplň.
Tento příklad nám zároveň ukazuje, že je možné (a žádoucí) kombinovat více správců rozmístění. Zde pro rozmístění na nejvyšší úrovní používáme BorderLayout, který zajistí roztažení komponent na maximální možné rozměry (při zvětšování má vždy přednost komponenta na středu). V levém panelu pak umísťujeme do boxu tři tlačítka a ponecháváme mezi nimi mezery 10 a 30 pixelů. V pravém sloupci pak umísťujeme dvě tlačítka a dáváme mezi ně tmel, čímž zajistíme, že mezi nimi bude situován veškerý volný prostor – tj. jedno tlačítko bude nahoře a druhé dole. Nakonec oběma boxům přidáme ohraničení s popiskem.
GridLayout, jak již název napovídá, umisťuje komponenty do pravoúhlé mřížky. Konstruktoru můžeme předat 2 parametry – počet komponent na šířku a na výšku – z nichž může maximálně jeden nulový. Nulový argument znamená, že se v daném rozměru bude mřížka automaticky natahovat.
Posledním layoutem, který si dnes představíme, je GridBagLayout. Jedná se o zobecnění GridLayoutu, ve němž může komponenta zabírat více buněk. GridBagLayout je ze všech uvedených správců nejsilnější a nejkomplexnější. Na druhou stranu je však mnohdy snazší (a přehlednější) použít složeninu několika jednodušších správců.
Pro každou komponentu umístěnou v GridBagLayoutu musíme vytvořit objekt typu GridBagConstraints, který specifikuje její pozici pomocí následujících vlastností:
Rozmísťování komponent pomocí layout managerů je poměrně otravná a náročná činnost, které by však měl rozumět každý programátor desktopových aplikací. Existuje však ještě jedna možnost, jak jednodušeji dosáhnout lepšího cíle. Jsou jí různé vizuální klikací nástroje, které obsahuje téměř každé vývojové prostředí.
Tyto nástroje mohu doporučit pro rychlý vývoj aplikací a pro tvorbu složitých layoutů, které by napsat ručně bylo přinejmenším obtížné. Na druhou stranu je vhodné psát jednoduché aplikace a znovupoužitelné komponenty ručně, jelikož je výsledný kód mnohem čistější. Dále je třeba mít na paměti, že programátor by měl při použití každého generátoru vědět, co výsledný kód dělá a proč to dělá.