Minulý díl byl pro nás revoluční z pohledu využití zdrojů, jež nám moderní počítače nabízejí. Dnešní díl bude neméně přelomový – tentokrát z pohledu user experience – vysvětlíme si základy grafického rozhraní v Javě a pomocí knihovny Swing vytvoříme grafickou obdobu programu hello world. Na tyto základy pak navážeme v dalších dílech.
Grafické knihovny v Javě
Abstract Window Toolkit
Historicky první standardní knihovnou pro tvorbu grafického rozhraní v Javě bylo AWT (Abstract Window Toolkit). AWT je tzv. heavyweight knihovna, což znamená, že deleguje vykreslování jednotlivých grafických komponent na operační systém. Z toho vyplývá, že grafické rozhraní bude na každém operačním systému vypadat trochu jinak.
Swing
Druhou standardní knihovnou, jíž se budeme zabývat především, je Swing. Vnitřní fungování Swingu se od AWT velmi liší, jelikož se jedná o tzv. lightweight knihovnu, která provádí (téměř) veškeré kreslení komponent sama (tj. v Javě, nikoliv delegováním na systémové binárky).
Tento přístup s sebou nese řadu výhod – jednak dobrou přenositelnost jak samotné knihovny, tak programů v ní napsaných, za druhé pak širší škálu poskytovaných funkcí, jelikož se knihovna nemusí zabývat tím, zda-li daný systém konkrétní funkcionalitu podporuje.
Nevýhodou Swingu pak jsou vyšší náklady na vykreslování, kvůli kterým bylo javovské grafické rozhraní často kritizováno. Druhou stranou mince však je, že za většinu problémů s rychlostí rozhraní mohli programátoři, kteří ignorovali některé základní zákonitosti tvorby grafických aplikací – například, že vlákno, které kreslí rozhraní, nesmí dělat žádné další nákladné operace (protože rozhraní pak po dobu zpracování operace vymrzne...).
Společné vlastnosti AWT a Swingu
Ačkoliv Swingové komponenty z balíčku java.awt dědí, tak není vhodné tyto dvě knihovny kombinovat, jelikož komponenty samotné nejsou zcela kompatibilní (zejména na nižších verzích Javy).
Poslední zmínkou pak je, že obě knihovny jsou vícevláknové a ani jedna z nich není vláknově bezpečná (a ani nemůže být, protože synchronizace s sebou nese příliš vysoké náklady). Je tedy zapotřebí dávat velký pozor na to, abychom nemodifikovali stav jedné komponenty z více vláken, jelikož by to mohlo skončit havárií programu.
Zpracování událostí
Grafické rozhraní je založeno na zpracovávání událostí. Pod pojmem událost si můžeme představit jakoukoliv uživatelskou akci – zmáčknutí klávesy, přejetí kurzoru přes komponentu, focus na komponentu, klik myši, držení tlačítka myši, jeho puštění a tak podobně. Ve všech těchto případech je upozorněna naše aplikace prostřednictvím k tomu určeného vlákna, jež se ve Swingu nazývá event dispatching thread, a které provede příslušnou reakci.
Všimněme si, že v tomto případě se již Swing nechová jako knihovna, ale jako aplikace, která volá náš program, jenž tak vystupuje v roli knihovny. Této situaci, která je charakteristická tzv holywoodským principem (don't call us, we'll call you), říkáme inversion of control (inverze řízení). Proto také nebudeme Swing označovat jako knihovnu, ale jako rámec (framework).
Hello world aplikace
/** * Hello world - Swing framework * @author Pavel Micka */ public class HelloWorld { /** * @param args the command line arguments */ public static void main(String[] args) { //Asynchronne pridej do fronty udalosti zadost o vykresleni GUI SwingUtilities.invokeLater(new Runnable() { public void run() { createHelloWorld(); } }); } public static void createHelloWorld(){ JFrame frame = new JFrame("Hello world frame"); //okno frame.add(new JLabel("Hello world")); frame.pack(); //vyskladej komponenty tak, aby byla respektovana jejich vyska a sirka frame.setLocation(100, 100); //levy horni roh bude na souradnici [100, 100] //Pri zmacknuti krizku zavri celou aplikaci (tj. vsechna vlakna) frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); //zobraz okno } }
Volání
Grafická aplikace začíná stejně jako konzolová voláním metody main(String[] args). Jak jsme si již uvedli, tak ve Swingu existuje privilegované vlákno – event dispatching thread, které se stará o obsluhu všech událostí. Jeho další vlastností pak je, že je to jediné vlákno, které smí modifikovat uživatelské rozhraní. Z tohoto důvodu nesmíme pro vykreslení okna s naším sdělením použít výchozí vlákno aplikace.
Pro popis akce, kterou má event dispatching thread vykonat, použijeme objekt typu Runnable, který obsahuje pouze metodu run() (zde je tento objekt instanciován pomocí anonymní třídy). Jedinou operací bude volání metody createHelloWorld(), která provádí vykreslení okna.
Samotné předání pak provedeme pomocí volání SwingUtilities.invokeLater(Runnable run), které asynchronně uloží námi vytvořený objekt typu runnable do fronty událostí, jíž event dispatching thread zpracovává. Toto volání taktéž vytvoří všechna obslužná vlákna, která grafická aplikace potřebuje.
Tvorba rozhraní
Tvorba rozhraní v metodě createHelloWorld() je poměrně přímočará. Nejprve si vytvoříme objekt okna typu JFrame a okno pojmenujeme „Hello world frame“. Do oka pak vložíme nápis (JLabel) „Hello world“. Abychom zajistili, že okno bude mít správnou velikost, zavoláme metodu pack(), která zjistí preferovanou velikost všech dceřiných komponent a změní velikost okna, kterému pak určíme pozici levého horního rohu na 100px vodorovně a svisle od levého horního rohu monitoru pomocí metody setPosition(int x, int y).
Poté vybereme operaci, která se provede při zavírání okna (např. kliknutí na křížek okna). Zde volíme zavření celé aplikace – tj. zničení všech oken a vláken. Mezi další volby patří například zrušení pouze daného okna (DISPOSE_ON_CLOSE), kde musíme mít na paměti, že aplikace terminuje až v okamžiku, kdy je zničeno poslední okno a doběhne poslední vlákno, které není označeno jako daemon.
Nakonec celé oko učiníme viditelným pro uživatele voláním setVisible(true).