LINUXSOFT.cz Přeskoč levou lištu

ARCHIV



   

> 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 55634×

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í:

Logická struktura GUI Karta vlastností pro položku menu

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

pridej.cz

 

DISKUZE

nastavenie GUI 3.9.2006 15:47 nill
  L Re: nastavenie GUI 4.9.2006 22:22 Petr Zajíc
    L Re: nastavenie GUI 5.9.2006 20:45 nill




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 ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2024) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze