Home Artykuły Java BoxLayout

BoxLayout

BoxLayout

Zauważylem że w polskojęzycznym internecie brakuje sensownego tutoriałka dla swinga a szczególnie na temat layout managerów (LM). Dlaczego warto znać LM. A chociaż by dlatego, że musimy pisać programy, które coś robią, a nie tylko wyglądają (chcemy żeby 80% czasu zajmowało nam programowanie logiki, a nie graficznego interfejsu użytkownika (GUI) .

Cel: stworzyc estetyczny dialog, który można zobaczyć w NetBeansie naciskając Ctrl+Shift+F:

A dokładniej: opanować technikę: „wiem jak to ma wyglądać i zaraz to zaimplementuję co do pixela”. Czyli spróbujemy zaimplementować dokładnie taki sam dialog. Nie będziemy korzystać z żadnych builderów. Najpierw zaimplementujemy klasę okna. Zmierzyłem rozmiary - okno ma dokładnie 424x306 pikseli

package boxlayouttutorial;
import javax.swing.JFrame;

public class BoxLayoutTutorialFrame extends JFrame {

    public BoxLayoutTutorialFrame()
    {
        super("BoxLayoutTutorialFrame");
        initComponents();
    }

    private void initComponents() {
         setSize(424, 306);
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        /* Tu bedzie implementacja */
        
    }

    public static void main(String[] args) {
        try {
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new BoxLayoutTutorialFrame().setVisible(true);
            }
        });
    }
}

Uruchomienie daje następujący wynik :

01-czysty

zostaje tylko wrzucić komponenty na okno korzystając z BoxLayout.

BoxLayout

Dlaczego BoxLayout? Znam kilka layout managerów, ale ten łączy w sobie następujące cechy: Pozwala na stosunkowo dokładne pozycjonowanie komponentów. Można ułożyć GUI posługując tylko nim samym.  Jednak w prawdziwych zastosowaniach stosuje jeszcze BorderLayout . Jeżeli nie chce programować, a chce oglądać StarTrek, to używam buildera i GroupLayout. Pokaże Wam jak używając tylko BoxLayout można precyzyjnie opanować pozcjonowanie komponentów, i dlaczego  warto nauczyć się chociaż jednego layout managera, właśnie tego ( BoxLayout ).

Rozkład BoxLayout układa komponenty w jednym wierszu lub w jednej kolumnie (poziomo lub pionowo). Ważne, brane pod uwage  preferowane, maksymalne i minimalne rozmiary komponentów oraz ich wyrównanie w kontenerze (do lewej, w centrum, do prawej).  Czyli mamy używać metody setPreferredSize(Dimension d)  zamiast setSize(int w, int h) , oraz czasami setMaximumSize(Dimension d),  setMinimumSize(Dimension d), aby istniejącemu kontenerowi cont nadać rozkład BoxLayout:

  • pionowy: container.setLayout(new BoxLayout(cont,  BoxLayout.Y_AXIS);
  • poziomy: container.setLayout(new BoxLayout(cont,  BoxLayout.X_AXIS);

Jeżeli użyjemy przykładowo pionowego rozmieszczenia oraz wkładając 7 paneli o rożnych rozmiarach, uzyskamy następujący wygląd:

 

horozontal

Jeżeli użyjemy poziomego rozmieszczenia oraz wkładając 4 paneli o rożnych rozmiarach uzyskamy następujący wygląd:

 

vertical

Kombinując te 2 układy (poziomowy oraz pionowy),  zagnieżdżając kontejniery z własnymi ustawieniami layoutManager-ów oraz  widoczne i niewidoczne komponenty możemy stworzyć interfejs o prawie dowolnej złożoności.
Żeby precyzyjnie zaprojektować taki dialog  jak w NetBeans warto najpierw zaprojektować go jeszcze przed kodowaniem. Ja zazwyczaj rysuję taki dialog na papierze, wykrywając miejsca, w które będę wstawiał rozporki (struts) oraz sprężyny (glue nie zbyt dobra angielska nazwa, bo działają jako sprężyny).  Kiedy już narysujemy wszystkie kontrolki, rysujemy tylko poziome styczne linie ze wszystkimi widocznymi granicami ale tylko dla widocznych komponentów:

 

01-horizontal-lines

Dla obszarów 1, 7, oraz 9 będziemy używać niewidocznych komponentów, bo te obszary nic nie zawierają.

  • Box.createVerticalStrut(int heigth): ma nieokresloną szerokość, ale okresloną wysokość
  • Box.createHorizontalStrut(int heigth): ma określoną szerokość, ale nieokreśloną wysokość
Dla mnie zawsze punktem wejściowym jest zdefiniowanie układu pionowego dla całego kontejnera, dla tego, że tak jest ułożona większość GUI, przyjrzyjcie się, układają się wiersz po wierszu.

Container mainPanel = getContentPane();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));

 ( * BoxLayout jest jedynym znanym mnie LayoutManager-em, który wymaga podania w konstruktorze kontainera do którego jest stosowany. Wygląda to troche dziwnie)

