MigLayout: správny layout manager pre swingovské aplikácie

Ak máte NetBeans a vizuálny editor okien, nemusíte čítať ďalej. Ostatní, čo majú radi veci pod kontrolou a svoje Eclipse pod rukami, nemusia až tak plakať.

Písať swingácke formuláre ručne môže byť bolesť, ale so správnymi layout manažérmi je to omnoho väčšia radosť.

Medzi tie správne manažéry (či správnych manažérov) patrí..

MigLayout

Súhlasím, v Jave existuje viacero layoutovačov. Mnoho vecí viete vyriešiť takým GridBagLayoutom, ale jeho používanie je však šialené. MigLayout, ako vysvitne, vie mnoho typických vizuálov okien nakonfigurovať na pár znakov v kóde.

Ukážme si to na príklade jednoduchého okienka: zopár dialógov a dve tlačidlá OK a Storno. Ale najprv hybajme po JARká k MigLayoutu, napríklad z domovskej stránky (licencia je BSD alebo GPL).

Potom nasekajme kostru formulára:

public class DemoForm extends JDialog {
    private JLabel lblNazov = new JLabel("Názov:");
    private JTextField txtNazov = new JTextField("", 25);

    private JLabel lblUrl = new JLabel("URL:");
    private JTextField txtUrl = new JTextField("", 25);

    private JButton btnOk = new JButton("OK");
    private JButton btnCancel = new JButton("Storno");

    public DemoForm() {
        add(lblNazov);
        add(txtNazov);
        add(lblUrl);
        add(txtUrl);

        add(btnOk);
        add(btnCancel);

        pack();
    }
}

1386112204811

Toto ešte nemá nič spoločné so zázračným layoutom. Ak to spustíme, uvidíme len jedno tlačidlo Storno: v JFrame sa totiž použil štandardný BorderLayout, v ktorom sa všetky komponenty nahádzali do centrálneho sektora: a keďže v borderlayoutistickom sektore môže byť len jeden komponent, zvíťazil ten, ktorý bol pridaný ako posledný.

Preč s ním radšej!

Miglajoutnime! (Ako prvú vec v konštruktore)

setLayout(new MigLayout());

Spustenie bude na začiatok nemastné neslané:

1386111834678

MigLayout nahádzal všetko vedľa seba a tvári sa ako maskovaný BoxLayout. Ak naťahujeme okno do šírky, nič sa nedeje, rovnako pri naťahovaní do výšky.

1386112428258

Na druhej strane: ak začneme zmenšovať okno, textové políčko sa zužuje!

1386112347889

Ohúrenie malé, ale predsa len prítomné.

Rozhádžme teraz komponenty poriadnejšie

Dajme výzoru nejakú fazónu. Mnoho dizajnérov si myslí, že použitie mriežkového dizajnu je skvelá vec: dáva veciam rád a rovnomernosť, popri tom je vzdušný, na oko pohľadný, a máločo ním možno pokaziť.

Nahliadnime do budúcnosti a pozrime si výsledné okno v mriežke:

grid

MigLayout sa drží tejto mriežkovej filozofie. Každá bunka mriežky obsahuje jeden komponent (ak nie je prázdna), ale možno ju deliť na podbunky, naťahovať cez viacero stĺpcov či riadkov a robiť s ňou kadejakú neplechu.

Veď uvidíme.

Začnime jednoduchou vecou: vytvorme layout o dvoch stĺpcoch. Inými slovami, zaželajme si, aby boli v riadku najviac dve bunky:

setLayout(new MigLayout("wrap 2");

Čítať to môžeme aj ako “po druhom komponente zalom na nový riadok”.

1386113390077

Aha, stačilo dodať 8 znakov, a okno hneď vyzerá utešene! Dokonca si všimnime, ako sa tlačidlo OK natlačilo do prvej bunky tretieho riadka a Storno obsadilo druhú bunku v treťom riadku.

Práve sme nastavili obmedzenia layoutu (layout constraints), teda globálne konfigurácie výzoru, a to by stačilo.

Pozrime sa však na naťahovanie do šírky: zatiaľ nefunguje, aj keď zužovanie áno.

Opravme to!

Naťahovanie do šírky

Čo vlastne chceme dosiahnuť? Pri naťahovaní do šírky by sme chceli:

  • aby šírky labelov (Názov: a URL:) ostali nemenné
  • aby sa všetky textové políčka naťahovali do šírky

V reči MigLayoutu chceme nastaviť obmedzenia pre stĺpce (column constraints). Tie majú zvláštnu, ale v skutočnosti veľmi jednoduchú syntax.

Napr. toto znamená “Vyrob dva stĺpce [a nedaj im žiadne obmedzenia]”:

[][]

Toto zase: vyrob dva stĺpce, pričom ten prvý nech sa naťahuje do šírky (grow) a komponent v ňom nech vyplní celú bunku (fill):

[][fill, grow]

Ak teda nastavíme

setLayout(new MigLayout("wrap 2", "[][grow, fill]"));

a spustíme a ponaťahujeme okno, uvidíme naozaj čo sme chceli:

1386113817232

Teda skoro.

Keďže tlačidlo Storno je v druhom stĺpci a má povinne nakázané rásť a plniť, robí presne to, čo sme mu nakázali.

Aj keď je to hlúposť, ale tak sme to chceli.

Opäť opravujme.

Vypnutie mriežky

Najjednoduchšou možnosťou je vypnutie mriežky v treťom riadku (v tom, kde sa nachádzajú tlačidlá). A dosiahnuť to možno… obmedzeniami riadkov (column constraints).

Syntax je podobná.

Toto znamená “tri riadky pod sebou”:

[][][]

A toto zase “prvé dva riadky sú normálne, a v treťom vypni mriežku”. (Dlhšia verzia: “V treťom riadku zapni plávajúci mód, tak ako by to robil flow layout.”)

[][][nogrid]

Všetky obmedzenia (layoutové, riadkové a stĺpcové) vyzerajú potom takto:

setLayout(new MigLayout("wrap 2", "[][fill, grow]", "[][][nogrid]"));

Po spustení uvidíme takmer pôvodný stav.

1386114198112

V treťom riadku sa vypla mriežka, čo prakticky znamená, že sa v ňom objaví len jedna bunka tiahnuca sa od okraja po okraj. Do nej sme nasekali dve tlačidlá vedľa seba.

A pozor! Textové políčka sa naozaj naťahujú, ale tlačidlá Storno už nie.

Istý nemenovaný pozorovateľ článku povedal, že to vyzerá síce pekne, ale tlačidlá majú úplne smiešnu veľkosť.

Aj to bude jednoduché opraviť.

Veľkosť tlačidiel

V MigLayoute môžete klasickým tlačidlám priradiť tagy, a tým ich vyhlásiť za špeciálne. Napríklad tlačidlo OK, klasický rezident každého dialógu má tag, prekvapivo, ok. Rovnako jeho spolupútnik Storno vyfasoval privilégium vlastného tagu cancel.

Priradzovať tagy možno cez obmedzenia komponentov (component constraint), čo môžeme urobiť priamo v metóde add():

add(btnOk, "tag ok");
add(btnCancel, "tag cancel");

1386114578533

Aký to zázrak! Obe tlačidlá sa:

  • odsťahovali napravo v okne a budú sa tam držať pri naťahovaní šírky okna
  • dostali rovnakú veľkosť
  • a dokonca na Linuxe a MacOS X dodržali konvencie a OK bude napravo od Storno (na Windowse to bude v poradí OK a Storno).

Dotiahnuť do dokonalosti to môžeme už len nastavením tlačidla OK na implicitné tlačidlo okna, ale to už nie je záležitosť MigLayoutu.

getRootPane().setDefaultButton(btnOk);

Celý výzor potom bude:

1386114705680

A celý kód:

public class DemoForm extends JDialog {
    private JLabel lblNazov = new JLabel("Názov:");
    private JTextField txtNazov = new JTextField("", 25);

    private JLabel lblUrl = new JLabel("URL:");
    private JTextField txtUrl = new JTextField("", 25);

    private JButton btnOk = new JButton("OK");
    private JButton btnCancel = new JButton("Storno");

    public DemoForm() {
        setLayout(new MigLayout("wrap 2", "[][grow, fill]", "[][][nogrid]"));

        add(lblNazov);
        add(txtNazov);
        add(lblUrl);
        add(txtUrl);

        add(btnOk, "tag ok");
        add(btnCancel, "tag cancel");

        getRootPane().setDefaultButton(btnOk);
    }
} 

Príklad č. 2: s prehliadačom

Príklad číslo dva bude paródia na paprehliadač webu. Áno, bude to katastrofa, ale nejde o funkcionalitu, ale o výzor.

Najprv nasekajme kostru formulára:

package sk.upjs.ics.ereses;

import java.awt.HeadlessException;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class WebBrowserForm extends JFrame {
    private JButton buttonBack = new JButton("Back");

    private JButton buttonForward = new JButton("Forward");

    private JTextField textAddress = new JTextField("http://www.google.com");

    private JButton buttonGo  = new JButton("Go!");

    private JEditorPane editorWeb = new JEditorPane();

    public WebBrowserForm() {

        add(buttonBack);
        add(buttonForward);
        add(textAddress);
        add(buttonGo);
        add(editorWeb);

        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new WebBrowserForm().setVisible(true);
            }
        });
    }
}

Toto ešte nemá nič spoločné so zázračným layoutom. Ak to spustíme, uvidíme len biele okno: v JFrame sa totiž použil štandardný borderlayout, a v ňom sa do stredného sektoru hodil “webový prehliadač”, teda JEditorPane.

Miglajoutnime! (Ako prvú vec v konštruktore)

setLayout(new MigLayout());

Spustenie bude na začiatok nešťastné:

webbrowser

Rozhádžme teraz komponenty poriadnejšie

Poupratujme v komponentoch!

Ale ešte predtým oživme prehliadač:

public WebBrowserForm() throws IOException {
    setLayout(new MigLayout());

    add(buttonBack);
    add(buttonForward);
    add(textAddress);
    add(buttonGo);
    add(editorWeb);

    /* dodajme načítavanie stránky */        
    try {
        editorWeb.setPage(textAddress.getText());
    } catch (IOException ex) {
        editorWeb.setText("<h1>Page not found!</h1>");
    } 

    pack();
} 

Spusťme nanovo!

webbrowser-4

No vskutku, najvyšší čas upratovať!

Najlepšie by bolo prehodiť komponent so stránkou pod navigačnú lištu. S MigLayoutom žiadny problém: stačí povedať, že za tlačidlo Go! nasleduje “nový riadok”. Upravme jemne príslušný riadok:

add(buttonGo, "wrap");

Áno, wrap znamená “tu zalom!”.

Výsledok:

webbrowser-6

Opäť žiadna sláva, ale vieme sa z toho použiť.

MigLayout uvažuje v mriežkach

Tento kus kódu…

add(buttonBack);
add(buttonForward);
add(textAddress);
add(buttonGo, "wrap");
add(editorWeb);

… hovorí, že sa má vytvoriť mriežka o štyroch stĺpcoch a dvoch riadkoch. V každej bunke mriežky sa ocitne jeden komponent, a priradí sa mu preferovaná veľkosť.

Komponent s webovou stránkou sa ocitne v druhom riadku a prvom stĺpci, a vďaka tomu sa natiahne aj tlačidlo Back o poschodie vyššie, teda v prvom riadku a prvom stĺpci.

Komponent sa môže tiahnuť

Komponent sa môže tiahnuť viacerými bunkami:

add(editorWeb, "span 4");

V tomto prípade sa bude webová stránka tiahnuť ako Lovosice štyroma bunkami, čiže zaberie celý riadok.

webbrowser-6

Stále sa však nebude rozťahovať!

Rozťahovanie so zmenou veľkosti

Ak chceme, aby sa web naťahoval aj do výšky, aj do šírky, môžeme použiť fintu podobnú z border layoutu. Použime dokovanie do prostredného sektora:

add(editorWeb, "span 4, dock center");

Aby to naozaj fungovalo, musíme upraviť obmedzenia (constraints) celého layoutu. Obmedzenie fill znamená, že

Uchmatne si všetko dostupné miesto v kontajneri pre stĺpce, či riadky.

setLayout(new MigLayout("fill"));

Aha, ako dokonale to funguje!

webbrowser-8

Zadokovaná stránka si uchmatla všetko miesto do šírky i výšky. Rovnako sa však naťahuje aj prvý riadok!

Naťahujme len niektoré stĺpce

Vidíme, že s naťahovaním okna do šírky sa naťahuje medzera medzi tlačidlami Back a Forward. Nechceli by sme radšej, aby sa naťahoval adresný riadok?

Kto nechce, nech preskočí túto sekciu.

My ostatní môžeme nahodiť obmedzenia pre stĺpce (column constraints). Toto znamená: vyrob štyri stĺpce, a tretí nech sa naťahuje do šírky (grow) a komponent v ňom má vyplniť celú bunku (fill):

[][][fill, grow][]

Celý layout

Celý layout bude vyzerať:

setLayout(new MigLayout("fill", "[][][fill, grow][]"));

add(buttonBack);
add(buttonForward);
add(textAddress);
add(buttonGo, "wrap");
add(editorWeb, "span 4, dock center");

A výsledok:

1386115256793

Celý kód zase:

package sk.upjs.ics.ereses;

import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import net.miginfocom.layout.AC;
import net.miginfocom.layout.LC;
import net.miginfocom.swing.MigLayout;

public class WebBrowserForm extends JFrame {
    private JButton buttonBack = new JButton("Back");

    private JButton buttonForward = new JButton("Forward");

    private JTextField textAddress = new JTextField("http://ics.upjs.sk/~novotnyr/home/skola/programovanie_algoritmy_zlozitost/");

    private JButton buttonGo  = new JButton("Go!");

    private JEditorPane editorWeb = new JEditorPane();

    public WebBrowserForm() {
        setLayout(new MigLayout("fill", "[][][fill, grow][]"));

        add(buttonBack);
        add(buttonForward);
        add(textAddress);
        add(buttonGo, "wrap");
        add(editorWeb, "span 4, dock center");

        try {
            editorWeb.setPage(textAddress.getText());
        } catch (IOException ex) {
            editorWeb.setText("<h1>Page not found!</h1>");
        } 

        buttonGo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                buttonGoActionPerformed(e);
            }
        });

        pack();

        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void buttonGoActionPerformed(ActionEvent e) {
        String url = textAddress.getText();
        try {
            editorWeb.setPage(url);
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(this, "Cannot load page<br />" + url, "Error!", ERROR);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new WebBrowserForm().setVisible(true);
            }
        });
    }
}

Záver

Ukázali sme veľmi veľmi veľmi letmý prelet MigLayoutom, ale naštastie je k nemu viacero zdrojov.

Stačí čítať a vzdelávať sa.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *