04. 컴포넌트 배치

컨테이너는 컴포넌트(버튼, 체크박스 등)를 배치할 때 배치 관리자(Layout Manager)를 사용합니다. 배치 관리자를 사용하면 컨테이너의 크기가 변경되더라도 컴포넌트의 크기나 위치가 유동적으로 조절되어 레이아웃을 유지할 수 있습니다.

물론 크기가 고정된 컨테이너의 경우, 좌표값(x, y)을 직접 지정하여 배치할 수도 있습니다(NullLayout).

배치 관리자 설명
BorderLayout 동·서·남·북·중앙(Center) 5개 영역으로 나누어 배치. (JFrame, JDialog, JWindow 기본)
FlowLayout 왼쪽에서 오른쪽으로 물 흐르듯 배치. 공간이 부족하면 아래로 줄바꿈. (JPanel 기본)
GridLayout 행(Row)과 열(Column)의 격자(Grid)에 균등한 크기로 배치.
CardLayout 여러 컴포넌트(패널)를 포개 놓고, 한 번에 하나씩 보여주는 배치 (탭과 유사).
GridBagLayout 격자 기반이지만, 컴포넌트의 크기나 위치를 매우 세밀하게 제어 가능 (복잡함).
NullLayout 배치 관리자 없이 좌표값(setBounds)으로 직접 배치.

setLayout() 메서드로 컨테이너의 배치 관리자를 변경할 수 있습니다.

jFrame.getContentPane().setLayout(new FlowLayout());

1. BorderLayout

BorderLayout은 컨테이너를 North, South, East, West, Center의 5개 영역으로 나눕니다.

graph TD
    N[North]
    W[West]
    C[Center]
    E[East]
    S[South]
    
    N --- C
    S --- C
    W --- C
    E --- C
    
    style N fill:#f9f,stroke:#333
    style S fill:#f9f,stroke:#333
    style W fill:#9ff,stroke:#333
    style E fill:#9ff,stroke:#333
    style C fill:#ff9,stroke:#333
  • North/South: 컴포넌트의 높이는 유지되고, 폭은 컨테이너에 맞춰집니다.
  • East/West: 컴포넌트의 폭은 유지되고, 높이는 컨테이너에 맞춰집니다.
  • Center: 남은 모든 공간을 차지합니다.
package sec04.exam01_borderlayout;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class BorderLayoutExample extends JFrame {
    private JTextField txtNorth;
    private JTextArea txtCenter;
    private JButton btnSouth;

    public BorderLayoutExample() {
        this.setTitle("BorderLayoutExample");
        this.setSize(300, 200);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // 북쪽, 중앙, 남쪽에 컴포넌트 배치
        this.getContentPane().add(getTxtNorth(), BorderLayout.NORTH);
        this.getContentPane().add(getTxtCenter(), BorderLayout.CENTER);
        this.getContentPane().add(getBtnSouth(), BorderLayout.SOUTH);
    }

    private JTextField getTxtNorth() {
        if (txtNorth == null) {
            txtNorth = new JTextField();
            txtNorth.setText("북쪽 컴포넌트");
            txtNorth.setBackground(Color.YELLOW);
        }
        return txtNorth;
    }

    private JTextArea getTxtCenter() {
        if (txtCenter == null) {
            txtCenter = new JTextArea();
            txtCenter.append("중앙 컴포넌트\n");
            txtCenter.append("동쪽/서쪽이 없으면 중앙이 확장됩니다.\n");
        }
        return txtCenter;
    }

    private JButton getBtnSouth() {
        if (btnSouth == null) {
            btnSouth = new JButton("남쪽 컴포넌트");
        }
        return btnSouth;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            BorderLayoutExample jFrame = new BorderLayoutExample();
            jFrame.setVisible(true);
        });
    }
}

2. FlowLayout

FlowLayout은 컴포넌트를 왼쪽에서 오른쪽으로 차례대로 배치합니다. 한 줄이 꽉 차면 다음 줄로 넘어갑니다. 컴포넌트의 크기는 각 컴포넌트의 적정 크기(Preferred Size)로 유지됩니다.

package sec04.exam02_flowlayout;

import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class FlowLayoutExample extends JFrame {
    private JButton btnOk;
    private JButton btnCancel;

    public FlowLayoutExample() {
        this.setTitle("FlowLayoutExample");
        this.setSize(300, 100);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // FlowLayout 설정
        this.setLayout(new FlowLayout());
        
        this.getContentPane().add(getBtnOk());
        this.getContentPane().add(getBtnCancel());
    }

    private JButton getBtnOk() {
        if(btnOk == null) {
            btnOk = new JButton("확인");
        }
        return btnOk;
    }

    private JButton getBtnCancel() {
        if(btnCancel == null) {
            btnCancel = new JButton("취소");
        }
        return btnCancel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            FlowLayoutExample jFrame = new FlowLayoutExample();
            jFrame.setVisible(true);
        });
    }
}

JPanel과 Layout

JPanel은 기본적으로 FlowLayout을 사용합니다. 복잡한 배치를 할 때 JFrame(BorderLayout)의 각 영역에 JPanel을 배치하고, 그 JPanel 안에 다시 컴포넌트들을 FlowLayout 등으로 배치하는 방식(중첩 레이아웃)을 많이 사용합니다.

package sec04.exam03_jpanel;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class JPanelExample extends JFrame {
    private JPanel panelSouth;
    private JButton btnOk;
    private JButton btnCancel;

    public JPanelExample() {
        this.setTitle("JPanelExample");
        this.setSize(250, 200);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // JFrame 남쪽에 JPanel 배치
        this.getContentPane().add(getPanelSouth(), BorderLayout.SOUTH);
    }

    public JPanel getPanelSouth() {
        if (panelSouth == null) {
            panelSouth = new JPanel(); // 기본 FlowLayout
            panelSouth.setBackground(Color.WHITE);
            panelSouth.add(getBtnOk());
            panelSouth.add(getBtnCancel());
        }
        return panelSouth;
    }

    public JButton getBtnOk() {
        if (btnOk == null) btnOk = new JButton("확인");
        return btnOk;
    }

    public JButton getBtnCancel() {
        if (btnCancel == null) btnCancel = new JButton("취소");
        return btnCancel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JPanelExample jFrame = new JPanelExample();
            jFrame.setVisible(true);
        });
    }
}

3. GridLayout

GridLayout은 컨테이너를 행(Row)과 열(Column)로 나누어 모든 구획을 동일한 크기로 만듭니다. 컴포넌트가 추가되면 왼쪽→오른쪽, 위→아래 순서로 채워집니다.

package sec04.exam04_gridlayout;

import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class GridLayoutExample extends JFrame {
    private JButton[][] btn;

    public GridLayoutExample() {
        setTitle("GridLayoutExample");
        setSize(300, 100);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // 2행 3열 GridLayout
        setLayout(new GridLayout(2, 3));
        
        for(int r = 0; r < 2; r++) {
            for(int c = 0; c < 3; c++) {
                getContentPane().add(getBtn()[r][c]);
            }
        }
    }

    public JButton[][] getBtn() {
        if(btn == null) {
            btn = new JButton[2][3];
            for(int r = 0; r < 2; r++) {
                for(int c = 0; c < 3; c++) {
                    btn[r][c] = new JButton("["+r+"][" + c + "]");
                }
            }
        }
        return btn;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            GridLayoutExample jFrame = new GridLayoutExample();
            jFrame.setVisible(true);
        });
    }
}

4. CardLayout

CardLayout은 여러 컴포넌트(주로 패널)를 같은 위치에 겹쳐 놓고, 한 번에 하나만 보여줍니다. first(), last(), next(), show() 메서드로 보여줄 카드를 전환합니다.

package sec04.exam05_cardlayout;

import java.awt.CardLayout;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class CardLayoutExample extends JFrame {
    private JPanel redCard, greenCard, blueCard;

    public CardLayoutExample() {
        this.setTitle("CardLayoutExample");
        this.setSize(250, 400);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // CardLayout 설정
        this.getContentPane().setLayout(new CardLayout());
        
        // 이름과 함께 패널 추가
        this.getContentPane().add("RedCard", getRedCard());
        this.getContentPane().add("GreenCard", getGreenCard());
        this.getContentPane().add("BlueCard", getBlueCard());
    }

    public JPanel getRedCard() {
        if (redCard == null) {
            redCard = new JPanel();
            redCard.setBackground(Color.RED);
        }
        return redCard;
    }
    public JPanel getGreenCard() {
        if (greenCard == null) {
            greenCard = new JPanel();
            greenCard.setBackground(Color.GREEN);
        }
        return greenCard;
    }
    public JPanel getBlueCard() {
        if (blueCard == null) {
            blueCard = new JPanel();
            blueCard.setBackground(Color.BLUE);
        }
        return blueCard;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            final CardLayoutExample jFrame = new CardLayoutExample();
            jFrame.setVisible(true);
            
            // 1초마다 자동으로 카드 전환하는 스레드
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {}
                    
                    SwingUtilities.invokeLater(() -> {
                        CardLayout cardLayout = (CardLayout) jFrame.getContentPane().getLayout();
                        cardLayout.next(jFrame.getContentPane());
                    });
                }
            }).start();
        });
    }
}

5. NullLayout (절대 위치)

배치 관리자를 null로 설정하고, setBounds(x, y, width, height)를 사용하여 컴포넌트의 위치와 크기를 픽셀 단위로 직접 지정합니다. 컨테이너 크기가 변해도 컴포넌트 위치가 변하지 않습니다.

package sec04.exam06_nulllayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class NullLayoutExample extends JFrame {
    private JButton btnOk;

    public NullLayoutExample() {
        this.setTitle("NullLayoutExample");
        this.setSize(300, 200);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // NullLayout 설정
        this.getContentPane().setLayout(null);
        this.getContentPane().add(getBtnOk());
    }

    public JButton getBtnOk() {
        if(btnOk == null) {
            btnOk = new JButton("확인");
            // 절대 위치 및 크기 설정
            btnOk.setBounds(100, 50, 70, 60);
        }
        return btnOk;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            NullLayoutExample jFrame = new NullLayoutExample();
            jFrame.setVisible(true);
        });
    }
}

6. Pack

pack() 메서드는 컨테이너 내부의 컴포넌트들이 선호하는 크기(Preferred Size)에 맞춰서 컨테이너(윈도우)의 크기를 자동으로 조절해줍니다. setSize() 대신 사용하면 내용물에 딱 맞는 윈도우 크기를 얻을 수 있습니다.

package sec04.exam07_pack;

import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class PackExample extends JFrame {
    private JButton btnOk;
    private JButton btnCancel;

    public PackExample() {
        this.setTitle("PackExample");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        this.setLayout(new FlowLayout());
        this.getContentPane().add(getBtnOk());
        this.getContentPane().add(getBtnCancel());
        
        // 내부 컴포넌트 크기에 맞게 윈도우 크기 자동 조절
        this.pack();
    }

    private JButton getBtnOk() {
        if(btnOk == null) {
            btnOk = new JButton("확인");
        }
        return btnOk;
    }

    private JButton getBtnCancel() {
        if(btnCancel == null) {
            btnCancel = new JButton("취소");
        }
        return btnCancel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            PackExample jFrame = new PackExample();
            jFrame.setVisible(true);
        });
    }
}
서브목차