Box.createVerticalStrut(int heigth). Program Paint.NET pokazuje , że wiersze mają wysokości: 10, 23, 31, 23, 30, 102, 28, 22, 14 pikseli.

Więc dodajemy wiersze (widoczne oraz niewidoczne):

private void initComponents() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        Container mainPanel = getContentPane();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
// Na razie dodamy tylko 2 wiersze
        mainPanel.add(getRow1());     
        mainPanel.add(getRow2());
/*    
        mainPanel.add(getRow3());
        mainPanel.add(getRow4());
        mainPanel.add(getRow5());
        mainPanel.add(getRow6());
        mainPanel.add(getRow7());
*/
        // Sprezynka, zeby guziki byli zawsze na dole
        mainPanel.add(Box.createVerticalGlue());
/*       
        mainPanel.add(getRow8());
        mainPanel.add(getRow9());
 */
        setSize(424, 306);
    }
    private Box getBox(int height)
    {
        Box box = Box.createHorizontalBox();
        setPrefferedMaxAndMinSize(box, 4096, height);
        setDebugBorder(box);
        return box;
    }
    void setDebugBorder(JComponent c)
    {
        //Odkomentowac, zeby widziec granice
        //c.setBorder(BorderFactory.createLineBorder(Color.BLUE));
    }
    private Component  getRow1()
    {
        return Box.createVerticalStrut(10);
    }

    private Component  getRow2()
    {
        Box box = getBox(23);
        buildRow2(box);
        return box;
    }

Specjalnie ustaliłem kolor granic (odkomentowałem setDebugBorder ), żeby było widać granice paneli.  Wynik kodu:

 

nb02

Nieźle: 1 wiersz (pionowa rozporka 10 pikseli ) ma wymagane rozmiary, oraz jest w odpowiedniej pozycji. Nad drugim jeszcze popracujemy. Ale panel drugiego wiersza też już jest we właściwym miejscu i ma odpowiedni rozmiar. A teraz dokładniej przeanalizujemy:

 

nb03

Teraz musimy zaimlementować 2 wiersz, czyli metodę getRow2(panel). Postępowanie jest identyczne jak dla całego dialogu.
Tak wygląda wiersz 2:

02-wiersz2

Nie będzie on zawierać poziomowych stycznych linii tylko pionowe styczne, które dzielą cały wiersz  na 4 obszary: 1, 2 są widoczne : JLabel wycentrowany w pionie i poziomie o ścisłych rozmiarach (używamy również setMaximumSize(Dimension d), JCombobox, który potrafi rozciągać, ale tylko w poziomie. Przy zmianie rozmiarów dialogu.
Obszary 3 oraz 4 maja scisłe rozmiary:

03-wiersz2

To, że ten wiersz nie ma stycznych poziomych tłumaczy wybór poziomowego trybu rozmieszczenia ( W tym panelu wszystkie komponenty mieszczą się w 1 wierszu ). Elementy wiersza mają szerokości: 13, 98, 300, oraz 15 pikseli. Zamiast JPanel uzyjemy innego kontejniera Box ze względu na wygodę. Klasa Box zawiera 2 fabryczne mietody :

Box.createHorizontalBox() oraz Box.createVerticalBox().
    private Component  getRow2()
    {
        Box box = getBox(23);
        buildRow2(box);
        return box;
    }
    private void buildRow2(JComponent panel) {
       //Element 4
       panel.add(Box.createHorizontalStrut(13));

       //Element 1
       JLabel label = new JLabel("Contain text:");
       setPrefferedMaxAndMinSize(label, 90, 23);
       panel.add(label);
       
       //Element 2:
       JComboBox cb = new JComboBox(new Object[]{"a","b","c"});
       cb.setPreferredSize(new Dimension(2048, 23));
       panel.add(cb);

       //Element 3:
       panel.add(Box.createHorizontalStrut(15));
    }

Mamy taki wynik:

 

04-2-wiersz

Nieźle, szczególnie, jeżeli spojrzeć jak wygląda oryginał. W wyniku rozsądnego używania  różnych metod , ustawiających rozmiary, zmniejszony dialog nadal wygląda przyzwoicie. Ponieważ dla JcomboBox był ustawiony tylko setPreferredSize jest on w stanie zmniejszać się oraz zwiększać się. Nie wchodząc na JLabel, oraz na niewidoczny component 4 ( Box.createHorizontalStrut(15) ) :

05-2-wiersz

06-2-wiersz

Kod 3 wiersza:

 

  private Component  getRow3()
    {
        Box box = getBox(31);
        buildRow3(box);
        return box;
    }
    private void buildRow3(JComponent panel) {
        panel.add(Box.createHorizontalStrut(104));
        JLabel label = new JLabel("(* = any string, ? = any characters, \\ = escape for *? )");
        label.setForeground(Color.GRAY);
        panel.add(label);
        panel.add(Box.createHorizontalGlue());
    }
  

Wynik:

08-3-wiersz

Tę samą czynność powtarzamy dla wierszy 4-9. To jest wynik końcowy z liniami siatki i bez nich:

90-wynik

91-wynik

 

Wazne: możemy zmieniac rozmiar dialogu i w tym jest ważna rola LM. Nie wymaga to dodatkowego programowania, tylko wlaściwego stosowania LM.

95-wynik

92-wynik

Użyłem niewidocznych komponentów Box.createHorizontalStrut(int szerokosc)  oraz  Box.createVerticalStrut(int szerokosc):

93-wynik-strutsy

Box.createHorizontalGlue(), Box.createVerticalGlue() :

94-wynik-sprezyny

 

Pełny listing kodu żrodłowego:

package boxlayouttutorial;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class BoxLayoutTutorialFrame extends JFrame {

    public BoxLayoutTutorialFrame()
    {
        super("BoxLayoutTutorialFrame");
        initComponents();

    }
    //
    void setDebugBorder(JComponent c)
    {
        //Odkomentowac, zeby widziec granice
        //c.setBorder(BorderFactory.createLineBorder(Color.BLUE));
    }

    void setPrefferedMaxAndMinSize(Component c, int width, int height)
    {
        Dimension rozmiar = new Dimension(width, height);
        c.setPreferredSize(rozmiar);
        c.setMaximumSize(rozmiar);
        c.setMinimumSize(rozmiar);
    }

    private void initComponents() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        Container mainPanel = getContentPane();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));

        mainPanel.add(getRow1());
        mainPanel.add(getRow2());
        mainPanel.add(getRow3());
        mainPanel.add(getRow4());
        mainPanel.add(getRow5());
        mainPanel.add(getRow6());
        mainPanel.add(getRow7());
        // Sprezynka, zeby guziki byli zawsze na dole
        mainPanel.add(Box.createVerticalGlue());
        mainPanel.add(getRow8());
        mainPanel.add(getRow9());
        setSize(424, 306);
    }

    private Box getBox(int height)
    {
        Box box = Box.createHorizontalBox();
        setPrefferedMaxAndMinSize(box, 4096, height);
        setDebugBorder(box);
        return box;
    }

    private Component  getRow1()
    {
        return Box.createVerticalStrut(10);
    }

    private Component  getRow2()
    {
        Box box = getBox(23);
        buildRow2(box);
        return box;
    }

    private Component  getRow3()
    {
        Box box = getBox(31);
        buildRow3(box);
        return box;
    }

    private Component  getRow4()
    {
        Box box = getBox(23);
        buildRow4(box);
        return box;
    }
    private Component  getRow5()
    {
        Box box = getBox(30);
        buildRow5(box);
        return box;
    }

    private Component  getRow6()
    {
        Box box = getBox(102);
        buildRow6(box);
        return box;

    }

    private Component  getRow7()
    {
       return  Box.createVerticalStrut(28);
    }

    private Component  getRow8()
    {
        Box box = getBox(22);
        buildRow8(box);
        return box;
    }

    private Component  getRow9()
    {
        return Box.createVerticalStrut(14);
    }
    private void buildRow2(JComponent panel) {
       //Element 4
       panel.add(Box.createHorizontalStrut(13));

       //Element 1
       JLabel label = new JLabel("Contain text:");
       setPrefferedMaxAndMinSize(label, 90, 23);
       panel.add(label);
       
       //Element 2:
       JComboBox cb = new JComboBox(new Object[]{"a","b","c"});
       cb.setPreferredSize(new Dimension(2048, 23));
       panel.add(cb);

       //Element 3:
       panel.add(Box.createHorizontalStrut(15));
    }

    private void buildRow3(JComponent panel) {
        panel.add(Box.createHorizontalStrut(104));
        JLabel label = new JLabel("(* = any string, ? = any characters, \\ = escape for *? )");
        label.setForeground(Color.GRAY);
        panel.add(label);
        panel.add(Box.createHorizontalGlue());
    }

    private void buildRow4(JComponent panel) {
        //Element 4
       panel.add(Box.createHorizontalStrut(13));

       //Element 1
       JLabel label = new JLabel("File name patterns:");
       setPrefferedMaxAndMinSize(label, 90, 23);
       panel.add(label);


       //Element 2:
       JComboBox cb = new JComboBox(new Object[]{"*","b","c"});
       cb.setPreferredSize(new Dimension(2048, 23));
       panel.add(cb);

       //panel.add(Box.createHorizontalGlue());
       //Wiersz 4

       panel.add(Box.createHorizontalStrut(15));
    }

    private void buildRow5(JComponent panel) {
        panel.add(Box.createHorizontalStrut(104));
        JLabel label = new JLabel("(Example: *.java, FZP??,.jsp)");
        label.setForeground(Color.GRAY);
        panel.add(label);
        panel.add(Box.createHorizontalGlue());
    }

    private void buildRow6(JComponent panel) {
        panel.add(Box.createHorizontalStrut(13));

        JPanel panel2 = new JPanel();
        panel2.setBorder(BorderFactory.createTitledBorder("Options"));
        panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
        Dimension rozmiar = new Dimension(2048, 2048);
        panel2.setPreferredSize(rozmiar);
        //panel2.setMinimumSize(rozmiar);
        
        panel2.add(new JCheckBox("Whole Words"));
        panel2.add(new JCheckBox("Match case"));
        panel2.add(new JCheckBox("Regular expression"));

        panel2.add(Box.createVerticalGlue());
        panel.add(panel2);

        panel.add(Box.createHorizontalStrut(16));

        panel2 = new JPanel();
        panel2.setBorder(BorderFactory.createTitledBorder("Scope"));
        panel2.setPreferredSize(rozmiar);

        panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
        panel2.add(new JRadioButton("Whole Words"));
        panel2.add(new JRadioButton("Match case"));
        panel2.add(new JRadioButton("Regular expression"));
        panel2.add(Box.createVerticalGlue());
        panel.add(panel2);

        panel.add(Box.createHorizontalStrut(15));

    }

    private void buildRow8(JComponent panel) {
        panel.add(Box.createHorizontalGlue());

        JButton button = new JButton("Find");
        setPrefferedMaxAndMinSize(button, 60, 24);
        panel.add(button);

        panel.add(Box.createHorizontalStrut(6));

        button = new JButton("Close");
        setPrefferedMaxAndMinSize(button, 60, 24);
        panel.add(button);

        panel.add(Box.createHorizontalStrut(6));

        button = new JButton("Help");
        setPrefferedMaxAndMinSize(button, 60, 24);
        panel.add(button);

        panel.add(Box.createHorizontalStrut(15));
        
    }

    public static void main(String[] args) {
        try {
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new BoxLayoutTutorialFrame().setVisible(true);
            }
        });

    }
}

Dla mnie zawsze punktem wejściowym jest zdefiniowanie układu pionowego dla całego kontejnera, dla tego, że tak jest ułożona większość GUI, przyjrzyjcie się, układają się wiersz po wierszu.