[이것이 자바다] Appendix 02 Java UI - Swing 정리
자바에서의 Swing을 이용한 UI 프로그램 개발에 대해 설명하고 있다. JTable, JTree, JMenu, JToolBar 등의 컴포넌트를 이용하여 테이블, 트리, 메뉴, 툴바를 구성하고 이벤트 처리하는 방법을 상세히 설명하고 있다. 또한, 이러한 컴포넌트들을 커스터마이징하고, 이벤트 리스너를 등록하여 사용자의 액션에 대응하는 방법을 설명하고 있다.
Jan 25, 2024
Contents
App.java핵심 키워드JWindowExam.java핵심 키워드JFrameExam.java핵심 키워드JTabbedPaneExam.java핵심 키워드JScrollPaneExam.java핵심 키워드BorderLayoutExam.java핵심 키워드FlowLayoutExam.javaJPanelExam.java핵심 키워드GridLayoutExam.java핵심 키워드CardLayoutExam.java핵심 키워드NullLayoutExam.java핵심 키워드PackExam.java핵심 키워드ClosableExam1.javaClosableExam2.javaActionListenerExam.java핵심 키워드JButtonExam.java핵심 키워드JToggleButtonExam.java핵심 키워드JLabelExam.java핵심 키워드JTxtPwFieldExam.java핵심 키워드JTextAreaExam.java핵심 키워드JEditorPaneExam.java핵심 키워드JListExam.java핵심 키워드JComboBoxExam.java핵심 키워드JTableExam.java핵심 키워드JTreeExam3.java핵심 키워드JMenuExam2.java핵심 키워드JToolBarExam.java핵심 키워드결론!App.java
package swingExam; import java.awt.BorderLayout; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class App extends JFrame { public App() { setTitle("처음 만든 윈도우"); // 제목 setSize(300, 100); // 윈도우 크기 add(new JButton("OK"), BorderLayout.SOUTH); // button 추가 addWindowListener(new WindowAdapter() { // 종료 버튼을 클릭하면 프로세스 종료 @Override public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { App app = new App(); // 윈도우 생성 app.setVisible(true); // 윈도우를 보여줌 }); } }
핵심 키워드
- 자바는 UI 프로그램을 개발할 수 있도록 JDK에서 JFC를 제공한다. JFC는 UI 프로그램을 만들기 위한 클래스들의 모음으로, AWT와 Swing을 제공하고 있다. AWT는 java.awt 패키지로, Swing은 javax.swing 패키지로 사용 가능하다.
- AWT는 운영체제가 가지고 있는 컴포넌트를 그대로 이용하고, Swing은 자바에서 직접 컴포넌트를만든다는 점이 다르다. 따라서 AWT는 여러 운영체제들이 공통적으로 가지고 있는 컴포넌트만 사용하므로 컴포넌트 수가 제한적이지만, Swing은 자바에서 직접 제공하는 컴포넌트이기 때문에 종류가 매우 다양하다. Swing의 단점은 자바가 직접 컴포넌트를 생성하기 때문에 AWT에 비해 CPU와 메모리를 상대적으로 많이 사용한다는 것이다.
- 윈도우를 만들기 위해 AWT는 Frame을 상속하고, Swing은 JFrame을 상속한다. 그리고 버튼은 AWT는 Button을, Swing은 JButton을 사용하고 있다. Swing의 UI 관련 클래스는 AWT의 클래스에 J를 붙였기 때문에 쉽게 구분할 수 있다.
- Swing은 스레드에 안전하지 않기 때문에 작업 스레드들이 동시에 접근해서 UI를 변경하게 되면 문제가 발생할 수 있다. 그래서 Swing은 이벤트 디스패칭 스레드에 의해 순차적으로 UI 변경 작업을 진행하도록 설계되어 있다.
- 작업 스레드에서 UI 를 생성하거나 변경하는 작업이 필요할 때에는 작업해야 할 내용을 Runnable객체로 생성한 뒤, 큐Queue에 저장해 놓는다. 그러면 이벤트 디스패칭 스레가 순차적으로 큐에 있는 Runnable 객체를 꺼내어 실행하면서 UI를 생성하거나 변경시킨다.
- 작업 스레드는 SwingUtilities 클래스의 invokeLater() 메소드를 이용해서 큐에 Runnable객체를 저장한다. 메소드 이름이 invokeLater 인 이유는 큐에 먼저 저장된 Runnable을 처리하고 나중에 처리한다는 의미이다.
JWindowExam.java
package swingExam; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JWindow; import javax.swing.SwingUtilities; public class JWindowExam extends JWindow { public JWindowExam() { // JWindow의 크기 this.setSize(600, 350); // JWindow를 화면 중앙에 띄우기 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); Point centerPoint = ge.getCenterPoint(); int leftTopX = centerPoint.x - getWidth() / 2; int leftTopY = centerPoint.y - getHeight() / 2; this.setLocation(leftTopX, leftTopY); // 마우스 클릭 이벤트 처리 this.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { dispose(); } }); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JWindowExam jWindow = new JWindowExam(); jWindow.setVisible(true); } }); } }
핵심 키워드
- JWindow는 윈도우 경계선, 제목 표시줄, 메뉴바가 모두 없는 윈도우를 만드는 컨테이너로, 컴포넌트만 배치할 수 있는 평면 공간만을 갖는다. 게임 애플리케이션처럼 제목 표시줄이 없는 윈도우를 만들 때 주로 이용된다. 새로운 개발자 정의 윈도우는 JWindow를 상속해서 만들 수 있다.
public class JWindowExample extends JWindow { public JWindowExample() { this.setSize(450, 300); } }
- java.awt.GraphicsEnvironment는 그래픽 환경에 대한 정보를 가지고 있는 객체이다. 이 객체는 정적 메소드인 getLocalGraphicsEnvironment()를 호출해서 얻을 수 있다.
- GraphincsEnvironment의 getContentPoint() 메소드는 화면 중앙 지점의 X좌표와 Y좌표를 가지고 있는 Point 객체를 리턴한다.
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); Point centerPoint = ge.getCenterPoint(); int leftTopX = centerPoint.x - getWidth()/2; int leftTopY = centerPoint.y - getHeight()/2; this.setLocation(leftTopX, leftTopY);
- JWindow를 화면에 띄우려면 setVisible(true) 메소드를 호출하면 된다. 만약 JWindow를 화면에서 완전히 제거하고 싶다면 setVisible(false) 메소드가 아니라 dispose() 메소드를 호출하면 된다.
JWindowExample jWindow = new JWindowExample(); jWindow.setVisible(true); jWindow.dispose();
JFrameExam.java
package swingExam; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class JFrameExam extends JFrame { public JFrameExam() { this.setSize(600, 500); this.setIconImage(new ImageIcon(getClass().getResource("image.jpg")).getImage()); this.setTitle("메인창"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JFrameExam jFrame = new JFrameExam(); jFrame.setVisible(true); } }); } }
핵심 키워드
- JFrame은 JWindow와는 달리 윈도우 경계선, 제목 표시줄, 메뉴바가 있는 윈도우를 만드는 컨테이너 클래스이다. 새로운 개발자 정의 윈도우를 만들기 위해서는 JFrame을 상속해서 만들 수 있다.
- JFrame의 제목 표시줄은 아이콘, 제목, 크기 조절용 버튼, 종료 버튼으로 구성된다. 아이콘은 setIconImage ( ) 메소드로 설정하면 되는데, ImageIcon 객체의 getImage() 메소드로 Image 객체를 얻어 매개값으로 설정하면 된다. 창 제목은 setTitle() 메소드로 설정할 수 있다. 종료 버튼의 기본 기능은 JFrame을 단순히 숨기기만 하고 프로세스를 종료하지 않는다. 프로세스를 완전히 종료하려면 setDefaultCloseOperation() 메소드로 종료 버튼의 기본 기능을 변경해야 한다.
public class JFrameExample extends JFrame { public JFrameExample() { //제목 표시줄 this.setIconImage( new ImageIcon(getClass().getResource("icon.png")).getImage()); this.setTitle("창제목"); //JFrame 크기 this.setSize(600, 500); //종료 버튼의 기본 기능 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
JTabbedPaneExam.java
package swingExam; import java.awt.BorderLayout; import java.awt.Component; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; public class JTabbedPaneExam extends JFrame { private JTabbedPane jTabbedPane; private JPanel tab1Panel; private JPanel tab2Panel; public JTabbedPaneExam() { this.setTitle("JTabbedPaneExample"); this.setSize(300, 200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().add(getJTabbedPane(), BorderLayout.CENTER); // null인지 검사하기 위해 getter로 만듬. } private JTabbedPane getJTabbedPane() { if (jTabbedPane == null) { jTabbedPane = new JTabbedPane(); jTabbedPane.setTabPlacement(JTabbedPane.LEFT); // 탭의 위치 왼쪽으로 지정 jTabbedPane.addTab("탭1", getTab1Panel()); jTabbedPane.addTab("탭2", getTab2Panel()); } return jTabbedPane; } private Component getTab1Panel() { if (tab1Panel == null) { tab1Panel = new JPanel(); JLabel jLabel = new JLabel(); jLabel.setIcon(new ImageIcon(getClass().getResource("image.jpg"))); tab1Panel.add(jLabel); } return tab1Panel; } private Component getTab2Panel() { if (tab2Panel == null) { tab2Panel = new JPanel(); JLabel jLabel = new JLabel(); jLabel.setIcon(new ImageIcon(getClass().getResource("image1.jpg"))); tab2Panel.add(jLabel); } return tab2Panel; } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JTabbedPaneExam jt = new JTabbedPaneExam(); jt.setVisible(true); }); } }
핵심 키워드
- JTabbedPane은 탭tab별로 다른 내용을 보여주기 위해 사용되는 컨테이너이다. JTabbedPane은 독립적인 윈도우 모양을 갖고 있지 않기 때문에 JWindow, JFrame, JDialog 등과 같은 최상위 레벨 컨테이너에 배치된다. 오른쪽 화면은 JTabbedPane의 구현 모습이다. 탭의 위치는 상단, 하단, 왼쪽, 오른쪽에 위치 시킬 수 있다. JTabbedPane을 생성하려면 기본 생성자를 호출하면 된다.
JTabbedPane jTabbedPane = new JTabbedPane();
- 탭의 위치를 설정하려면 setTabPlacement() 메소드로 탭의 위치 상수를 다음과 같이 지정한다.
jTabbedPane.setTabPlacement( JTabbedPane.TOP | JTabbedPane.BOTTOM | JTabbedPane.LEFT | JTabbedPane.RIGHT );
- JTabbedPane에 탭을 추가하려면 addTab() 메소드를 이용한다. addTab() 메소드는 탭의 이름과 탭안에 배치될 컴포넌트를 매개값으로 받는데, 컴포넌트는 주로 JPanel을 객체를 사용한다.
jTabbedPane.addTab("TapName1", jPanel1); jTabbedPane.addTab("TapName2", jPanel2);
- 그리고 각각의 JPanel 안에는 해당 탭에서 보여줄 컴포넌트를 배치하면 된다. 이렇게 생성된JTabbedPane은 다른 컴포넌트와 마찬가지로 ContentPane에 배치된다. 다음은 JFrame의 중앙에 JTabbedPane을 배치하는 코드이다.
jFrame.getContentPane().add("Center", jTabbedPane);
JScrollPaneExam.java
package swingExam.sec03; import java.awt.BorderLayout; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; public class JScrollPaneExam extends JFrame { private JScrollPane scrollImage; private JLabel lblImage; // 메인 윈도우 설정 public JScrollPaneExam() { this.setTitle("JScrollPaneExam"); this.setSize(350, 230); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // JFrame 중앙에 JScrollPane 추가 this.getContentPane().add(getScrollImage(), BorderLayout.CENTER); } // JScrollPane 생성 private JScrollPane getScrollImage() { if (scrollImage == null) { scrollImage = new JScrollPane(getLblImage()); } return scrollImage; } // JLabel 생성 private JLabel getLblImage() { if (lblImage == null) { lblImage = new JLabel(); lblImage.setIcon(new ImageIcon(getClass().getResource("../image3.jpg"))); } return lblImage; } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JScrollPaneExam jFrame = new JScrollPaneExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JScrollPane은 포함된 컴포넌트의 크기가 JScrollPane 자신보다 큰 경우 수평 또는 수직 스크롤바를 이용해서 볼 수 있게 해준다. JScrollPane은 다른 컨테이너와는 달리 단 하나의 컴포넌트만을 포함시킬 수 있다.
- JScrollPane은 JFrame의 중앙에 위치하고 있다. JScrollPane안에는 그림을 포함 하고 있는 JLabel 배치되어 있다고 했을 때, 그림의 크기가 JScrollPane보다 크다면수직 및 수평 스크롤이 생긴다.
- 스크롤이 필요한 컴포넌트에는 큰 내용을 포함하고 있는 JLabel, JTextArea, JList, JTable, JTree등이 있다. 이 컴포넌트에 스크롤을 적용시키려면 다음과 같이 JScrollPane 생성자에 컴포넌트를 매개값으로 주면 된다. 이렇게 생성된 JScrollPane은 컴포넌트가 배치될 수 있는 곳이라면 어디든지 배치가 가능하다.
JScrollPane scrollJList = new JScrollPane(jLabel); JScrollPane scrollJTextArea = new JScrollPane(jTextArea); JScrollPane scrollJList = new JScrollPane(jList); JScrollPane scrollJTable = new JScrollPane(jTable);
BorderLayoutExam.java
package swingExam.sec04; 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 BorderLayoutExam extends JFrame { private JTextField txtNorth; private JTextArea txtCenter; private JButton btnSouth; // 메인 윈도우 설정 public BorderLayoutExam() { this.setTitle("BorderLayoutExam"); 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); } // JTextField 생성 private JTextField getTxtNorth() { if(txtNorth == null) { txtNorth = new JTextField(); txtNorth.setText("북쪽 컴포넌트"); txtNorth.setBackground(Color.yellow); } return txtNorth; } // JTextArea 생성 private JTextArea getTxtCenter() { if(txtCenter == null) { txtCenter = new JTextArea(); txtCenter.append("중앙 컴포넌트\n"); txtCenter.append("동쪽 컴포넌트가 없으니 동쪽으로 확장\n"); txtCenter.append("서쪽 컴포넌트가 없으니 서쪽으로 확장\n"); } return txtCenter; } // JButton 생성 private JButton getBtnSouth() { if(btnSouth == null) { btnSouth = new JButton(); btnSouth.setText("남쪽 컴포넌트"); } return btnSouth; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ BorderLayoutExam jFrame = new BorderLayoutExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 컨테이너에는 UI 컴포넌트들이 배치된다. 대표적인 컴포넌트에는 버튼, 체크박스, 라디오 버튼, 콤포, 리스트 등이 있다. 컨테이너는 기본적으로 배치 관리자로 컴포넌트를 배치한다.
- 배치 관리자는 좌표값으로 컴포넌트를 배치하지 않고, 컨테이너를 몇 개의 구획으로 나누어 하나의 구획에 하나의 컴포넌트를 배치해준다. 배치 관리자로 배치하게 되면 컨테이너의 크기가 사용자에 의해 변경되더라도 컴포넌트의 크기가 비율적으로 늘거나 줄게되어 배치 모양이 그대로 유지된다는 장점이 있다.
- 컨테이너가 컴포넌트를 배치할 때에는 배치 관리자Layout Manager가 무엇이냐에 따라 달라진다. JWindow, JFrame, JDialog는 기본적으로 BorderLayout 배치 관리자를 사용하고, JPanel은 FlowLayout을 사용한다. 자바는 java.awt 패키지에서 BorderLayout, CardLayout, FlowLayout, GridLayout, GridBagLayout을 제공한다.
- BorderLayout 배치 관리자는 컨테이너를 중앙·동·서·남·북으로 구획 짓고, 각 구획에 하나의 컴포넌트 또는 컨테이너를 배치한다. 일반적으로 각 구획에는 JPanel 컨테이너가 배치되어 복잡한 형태의 UI를 만들어낸다.
- BorderLayout을 기본적으로 사용하는 컨테이너는 JWindow, JFrame, JDialog 등이 있다. BorderLayout이 적용된 컨테이너에 컴포넌트를 배치할 때에는 ContentPane을 얻고 add() 메소드를 사용해야 한다.
- 첫 번째 매개값에는 배치할 컴포넌트 객체가 오고, 두 번째 매개값에는 어떤 구획에 배치할 것인지지정하는 BorderLayout의 상수가 온다. 만약 동·서·남·북 중에서 컴포넌트가 배치되지 않은 구획이 있다면 중앙에 배치된 컴포넌트가 해당 구획까지 확장된다.
jFrame.getContentPane().add(컴포넌트, BorderLayout.CENTER); jFrame.getContentPane().add(컴포넌트, BorderLayout.EAST); jFrame.getContentPane().add(컴포넌트, BorderLayout.WEST); jFrame.getContentPane().add(컴포넌트, BorderLayout.SOUTH); jFrame.getContentPane().add(컴포넌트, BorderLayout.NORTH);
FlowLayoutExam.java
package swingExam.sec04; import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class FlowLayoutExam extends JFrame { private JButton btnOk; private JButton btnCancel; // 메인 윈도우 설정 public FlowLayoutExam() { this.setTitle("FlowLayoutExam"); this.setSize(300, 100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // FlowLayout으로 변경하고 두 개의 버튼 추가 this.setLayout(new FlowLayout()); this.getContentPane().add(getBtnOk()); this.getContentPane().add(getBtnCancel()); } // OK 버튼 생성 private JButton getBtnOk() { if(btnOk == null) { btnOk = new JButton(); btnOk.setText("확인"); } return btnOk; } // Cancel 버튼 생성 private JButton getBtnCancel() { if(btnCancel == null) { btnCancel = new JButton(); btnCancel.setText("취소"); } return btnCancel; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ FlowLayoutExam jFrame = new FlowLayoutExam(); jFrame.setVisible(true); }); } }
JPanelExam.java
package swingExam.sec04; 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 JPanelExam extends JFrame { private JPanel panelSouth; private JButton btnOk; private JButton btnCancel; // 메인 윈도우 설정 public JPanelExam() { this.setTitle("JPanelExam"); this.setSize(250, 200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 남쪽에 JPanel 추가 this.getContentPane().add(getPanelSouth(), BorderLayout.SOUTH); } // JPanel 생성 public JPanel getPanelSouth() { if(panelSouth == null) { panelSouth = new JPanel(); panelSouth.setBackground(Color.WHITE); panelSouth.add(getBtnOk()); panelSouth.add(getBtnCancel()); } return panelSouth; } // Ok 버튼 생성 public JButton getBtnOk() { if(btnOk == null) { btnOk = new JButton(); btnOk.setText("확인"); } return btnOk; } // Cancel 버튼 생성 public JButton getBtnCancel() { if(btnCancel == null) { btnCancel = new JButton(); btnCancel.setText("취소"); } return btnCancel; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JPanelExam jFrame = new JPanelExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- FlowLayout 배치 관리자는 이미 배치된 컴포넌트의 오른쪽 옆에 새로운 컴포넌트를 배치한다. 오른쪽에 배치할 공간이 부족하면 하단에 배치하기 때문에 사용자에 의해 컨테이너의 폭이 변경되면 컴포넌트의 베치 위치가 변경될 수 있다.
- FlowLayout이 적용된 컨테이너에 컴포넌트를 배치할 때는 컴포넌트만 매개변수로 갖는 add() 메소드를 사용한다. 예를 들어 JFrame이 FlowLayout을 사용하여 컴포넌트를 배치한다면, 다음 과 같은 add() 메소드로 컴포넌트를 배치한다.
jFrame.getContentPane().setLayout(new FlowLayout()); jFrame.getContentPane().add(컴포넌트);
- FlowLayout을 기본적으로 사용하는 컨테이너에는 JPanel이 있다. JPanel은 JWindow, JFrame, JDialog처럼 하나의 윈도우 창을 만들는 최상위 레벨 컨테이너가 아니라, 컨테이너 속에서 컴포넌트의 배치를 위해 사용되는 투명한 보조 컨테이너이다.
- JPanel은 컴포넌트가 배치될 수 있는 어떤 곳이라도 배치가 가능하다. 심지어 JPanel에 또다른 JPanel을 배치하는 것도 가능하다. JPanel은 기본적으로 FlowLayout을 사용하지만, 다음과 같이setLayout() 메소드로 배치 관리자를 변경할 수도 있다.
JPanel jPanel = new JPanel(); jPanel.setLayout(new BorderLayout());
FlowLayout일 경우: jPanel.add(컴포넌트); BorderLayout일 경우: jPanel.add(컴포넌트, BorderLayout.CENTER);
GridLayoutExam.java
package swingExam.sec04; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class GridLayoutExam extends JFrame { private JButton[][] btn; // 메인 윈도우 설정 public GridLayoutExam() { setTitle("GridLayoutExam"); setSize(300, 100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 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(); btn[r][c].setText("[" + r + "]" + "[" + c + "]"); } } } return btn; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ GridLayoutExam jFrame = new GridLayoutExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- GridLayout 배치 관리자는 컨테이너를 행과 열로 구성된 테이블 모양으로 구획 짓고, 각 구획 에 하나의 컴포넌트를 배치한다. 행과 열의 수는 GridLayout 객체를 생성할 때 생성자의 매개값으로 주거나, 객체 생성 후 setRows(), setColumns() 메소드로 지정할 수도 있다.
jFrame.getContentPane().setLayout(new GridLayout(행수, 열수));
CardLayoutExam.java
package swingExam.sec04; import java.awt.CardLayout; import java.awt.Color; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class CardLayoutExam extends JFrame{ private JPanel redCard, greenCard, blueCard; // 메인 윈도우 설정 public CardLayoutExam() { this.setTitle("CardLayoutExam"); this.setSize(250,400); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // CardLayout으로 변경하고 3개의 카드 추가 this.getContentPane().setLayout(new CardLayout()); this.getContentPane().add("RedCard", getRedCard()); this.getContentPane().add("BlueCard", getBlueCard()); this.getContentPane().add("GreenCard", getGreenCard()); } // RedCard Jpanel 생성 public JPanel getRedCard() { if(redCard == null) { redCard = new JPanel(); redCard.setBackground(Color.RED); } return redCard; } // GreenCard Jpanel 생성 public JPanel getGreenCard() { if(greenCard == null) { greenCard = new JPanel(); greenCard.setBackground(Color.GREEN); } return greenCard; } // BlueCard Jpanel 생성 public JPanel getBlueCard() { if(blueCard == null) { blueCard = new JPanel(); blueCard.setBackground(Color.BLUE); } return blueCard; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ final CardLayoutExam jFrame = new CardLayoutExam(); jFrame.setVisible(true); // 반복 스레드 생성 new Thread(()->{ for(int i=0; i<10; i++) { try { Thread.sleep(1000); }catch(InterruptedException e) { } //이벤트 큐에 Runnable 객체 넣기 SwingUtilities.invokeLater(()->{ //CardLayout을 얻어 다음 카드 보여주기 CardLayout cardLayout = (CardLayout) jFrame.getContentPane().getLayout(); cardLayout.next(jFrame.getContentPane()); }); } }).start(); // 반복 스레드 시작 }); } }
핵심 키워드
- CardLayout 배치 관리자는 이름에서도 알 수 있듯이 여러 장의 카드를 포개 놓고 한 번에 하나의 카드를 보여주는 역할을 한다. 이때 카드는 하나의 JPanel로 구성된다.
- 여러 개의 카드를 추가하더라도 제일 먼저 추가한 카드만 보인다. 다른 카드는 아래에 겹쳐져 있어볼 수 없는데, 이 카드를 보이게 하려면 CardLayout의 first(), last(), next(), show() 메소드를 호출하면 된다.
NullLayoutExam.java
package swingExam.sec04; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class NullLayoutExam extends JFrame { private JButton btnOk; // 메인 윈도우 설정 public NullLayoutExam() { this.setTitle("NullLayoutExam"); 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.setText("확인"); // 버튼이 위치할 좌표값과 폭과 높이 설정 btnOk.setBounds(100, 50, 70, 60); } return btnOk; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ NullLayoutExam jFrame = new NullLayoutExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- NullLayout은 컨테이너의 setLayout() 메소드에 배치 관리자 대신 매개값을 null로 설정한 것을 말한다. 이것은 어떠한 배치 관리자도 사용하지 않고 좌표값으로 컴포넌트를 배치함을 뜻한다.
jFrame.getContentPane().setLayout(null);
setBounds(int x, int y, int width, int height);
PackExam.java
package swingExam.sec04; import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class PackExam extends JFrame{ private JButton btnOk; private JButton btnCancel; // 메인 윈도우 설정 public PackExam() { this.setTitle("FlowLayoutExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // FlowLayout으로 변경하고 버튼 추가 this.setLayout(new FlowLayout()); this.getContentPane().add(getBtnOk()); this.getContentPane().add(getBtnCancel()); // Pack() 메소드 호출 this.pack(); } // Ok 버튼 생성 private JButton getBtnOk() { if(btnOk == null) { btnOk = new JButton(); btnOk.setText("확인"); } return btnOk; } // Cancel 버튼 생성 private JButton getBtnCancel() { if(btnCancel==null) { btnCancel = new JButton(); btnCancel.setText("취소"); } return btnCancel; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ PackExam jFrame = new PackExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JWindow, JFrame, JDialog와 같이 java.awt.Window를 상속받는 최상위 레벨 컨테이너는 pack()이라는 메소드를 사용해서 내부의 컴포넌트의 크기에 맞게 컨테이너의 크기를 자동으로 조절할 수 있다.
- 컴포넌트에는 PreferredSize라는 속성이 있는데, 이것은 컴포넌트의 기본 배치 크기를 말한다. 컨테이너의 pack() 메소드가 호출되면 내부 컴포넌트의 getPreferredSize()를 호출해서 컴포넌트의 기본 배치 크기를 알아낸 뒤, 컨테이너의 크기를 계산한다.
- 컨테이너의 setSize() 메소드는 직접 컨테이너의 폭과 높이를 설정하지만, pack() 메소드는 내부 컴포넌트의 크기에 따라 컨테이너의 크기가 결정된다. 따라서 pack() 메소드를 호출하는 시점은 컨테이너에 컴포넌트들이 모두 배치가 끝난 시점이어야 한다.
ClosableExam1.java
package swingExam.sec05; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ClosableExam1 extends JFrame { private JButton btnClose; // 메인 윈도우 설정 public ClosableExam1() { this.setTitle("CloseExam"); this.setSize(300, 100); this.setLayout(new FlowLayout()); this.getContentPane().add(getBtnClose()); // WindowListner 추가 this.addWindowListener(new MyWindowAdapter()); } private JButton getBtnClose() { if(btnClose == null) { btnClose = new JButton(); btnClose.setText("닫기"); // ActionListner 추가 btnClose.addActionListener(new MyActionListner()); } return btnClose; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ ClosableExam1 jFrame = new ClosableExam1(); jFrame.setVisible(true); }); } } // WindowAdapter 클래스를 상속해서 WindowListner 클래스 작성 class MyWindowAdapter extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { System.exit(0); } } // ActionListner를 구현해서 ActionListner 클래스 작성 class MyActionListner implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }
ClosableExam2.java
package swingExam.sec05; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ClosableExam2 extends JFrame{ private JButton btnClose; // 메인 윈도우 설정 public ClosableExam2() { this.setTitle("CloseExam"); this.setSize(300,100); this.setLayout(new FlowLayout()); this.getContentPane().add(getBtnClose()); // 익명 WindowListner 객체 추가 this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); } }); } private JButton getBtnClose() { if(btnClose == null) { btnClose = new JButton(); btnClose.setText("닫기"); // 익명 ActionListner 객체 추가 btnClose.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); } return btnClose; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ ClosableExam2 jFrame = new ClosableExam2(); jFrame.setVisible(true); }); } }
ActionListenerExam.java
package swingExam.sec05; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ActionListenerExam extends JFrame { private JButton btnOk; private JButton btnCancel; // 메인 윈도우 설정 public ActionListenerExam() { this.setTitle("ActionListenerExam"); this.setSize(300, 100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLayout(new FlowLayout()); this.getContentPane().add(getBtnOk()); this.getContentPane().add(getBtnCancel()); } // ActionListener 타입의 필드 선언 및 익명 객체로 초기화 private ActionListener actionListner = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == btnOk) { System.out.println("확인 버튼을 클릭했습니다."); } else if (e.getSource() == btnCancel) { System.out.println("취소 버튼을 클릭했습니다."); } } }; // Ok 버튼 생성 private JButton getBtnOk() { if (btnOk == null) { btnOk = new JButton(); btnOk.setText("확인"); // actionListner 필드 대입 btnOk.addActionListener(actionListner); } return btnOk; } // Cancel 버튼 생성 private JButton getBtnCancel() { if(btnCancel==null) { btnCancel = new JButton(); btnCancel.setText("취소"); // actionListener 필드 대입 btnCancel.addActionListener(actionListner); } return btnCancel; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ ActionListenerExam jFrame = new ActionListenerExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 자바는 이벤트 소스 객체(컨테이너, 컴포넌트)와 이벤트 처리 객체(리스너)를 분리하는 위임형 방식을 사용한다. 위임형 방식이란 이벤트 소스에서 이벤트가 발생하면 직접 처리하지 않고 이벤트 소스에 추가된 리스너에게 이벤트 처리를 위임하는 방식이다.
- 컴포넌트에서 발생하는 모든 이벤트를 처리하기 위해서는 이벤트별로 리스너가 추가되어야 한다. JButton에서 발생하는 액션 이벤트와 마우스 이벤트를 동시에 처리하기 위해서는 액션 리스너와마우스 리스너가 모두 필요하다.
- 하지만, 동시에 발생되는 이벤트가 많다고 하더라도 모두 처리할 필요가 없다. 처리하고 싶은 관심 이벤트에 대해서만 리스너를 추가하면 된다.
- 컴포넌트에 리스너를 추가하기 위해서는 리스너 클래스를 먼저 작성해야 한다. 리스너 클래스를 생성하는 방법은 리스너 인터페이스를 구현하는 방법과 어댑터 클래스를 상속하는 방법이 있다.
- 리스너 인터페이스를 구현하려면 리스너 인터페이스에 정의되어 있는 이벤트 처리 메소드를 모두 재정의해야 한다.
- 리스너 어댑터를 상속하면 관심 있는 이벤트 처리 메소드만 재정의할 수 있기 때문에 리스너 인터페이스를 구현하는 방법보다 좀 더 효율적이다.
- 이벤트를 처리할 때 리스너 클래스를 외부 클래스로 선언하게 되면 컨테이너의 필드와 메소드에 접근하는 것이 불편하다. 그래서 리스너는 일반적으로 익명 객체로 작성한다.
- 만약 복수 개의 컴포넌트에서 동일한 리스너를 사용해서 이벤트를 처리하고 싶다면 리스너를 필드로 선언한다. 이 경우에는 리스너에서 어떤 컴포넌트에서 이벤트가 발생되었는지 코드로 구분해야 한다.
JButtonExam.java
package swingExam.sec06; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class JButtonExam extends JFrame { private JButton btn1, btn2, btn3; // 메인 윈도우 설정 public JButtonExam() { this.setTitle("JButtonExam"); this.setSize(300, 100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setLayout(new FlowLayout()); this.getContentPane().add(getBtn1()); this.getContentPane().add(getBtn2()); this.getContentPane().add(getBtn3()); } // 글자만 있는 버튼 생성 public JButton getBtn1() { if (btn1 == null) { btn1 = new JButton(); btn1.setText("새 문서"); btn1.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFileChooser jFileChooser = new JFileChooser(); jFileChooser.showOpenDialog(JButtonExam.this); } }); } return btn1; } // 아이콘만 있는 버튼 생성 public JButton getBtn2() { if (btn2 == null) { btn2 = new JButton(); btn2.setIcon(new ImageIcon(getClass().getResource("../image1.jpg"))); btn2.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFileChooser jFileChooser = new JFileChooser(); jFileChooser.showOpenDialog(JButtonExam.this); } }); } return btn2; } // 아이콘과 글자가 있는 버튼 생성 public JButton getBtn3() { if(btn3==null) { btn3 = new JButton(); btn3.setText("새 문서"); btn3.setIcon(new ImageIcon(getClass().getResource("../image.jpg"))); btn3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jFileChooser = new JFileChooser(); jFileChooser.showOpenDialog(JButtonExam.this); } }); } return btn3; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JButtonExam jFrame = new JButtonExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 버튼 컴포넌트는 AbstractButton을 상속받은 하위 클래스들을 말한다. 버튼 컴포넌트에는 JButton,JToggleButton, JRadioButton, JCheckBox가 있는데, 모두 사용자가 마우스로 클릭하여 사용할 수 있다.
- 버튼 컴포넌트를 마우스로 클릭하면 모두 ActionEvent가 발생한다. 그래서 addActionListener() 메소드로 ActionListener 객체를 등록하여 이벤트를 처리할 수 있다.
- JButton은 이미지와 텍스트로 구성된 일반적인 버튼을 만들 때 사용한다. JButton의 setText() 메소드는 버튼의 텍스트를 설정하고, setIcon() 메소드는 버튼의 이미지를 설정한다.
JButton jButton = new JButton(); jButton.setText("새문서"); jButton.setIcon( new ImageIcon( getClass().getResource("new.gif") );
JToggleButtonExam.java
package swingExam.sec06; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; public class JToggleButtonExam extends JFrame { private JPanel pFirst; private JPanel pSecond; private JToggleButton tbOnOff; private JToggleButton tbStart; private JToggleButton tbStop; // 메인 윈도우 설정 public JToggleButtonExam() { this.setTitle("JToggleButtonExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setLayout(new GridLayout(2, 1)); this.getContentPane().add(getPFirst()); this.getContentPane().add(getPSecond()); this.pack(); } public JPanel getPFirst() { if (pFirst == null) { pFirst = new JPanel(); pFirst.add(getTbOnOff()); } return pFirst; } public JPanel getPSecond() { if (pSecond == null) { pSecond = new JPanel(); pSecond.setBorder(new TitledBorder("원하는 기능은?")); pSecond.add(getTbStart()); pSecond.add(getTbStop()); // 배타적 선택을 위한 ButtonGroup 생성 및 토글 버튼 추가 ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(getTbStart()); buttonGroup.add(getTbStop()); } return pSecond; } // On/Off 토글 버튼 생성 public JToggleButton getTbOnOff() { if(tbOnOff==null) { tbOnOff = new JToggleButton(); tbOnOff.setText("On"); tbOnOff.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if(e.getStateChange()==ItemEvent.SELECTED) { getTbOnOff().setText("Off"); }else { getTbOnOff().setText("On"); } } }); } return tbOnOff; } // Start 토글 버튼 생성 public JToggleButton getTbStart() { if(tbStart==null) { tbStart = new JToggleButton(); tbStart.setText("Start"); tbStart.setIcon(new ImageIcon(getClass().getResource("../image1.jpg"))); tbStart.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(JToggleButtonExam.this, "Start"); } }); } return tbStart; } // Stop 토글 버튼 생성 public JToggleButton getTbStop() { if(tbStop==null) { tbStop = new JToggleButton(); tbStop.setText("Stop"); tbStop.setIcon(new ImageIcon(getClass().getResource("../image2.jpg"))); tbStop.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(JToggleButtonExam.this, "Stop"); } }); } return tbStop; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JToggleButtonExam jFrame = new JToggleButtonExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JToggleButton은 선택된 상태와 그렇지 않은 두 가지 상태를 가지는 버튼이다. 생성 방법은 JButton과 유사해서 텍스트와 이미지를 설정할 수 있다.
JToggleButton jToggleButton = new JToggleButton(); jToggleButton.setText("확인"); jToggleButton.setIcon(new ImageIcon( getClass().getResource("ok.gif") );
- JToggleButton은 선택된 상태를 가지는 버튼이기 때문에 ActionListener보다는 ItemListener로 이벤트를 처리하는 것이 좋다. ItemEvent의 getStateChange() 메소드는 JToggleButton이 선택되었을 경우 ItemEvent.SELECTED 상수값을 리턴한다.
jToggleButton.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if(e.getStateChange() = = ItemEvent.SELECTED) { //선택된 상태 } else { //해제된 상태 } } });
JLabelExam.java
package swingExam.sec07; import java.awt.GridLayout; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; public class JLabelExam extends JFrame { private JLabel jLabel1, jLabel2, jLabel3, jLabel4; // 메인 윈도우 설정 public JLabelExam() { this.setTitle("JLabelExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setLayout(new GridLayout(4, 1)); this.getContentPane().add(getJLabel1()); this.getContentPane().add(getJLabel2()); this.getContentPane().add(getJLabel3()); this.getContentPane().add(getJLabel4()); this.setSize(200, 300); } // JLabel 생성: 텍스트 좌측 정렬, EtchedBorder 적용 public JLabel getJLabel1() { if (jLabel1 == null) { jLabel1 = new JLabel(); jLabel1.setText("JLabel1"); jLabel1.setHorizontalAlignment(JLabel.LEFT); jLabel1.setBorder(new EtchedBorder()); } return jLabel1; } // JLabel 생성: 이미지 추가, 내용물 중앙 정렬 public JLabel getJLabel2() { if (jLabel2 == null) { jLabel2 = new JLabel(); jLabel2.setText("JLabel2"); jLabel2.setIcon(new ImageIcon(getClass().getResource("../image3.jpg"))); jLabel2.setHorizontalAlignment(JLabel.CENTER); jLabel2.setBorder(new EtchedBorder()); } return jLabel2; } // JLabel 생성: 이미지 왼쪽에 텍스트가 오도록 설정 public JLabel getJLabel3() { if (jLabel3 == null) { jLabel3 = new JLabel(); jLabel3.setText("JLabel3"); jLabel3.setIcon(new ImageIcon(getClass().getResource("../image2.jpg"))); jLabel3.setHorizontalAlignment(JLabel.CENTER); jLabel3.setHorizontalTextPosition(JLabel.LEFT); jLabel3.setBorder(new EtchedBorder()); } return jLabel3; } // JLabel 생성: 이미지와 텍스트 사이의 간격 설정 public JLabel getJLabel4() { if (jLabel4 == null) { jLabel4 = new JLabel(); jLabel4.setText("JLabel4"); jLabel4.setIcon(new ImageIcon(getClass().getResource("../image1.jpg"))); jLabel4.setHorizontalAlignment(JLabel.CENTER); jLabel4.setIconTextGap(200); jLabel4.setBorder(new EtchedBorder()); } return jLabel4; } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JLabelExam jFrame = new JLabelExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 텍스트 컴포넌트는 텍스트를 나타내거나 편집할 수 있는 컴포넌트를 말한다. 텍스트 컴포넌트에는JLabel, JTextField, JPasswordField, JTextArea, JEditorPane, JTextPane 등이 있다. 이 중에서 JLabel만 텍tm트를 편집할 수 없고, 나머지는 텍스트를 편집할 수 있다. 편집 가능한 텍스트 컴포넌트는 모두JTextComponent를 상속받아 각 컴포넌트의 특징에 맞게 설계되었다.
- JLabel은 편집할 수 없는 한 줄의 간단한 텍스트와 정적인 이미지를 보여주는 컴포넌트이다. JLabel 에 텍스트와 이미지를 설정하는 방법은 다음과 같다.
JLabel jLabel = new JLabel(); jLabel.setText("텍스트"); jLabel.setIcon(new ImageIcon( getClass().getResource("이미지파일"));
- 텍스트와 이미지의 배치는 정렬alignment과 위치position 그리고 간격gap으로 조절할 수 있다. 정렬은JLabel 전체 내용물의 위치를 의미하고, 위치는 이미지와 텍스트 사이의 상대적인 위치를 의미한다. 그리고 간격은 텍스트와 이미지의 간격이다.
//JLabel 영역에서의 내용물(텍스트+이미지)의 위치 setHorizontalAlignment( JLabel.LEFT | JLabel.CENTER | JLabel.RIGHT ); setVerticalAlignment( JLabel.TOP | JLabel.CENTER | JLabel.BOTTOM ); //텍스트와 이미지의 상대적인 위치 setHorizontalTextPosition( JLabel.LEFT | JLabel.CENTER | JLabel.RIGHT ); setVerticalTextPosition( JLabel.TOP | JLabel.CENTER | JLabel.BOTTOM ); //텍스트와 이미지 사이의 간격 setIconTextGap( iconTextGap );
- JLabel의 경계선은 기본적으로 없기 때문에 경계선의 모양을 주고 싶다면 setBorder() 메소드를 사용할 수 있다.
setBorder(Border border);
JTxtPwFieldExam.java
package swingExam.sec07; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class JTxtPwFieldExam extends JFrame { private JTextField txtId; private JPasswordField txtPassword; // 메인 윈도우 설정 public JTxtPwFieldExam() { this.setTitle("JTextField & JPasswordField"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setLayout(new GridLayout(2, 2)); this.getContentPane().add(new JLabel("아이디", JLabel.CENTER)); this.getContentPane().add(getTxtId()); this.getContentPane().add(new JLabel("패스워드", JLabel.CENTER)); this.getContentPane().add(getTxtPassword()); this.setSize(200, 100); } // JTextField 생성 public JTextField getTxtId() { if (txtId == null) { txtId = new JTextField(); txtId.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() >= KeyEvent.VK_A && e.getKeyCode() <= KeyEvent.VK_Z) { JOptionPane.showMessageDialog(JTxtPwFieldExam.this, "알파벳 이군요"); } else { JOptionPane.showMessageDialog(JTxtPwFieldExam.this, "알파벳이 아니군요"); } } }); } return txtId; } // JPasswordField 생성 public JPasswordField getTxtPassword() { if (txtPassword == null) { txtPassword = new JPasswordField(); txtPassword.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String password = new String(txtPassword.getPassword()); JOptionPane.showMessageDialog(JTxtPwFieldExam.this, "입력한 패스워드: " + password); } }); } return txtPassword; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JTxtPwFieldExam jFrame = new JTxtPwFieldExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JTextField와 JPasswordField는 단일 라인의 텍스트 입력란을 제공하는 컴포넌트이다. 차이점은JPasswordField는 사용자의 입력을 다른 사람이 볼 수 없도록 숨긴다는 것이다.
JTextField jTextField = new JTextField(); JPasswordField jPasswordField = new JPasswordField();
- 사용자가 입력한 텍스트는 JTextField일 경우 getText( ) 메소드로, JPasswordField일 경우에는 getPassword ( ) 메소드로 얻을 수 있다. getText( )는 String 타입으로 리턴하지만, getPassword()는 char[ ] 배열로 리턴하므로 String 타입으로 변환할 필요가 있다.
String inputData = jTextField.getText(); String inputData = new String( jPasswordField.getPassword() );
- JTextField와 JPasswordField는 두 가지 주요 이벤트가 발생한다. 키보드로 문자를 입력할 때마다 KeyEvent가 발생하고, Enter 키를 입력하면 ActionEvent가 발생한다. 사용자가 입력하는 각 문자마다 처리할 내용이 있다면 KeyEvent를 처리하는 것이 좋고, Enter 키를 누르기 전까지 입력된 모든 문자들을 한꺼번에 처리하려면 ActionEvent를 처리하는 것이 좋다.
- KeyEvent를 처리하기 위해 KeyListener 객체를 등록하는 코드는 다음과 같다. KeyListener의keyPressed() 메소드는 키보드를 누를 때마다 실행된다.
jTextField.addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { char keyCar = e.getKeyChar(); //입력된 문자 얻기 int keyCode = e.getKeyCode(); //입력된 키코드 얻기 ... } } );
- KeyEvent의 getKeyChar() 메소드는 입력된 키문자를 리턴하고, getKeyCode() 메소드는 키코드를 리턴한다. 키코드는 KeyEvent 클래스의 상수로 선언되어 있기 때문에 키보드에서 어떤 키가 입력되었는지 확인하려면 상수와 비교하면 된다. 예를 들어 다음은 F1 , F2 , F3 키가 입력되었는지 확인한다.
if(e.getKeyCode() = = KeyEvent.VK_F1) { … } if(e.getKeyCode() = = KeyEvent.VK_F2) { … } if(e.getKeyCode() = = KeyEvent.VK_F3) { … }
JTextAreaExam.java
package swingExam.sec07; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class JTextAreaExam extends JFrame { private JTextArea txtDisplay; private JPanel pSouth; private JTextField txtInput; private JButton btnSend; // 메인 윈도우 설정 public JTextAreaExam() { this.setTitle("JTextAreaExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().add(new JScrollPane(getTxtDisplay()), BorderLayout.CENTER); this.getContentPane().add(getPSouth(), BorderLayout.SOUTH); this.setSize(300, 200); } // JTextArea 생성 public JTextArea getTxtDisplay() { if (txtDisplay == null) { txtDisplay = new JTextArea(); txtDisplay.setEditable(false); } return txtDisplay; } // 남쪽에 부탁할 JPanel 생성 public JPanel getPSouth() { if (pSouth == null) { pSouth = new JPanel(); pSouth.setLayout(new BorderLayout()); pSouth.add(getTxtInput(), BorderLayout.CENTER); pSouth.add(getBtnSend(), BorderLayout.EAST); } return pSouth; } // JTextField 생성 public JTextField getTxtInput() { if (txtInput == null) { txtInput = new JTextField(); } return txtInput; } // JButton 생성 public JButton getBtnSend() { if (btnSend == null) { btnSend = new JButton(); btnSend.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getTxtDisplay().append(getTxtInput().getText() + "\n"); getTxtInput().setText(""); } }); } return btnSend; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JTextAreaExam jFrame = new JTextAreaExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JTextArea는 멀티 라인의 텍스트를 편집할 수 있는 컴포넌트이다. JTextArea는 자체적으로 스크롤을 제공하지 않으므로 JScrollPane에 추가해서 사용된다.
JTextArea jTextArea = new JTextArea(); JScrollPane jScrollPane = new JScrollPane( jTextArea );
- JTextArea에서 키보드로 텍스트를 편집할 경우에는 스크롤이 따라 움직이지만, 프로그램에 의해 편집될 경우에는 스크롤이 따라 움직이지 않는다. 이럴 때 다음 코드를 추가하면 스크롤이 자동으로 내용에 맞게 움직이게 된다.
jTextArea.setCaretPosition( jTextArea.getText().length() );
JEditorPaneExam.java
package swingExam.sec07; import java.awt.BorderLayout; import java.io.IOException; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.event.HyperlinkEvent; public class JEditorPaneExam extends JFrame { private JEditorPane jEditorPane; // 메인 윈도우 설정 public JEditorPaneExam() { this.setTitle("JEditorPaneExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().add(new JScrollPane(getJEditorPane()), BorderLayout.CENTER); this.setSize(400, 300); } // JEditorPane 생성 public JEditorPane getJEditorPane() { if(jEditorPane==null) { jEditorPane = new JEditorPane(); try { jEditorPane.setPage(getClass().getResource("../index.html")); }catch(Exception e) { } jEditorPane.setEditable(false); jEditorPane.addHyperlinkListener(e->{ if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) { try { jEditorPane.setPage(e.getURL()); }catch(IOException e2) { } } }); } return jEditorPane; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JEditorPaneExam jFrame = new JEditorPaneExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JEditorPane은 다양한 타입의 문서를 보여주거나 편집이 가능한 멀티 라인의 텍스트 컴포넌트이다. 기본적으로 단순 텍스트(text/plain), HTML(text/html) 타입의 문서를 지원한다. 다음 코드는 JEditorPane으로 HTML 파일의 내용을 보는 방법을 보여준다.
JEditorPane jEditorPane = new JEditorPane(); try { jEditorPane.setPage(HTML파일URL); } catch(Exception e) {} jEditorPane.setEditable(false);
- HTML 문서를 표시할 경우에는 setEditable(false)를 호출해서 편집할 수 없도록 하고, 사용자가 링크를 클릭할 경우 HyperlinkListener를 다음과 같이 등록해서 HyperlinkEvent 이벤트를 처리할 수 있다.
jEditorPane.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if(e.getEventType() = = HyperlinkEvent.EventType.ACTIVATED) { try { jEditorPane.setPage(e.getURL()); } catch(Exception e) {} } } });
JListExam.java
package swingExam.sec08; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridLayout; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; public class JListExam extends JFrame { private JPanel pWest; private JList listString; private JList listImage; private JLabel jLabel; // 메인 윈도우 설정 public JListExam() { this.setTitle("JListExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(250, 200); this.getContentPane().setBackground(Color.white); this.getContentPane().add(getPWest(), BorderLayout.WEST); this.getContentPane().add(getJLabel(), BorderLayout.CENTER); } // 좌측 목록 JPanel 생성 public JPanel getPWest() { if (pWest == null) { pWest = new JPanel(); pWest.setLayout(new GridLayout(2, 1)); pWest.add(new JScrollPane(getListString())); pWest.add(new JScrollPane(getListImage())); } return pWest; } // 텍스트 목록을 갖는 JList 생성 public JList getListString() { if (listString == null) { String[] items = { "Bass1", "Bass2", "Bass3" }; listString = new JList(items); listString.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { int selectedIndex = listString.getSelectedIndex(); ImageIcon image = new ImageIcon(getClass().getResource("../image" + (selectedIndex + 1) + ".jpg")); getJLabel().setIcon(image); } }); } return listString; } // 이미지 목록을 갖는 JList 생성 public JList getListImage() { if (listImage == null) { Vector items = new Vector(); for (int i = 1; i < 4; i++) { ImageIcon image = new ImageIcon(getClass().getResource("../image" + i + ".jpg")); items.addElement(image); } listImage = new JList(items); listImage.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { ImageIcon image = (ImageIcon) listImage.getSelectedValue(); getJLabel().setIcon(image); } }); } return listImage; } // 선택한 항목의 이미지를 보여주는 JLabel 생성 public JLabel getJLabel() { if (jLabel == null) { jLabel = new JLabel(); jLabel.setHorizontalAlignment(JLabel.CENTER); } return jLabel; } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JListExam jFrame = new JListExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 리스트 컴포넌트는 목록 중에서 항목을 선택할 수 있는 컴포넌트를 말한다. 리스트 컴포넌트에는 JList, JComboBox가 있다. JList는 목록에서 하나 이상의 항목을 선택할 수 있고, JComboBox는 버튼을 클릭했을 때 목록이 보이고, 그 중 하나를 선택할 수 있다.
- JList는 목록에서 하나 이상의 항목을 선택할 수 있는 컴포넌트이다. JList의 항목은 배열로 주거나, Vector 객체로 줄 수 있다.
//배열 String[] stringItems = { "one", "two", "tree", "four" }; JList jList = new JList(stringItems); //Vector 객체 Vector items = new Vector(); items.add("one"); items.add("two"); items.add("tree"); items.add("four"); JList jList = new JList( items );
- JList의 기본적인 항목 컴포넌트는 JLabel로 구성되어 있기 때문에 텍스트 이외에도 이미지 표현도 가능하다.
Vector items = new Vector(); items.add( new ImageIcon( getClass().getResource("fruit1.gif") ); items.add( new ImageIcon( getClass().getResource("fruit2.gif") ); JList jList = new JList( items );
- JList는 자동으로 스크롤 바가 생성되지 않으므로 JScrollPane에 추가해서 사용할 수 있다.
JScrollPane jScrollPane = new JScrollPane( jList );
- 사용자가 JList의 항목을 선택하면 ListSelectionEvent가 발생하기 때문에 ListSelectionListener를 등록하면 이벤트를 처리할 수 있다.
- JList는 선택 항목이 변경되면 ListSelectionEvent를 두 번 발생시킨다. 이것을 해결하기 위해ListSelectionEvent는 getValueIsAdjusting() 메소드를 제공하고 있다. 첫 번째 이벤트가 발생했을 때는 false를 리턴하고, 두 번째 이벤트가 연이어 발생하면 true를 리턴한다. 따라서 getValueIsAdjusting()가 false를 반환할 때만 이벤트 처리 코드를 실행하도록 해야 한다.
jList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if(e.getValueIsAdjusting() = = false) { //이벤트 처리 코드 } } });
- JList에서 선택된 항목을 알아내는 방법은 두 가지가 있다. getSelectedIndex() 메소드는 선택된 항목의 인덱스을 리턴하고, getSelectedValue()는 선택된 항목의 값이 리턴된다. 어떤 정보가 이벤트 처리 시 유리한지를 판단하고 사용하면 된다.
//선택된 항목의 인덱스 얻기 int selectedIndex = jList.getSelectedIndex(); int[] selectedIndices = jList.getSelectedIndices(); //선택된 항목의 텍스트 얻기 String selectedItem = (String) jList.getSelectedValue(); String[] selectedItems = (String[]) jList.getSelectedValues(); //선택된 항목의 이미지 얻기 ImageIcon selectedItem = (ImageIcon) jList.getSelectedValue(); ImageIcon[] selectedItem = (ImageIcon[]) jList.getSelectedValue();
JComboBoxExam.java
package swingExam.sec08; import java.awt.BorderLayout; import java.awt.Color; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class JComboBoxExam extends JFrame { private JPanel pNorth; private JComboBox comboString; private JComboBox comboImage; private JLabel jLabel; // 메인 윈도우 설정 public JComboBoxExam() { this.setTitle("JComboBoxExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().setBackground(Color.white); this.getContentPane().add(getPNorth(), BorderLayout.NORTH); this.getContentPane().add(getJLabel(), BorderLayout.CENTER); this.setSize(250, 200); } // 상단 목록 JPanel 생성 public JPanel getPNorth() { if (pNorth == null) { pNorth = new JPanel(); pNorth.add(getComboString()); pNorth.add(getComboImage()); } return pNorth; } // 텍스트 목록을 갖는 JComboBox 생성 public JComboBox getComboString() { if (comboString == null) { String[] arrString = { "Bass1", "Bass2", "Bass3" }; comboString = new JComboBox(arrString); comboString.setBackground(Color.white); comboString.addActionListener(e -> { int selectedIndex = comboString.getSelectedIndex(); ImageIcon image = new ImageIcon(getClass().getResource("../image" + (selectedIndex + 1) + ".jpg")); getJLabel().setIcon(image); }); } return comboString; } // 이미지 목록을 갖는 JComboBox 생성 public JComboBox getComboImage() { if (comboImage == null) { Vector vImage = new Vector(); for (int i = 1; i < 4; i++) { ImageIcon image = new ImageIcon(getClass().getResource("../image" + i + ".jpg")); vImage.add(image); } comboImage = new JComboBox(vImage); comboImage.setBackground(Color.white); comboImage.addActionListener(e -> { ImageIcon image = (ImageIcon) comboImage.getSelectedItem(); getJLabel().setIcon(image); }); } return comboImage; } // 선택된 항목의 이미지를 보여주는 JLabel 생성 public JLabel getJLabel() { if(jLabel==null) { jLabel = new JLabel(); jLabel.setHorizontalAlignment(JLabel.CENTER); } return jLabel; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JComboBoxExam jFrame = new JComboBoxExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- JComboBox는 버튼을 클릭하면 항목이 나열되고 그 중 하나를 선택할 수 있다. JComboBox의 항목은 배열로 주거나, Vector 객체로 줄 수 있다.
- JComboBox의 항목은 텍스트와 이미지 모두 가능하다. 이미지 항목은 ImageIcon 객체를 배열 또는 Vector 항목으로 만들면 된다.
- JComboBox에서도 getSelectedIndex ( ) 메소드는 선택된 항목의 인덱스을 리턴하고, getSelectedItem()은 선택된 항목의 값을 리턴한다.
JTableExam.java
package swingExam.sec09; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Vector; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableModel; public class JTableExam5 extends JFrame { private JTable jTable; private JPanel pSouth; private JTextField txtName, txtAge; private JButton btnInsert, btnUpdate, btnDelete; public JTableExam5() { this.setTitle("JTableExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().add(new JScrollPane(getJTable()), BorderLayout.CENTER); this.getContentPane().add(getPSouth(), BorderLayout.SOUTH); this.setSize(250, 250); } public JTable getJTable() { if (jTable == null) { jTable = new JTable(); final DefaultTableModel tableModel = (DefaultTableModel) jTable.getModel(); tableModel.addColumn("이름"); tableModel.addColumn("나이"); jTable.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { int rowIndex = jTable.getSelectedRow(); if (rowIndex != -1) { String name = (String) tableModel.getValueAt(rowIndex, 0); String age = (String) tableModel.getValueAt(rowIndex, 1); txtName.setText(name); txtAge.setText(age.toString()); } } }); } return jTable; } public JPanel getPSouth() { if (pSouth == null) { pSouth = new JPanel(); pSouth.setLayout(new GridLayout(3, 1)); JPanel pNameInput = new JPanel(); pNameInput.setLayout(new GridLayout(1, 2)); pNameInput.add(new JLabel("이름", JLabel.CENTER)); pNameInput.add(getTxtName()); pSouth.add(pNameInput); JPanel pAgeInput = new JPanel(); pAgeInput.setLayout(new GridLayout(1, 2)); pAgeInput.add(new JLabel("나이", JLabel.CENTER)); pAgeInput.add(getTxtAge()); pSouth.add(pAgeInput); JPanel pButton = new JPanel(); pButton.add(getBtnInsert()); pButton.add(getBtnUpdate()); pButton.add(getBtnDelete()); pSouth.add(pButton); } return pSouth; } public JTextField getTxtName() { if (txtName == null) { txtName = new JTextField(); } return txtName; } public JTextField getTxtAge() { if (txtAge == null) { txtAge = new JTextField(); } return txtAge; } public JButton getBtnInsert() { if (btnInsert == null) { btnInsert = new JButton(); btnInsert.setText("추가"); btnInsert.addActionListener(e -> { Object[] rowData = { txtName.getText(), txtAge.getText() }; DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel(); tableModel.addRow(rowData); txtName.setText(""); txtAge.setText(""); }); } return btnInsert; } public JButton getBtnUpdate() { if (btnUpdate == null) { btnUpdate = new JButton(); btnUpdate.setText("수정"); btnUpdate.addActionListener(e -> { DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel(); Vector<Vector> rows = tableModel.getDataVector(); Vector row = rows.elementAt(jTable.getSelectedRow()); row.set(0, txtName.getText()); row.set(1, txtAge.getText()); tableModel.fireTableDataChanged(); txtName.setText(""); txtAge.setText(""); }); } return btnUpdate; } public JButton getBtnDelete() { if(btnDelete==null) { btnDelete = new JButton(); btnDelete.setText("삭제"); btnDelete.addActionListener(e->{ int rowIndex = getJTable().getSelectedRow(); if(rowIndex!=-1) { DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel(); tableModel.removeRow(rowIndex); txtName.setText(""); txtAge.setText(""); } }); } return btnDelete; } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JTableExam5 jFrame = new JTableExam5(); jFrame.setVisible(true); }); } }
핵심 키워드
- JTable은 테이블 형식의 데이터를 표시하고 편집할 수 있는 컴포넌트이다. 테이블은 컬럼과 행으로 구성되어 있고, 컬럼과 행이 만나는 곳이 셀이다. 셀은 실제 데이터가 표시되는 곳이다. 하나의 컬럼을 구성하는 셀들은 동일한 데이터 타입을 가져야 한다.
- 간단한 JTable 객체를 만들기 위해서는 먼저 컬럼 이름을 포함하고 있는 1차원 String 배열과 셀의 데이터인 2차원 Object 배열을 생성해야 한다. 그리고 이들을 JTable 생성자를 호출할 때 매개 값으로 넘겨준다.
- 2차원 Object 배열을 생성할 때 주의할 점은 하나의 컬럼을 구성하는 셀의 데이터들은 모두 같은 타입이어야 한다.
String[] columnNames = { "이름", "나이" }; Object[][] rowData = { { "춘삼월", 25 }, { "하여름", 23 }, { "하바다", 26 } }; JTable jTable = new JTable(rowData, columnNames);
- 각 컬럼의 폭을 변경하고 싶다면 주어진 컬럼 이름에 해당하는 TableColumn을 getColumn() 메소드로 얻고, TableColumn의 setPreferredWidth() 메소드로 폭을 변경하면 된다. 또한 JTable은 자체적으로 스크롤을 지원하지 않으므로 JScrollPane에 추가해서 사용할 수 있다.
TableColumn tableColumn = jTable.getColumn("컬럼 이름"); tableColumn. setPreferredWidth(폭길이); JScrollPane jScrollPane = new JScrollPane( jTable); jFrame.getCententPane().add(jScrollPane, BorderLayout.CENTER);
- JTable은 TableModel 객체를 사용해서 컬럼과 셀의 데이터를 관리한다. JTable을 생성할 때 컬럼 이름인 1차원 String 배열과 셀의 데이터인 2차원 Object 배열을 생성자로 넘겨주면, TableModel이 내부적으로 생성되고 이 데이터들을 관리하게 된다.
- 컬럼 헤더와 셀 텍스트 내용을 수평 정렬하거나 셀 내용으로 다양한 컴포넌트를 넣고 싶다면 새로운 셀 렌더러를 정의해야 한다. 셀 렌더러는 넣고 싶은 컴포넌트를 상속하고 TableCellRenderer 인터페이스를 구현해서 만든다.
public class MyTableCellRenderer extends 컴포넌트 implements TableCellRenderer { public Component getTableCellRendererComponent(...) { //컴포넌트를 초기화하고 반환하는 코드 return this; } };
- getTableCellRendererComponent() 메소드는 상속받은 컴포넌트를 매개값으로 초기화하고 리턴해야 한다. 새로운 셀 렌더러가 정의되었다면 TableColumn의 setHeaderRenderer ( ) 또는 setCellRenderer() 메소드로 헤더와 셀 렌더러를 변경하면 된다.
TableColumn tc = jTable.getColumn("컬럼 이름"); TableCellRenderer tc = new new MyTableCellRenderer(); //헤더 모양 변경 tableColumn.setHeaderRenderer(tc); //셀 모양 변경 tableColumn. setCellRenderer(tc);
- JTable은 MouseEvent가 발생하므로 MouseListener를 등록해서 이벤트를 처리할 수 있다. MouseEvent에서 getSelectedColumn()과 getSelectedRow() 메소드를 이용하면 어떤 컬럼과 행에서 클릭되었는지 알아낼 수 있다.
jTable.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { int rowIndex = jTable.getSelectedRow(); int columnIndex = jTable.getSelectedColumn(); if(rowIndex !=-1 || columnIndex != -1) { //이벤트 처리 코드 } } });
- TableModel 타입은 JTable이 어떤 생성자로 만들어졌는지에 따라 다르다. JTable (Object[ ][ ] rowData, String[ ] columnNames ) 생성자로 만들 경우 AbstractTableModel을 상속해서 만들어지고, 매개변수가 없는 JTable ( ) 생성자로 만들었다면 내부적으로DefaultTableMode을 상속해서 만든다. AbstractTableModel과 DefaultTableModel의 차이점은 DefaultTableModel은 행을 추가, 삽입, 삭제할 수 있는 메소드들이 추가적으로 제공된다는 점이 있다.
JTable jTable = new JTable(); DefaultTableModel tableModel = (DefaultTableModel) jTable.getModel(); //맨 뒤에 행 추가 tableModel.addRow(rowData); //0 인덱스에 행 삽입 tableModel.insertRow(0, rowData); //2 인덱스의 행 삭제 tableModel.removeRow(2);
JTreeExam3.java
package swingExam.sec10; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.event.MouseAdapter; import java.awt.event.MouseListener; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; public class JTreeExam3 extends JFrame { private JTree jTree; public JTreeExam3() { this.setTitle("JTreeExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.getContentPane().add(new JScrollPane(getJTree()), BorderLayout.CENTER); this.setSize(200, 150); } public JTree getJTree() { if(jTree==null) { DefaultMutableTreeNode root = new DefaultMutableTreeNode("그룹리스트"); DefaultMutableTreeNode node1 = new DefaultMutableTreeNode("친구"); node1.add(new DefaultMutableTreeNode("친구1")); node1.add(new DefaultMutableTreeNode("친구2")); root.add(node1); jTree = new JTree(root); jTree.setCellRenderer(new MyTreeCellRenderer()); jTree.addTreeSelectionListener(treeSelectionListener); jTree.addMouseListener(mouseListener); } return jTree; } private TreeSelectionListener treeSelectionListener = e -> { TreePath treePath = e.getPath(); DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) treePath.getLastPathComponent(); String nodeText = (String) treeNode.getUserObject(); JOptionPane.showMessageDialog(JTreeExam3.this, "노드 변경: " + nodeText); }; private MouseListener mouseListener = new MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent e) { if (e.getClickCount() == 2) { TreePath treePath = jTree.getPathForLocation(e.getX(), e.getY()); DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) treePath.getLastPathComponent(); String nodeText = (String) treeNode.getUserObject(); JOptionPane.showMessageDialog(JTreeExam3.this, "더블 클릭: " + nodeText); } }; }; public class MyTreeCellRenderer implements TreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (!leaf) { JLabel jLabel = new JLabel(); jLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); jLabel.setIcon(new ImageIcon(getClass().getResource("../image1.jpg"))); jLabel.setText(value.toString()); return jLabel; } else { JPanel jPanel = new JPanel(); jPanel.setBackground(Color.white); jPanel.setLayout(new BorderLayout()); jPanel.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); JLabel lblWest = new JLabel(new ImageIcon(getClass().getResource("../image2.jpg"))); JLabel lblCenter = new JLabel(" " + value.toString() + " "); JLabel lblEast = new JLabel(new ImageIcon(getClass().getResource("../image3.jpg"))); jPanel.add(lblWest, BorderLayout.WEST); jPanel.add(lblCenter, BorderLayout.CENTER); jPanel.add(lblEast, BorderLayout.EAST); if(selected) { jPanel.setBackground(Color.orange); } return jPanel; } } } public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JTreeExam3 jFrame = new JTreeExam3(); jFrame.setVisible(true); }); } }
핵심 키워드
- JTree를 생성하려면 생성자의 매개값으로 루트 노드를 대입해야 하는데, 루트 노드는 DefaultMutableTreeNode로 생성한다. DefaultMutableTreeNode는 루트 노드뿐만 아니라 부모 노드, 리프 노드를 생성하는 데 사용된다.
DefaultMutableTreeNode |-- DefaultMutableTreeNode |-- DefaultMutableTreeNode |-- DefaultMutableTreeNode |-- DefaultMutableTreeNode JTree jTree = new JTree( );
- 노드의 아이콘과 리프 노드의 표현을 다르게 변형하고 싶다면 새로운 TreeCellRenderer를 만들어 기본 렌더러인 DefaultTreeCellRenderer를 대체하면 된다. 새로운 TreeCellRenderer는 TreeCellRenderer 인터페이스를 구현해서 만든다. 새로운 TreeCellRenderer가 정의되었다면 JTree의 setCellRenderer() 메소드로 기본 렌더러를 변경하면 된다.
public class MyTreeCellRenderer implements TreeCellRenderer { public Component getTreeCellRendererComponent(...) { //컴포넌트를 초기화하고 리턴하는 코드 } } jTree.setCellRenderer(new MyTreeCellRenderer());
- JTree에서 노드 선택이 변경되면 TreeSelectionEvent가 발생하기 때문에 TreeSelectionListener를 추가해서 이벤트를 처리할 수 있다. TreeSelectionListener의 valueChanged() 메소드는 노드 선택이 변경되면 호출된다.
public class MyTreeSelectionListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { //선택된 노드의 전체 경로 얻기 TreePath treePath = e.getPath(); //선택된 노드의 DefaultMutableTreeNode 얻기 DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); //선택된 노드의 텍스트 얻기 String userObject = (String) node.getUserObject(); //처리 코드 //~ } }
- TreeSelectionEvent의 getPath()는 루트 노드에서부터 선택된 노드까지의 경로 정보를 가지고 있는 TreePath를 리턴한다. TreePath의 getLastPathComponent()는 선택된 노드를 Object 타입으로 리턴하는데, DefaultMutableTreeNode로 타입 변환할 수 있다. 선택된 노드의 문자열은 DefaultMutableTreeNode의 getUserObject() 메소드로 얻을 수 있다. 만약 노드 선택 변경보다는 클릭과 더블 클릭에 더 관심이 있다면 다음과 같이 MouseEvent를처리할 수도 있다.
public class MyMouseListener extends MouseAdapter { public void mousePressed(MouseEvent e) { //JTree 얻기 JTree jTree = (JTree) e.getSource(); //선택된 노드의 전체 경로 얻기 TreePath treePath = jTree.getPathForLocation(e.getX(), e.getY()); //선택된 노드가 있을 경우 if(selRow !=-1) { if(e.getClickCount() = = 1) { //클릭했을 경우 실행할 코드 } else if(e.getClickCount() = = 2) { //더블 클릭했을 경우 실행할 코드 } } } }
JMenuExam2.java
package swingExam.sec11; import java.awt.event.ActionListener; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; public class JMenuExam2 extends JFrame { private JMenuBar jMenuBar; private JMenu menuFile; private JMenuItem menuItemSave, menuItemExit; public JMenuExam2() { this.setTitle("JMenuExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setJMenuBar(getJMenuBar()); this.setSize(300, 200); } public JMenuBar getJMenuBar() { if (jMenuBar == null) { jMenuBar = new JMenuBar(); jMenuBar.add(getMenuFile()); } return jMenuBar; } public JMenu getMenuFile() { if (menuFile == null) { menuFile = new JMenu("파일"); menuFile.add(getMenuItemSave()); menuFile.add(getMenuItemExit()); } return menuFile; } public JMenuItem getMenuItemSave() { if (menuItemSave == null) { menuItemSave = new JCheckBoxMenuItem("파일 저장"); menuItemSave.addActionListener(actionListener); } return menuItemSave; } public JMenuItem getMenuItemExit() { if (menuItemExit == null) { menuItemExit = new JMenuItem("종료"); menuItemExit.addActionListener(actionListener); } return menuItemExit; } private ActionListener actionListener = e -> { if (e.getSource() == menuItemSave) { if (menuItemSave.isSelected()) { JOptionPane.showMessageDialog(JMenuExam2.this, "해제 상태 >> 체크 상태."); } else { JOptionPane.showMessageDialog(JMenuExam2.this, "체크 상태 >> 해제 상태."); } } else if (e.getSource() == menuItemExit) { System.exit(0); } }; public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JMenuExam2 jFrame = new JMenuExam2(); jFrame.setVisible(true); }); } }
핵심 키워드
- 메뉴를 생성하기 위해서는 제일 먼저 JMenuBar를 생성하고 윈도우 컨테이너 상단에 배치해야 한다. JMenuBar를 배치할 수 있는 윈도우 컨테이너에는 JFrame, JDialog 등이 있다. 이 컨테이너는 JMenuBar를 상단에 배치하기 위해 setMenuBar() 메소드를 제공하고 있다.
- JMenuBar는 윈도우 상단에 메뉴를 수평으로 배치하는 역할만 하기 때문에 메뉴가 없을 경우에는 윈도우 상단에 보이지 않는다. JMenu로 생성된 메뉴를 JMenuBar에 추가해야만 윈도우 상단에 나타난다.
- JMenuBar에 추가된 JMenu는 마우스로 클릭했을 때 보여줄 메뉴 아이템과 서브 메뉴를 가져야한다. 메뉴 아이템은 JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem이고, 서브 메뉴는 JMenu를 말한다. 이들은 JMenu의 add() 메소드에 의해 추가된다.
JMenuBar jMenuBar = new JMenuBar(); jFrame.setJMenuBar(jMenuBar); JMenu jMenu = new JMenu("메뉴명"); jMenuBar.add(jMenu); //메뉴 아이템 생성 JMenuItem menuItem1 = new JMenuItem("JMenuItem"); JMenuItem menuItem2 = new JCheckBoxMenuItem("JCheckBoxMenuItem"); JMenuItem menuItem3 = new JRadioButtonMenuItem("JRadioButtonMenuItem"); //서브 메뉴 생성 JMenu subMenu = new JMenu("JMenu"); JMenuItem subMenuItem1 = new JMenuItem("JMenuItem"); JMenuItem subMenuItem2 = new JMenuItem("JMenuItem"); jMenuSub.add(subMenuItem1); jMenuSub.add(subMenuItem1); //메뉴 아이템과 서브 메뉴 추가 jMenu.add(menuItem1); jMenu.add(menuItem2); jMenu.add(new JSeparator()); jMenu.add(menuItem3); jMenu.add(subMenu);
- 사용자가 마우스로 메뉴 아이템을 선택하면 ActionEvent가 발생하게 된다. 따라서 메뉴 아이템에 ActionListener를 등록하여 이벤트를 처리할 수 있다. 메뉴 아이템마다 별도의 ActionListener를 추가해도 되지만, 하나만 만들어서 모든 메뉴 아이템의 ActionEvent를 처리하는 것이 편리하다. ActionEvent 의 getSource ( ) 는 마우스로 선택한 JMenuItem 을 리턴하고 , getActionCommand()는 JMenuItem의 텍스트를 리턴한다. 따라서 어떤 JMenuItem을 선택했는지는 다음과 같이 2가지 방법으로 구분할 수 있다.
public class MenuActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { //선택된 JMenuItem 객체로 구분 JMenuItem selected = e.getSource(); if(selected = = menuItem1) { //처리 코드 } else if(selected = = menuItem2) { //처리 코드 } //선택된 JMenuItem의 텍스트로 구분 String ac = e.getActionCommand(); if(ae.equals("메뉴 문자열1")) { //처리 코드 } else if(ae.equals("메뉴 문자열2")) { //처리 코드 } } }
JToolBarExam.java
package swingExam.sec12; import java.awt.BorderLayout; import java.awt.event.ActionListener; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.border.SoftBevelBorder; public class JToolBarExam extends JFrame { private JMenuBar jMenuBar; private JToolBar jToolBar; private JButton btnNew, btnSave, btnCopy, btnPaste; public JToolBarExam() { this.setTitle("JToolBarExam"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setJMenuBar(getJMenuBar()); this.getContentPane().add(getJToolBar(), BorderLayout.NORTH); this.setSize(300, 200); } public JMenuBar getJMenuBar() { if(jMenuBar==null) { jMenuBar = new JMenuBar(); jMenuBar.add(new JMenu("파일")); jMenuBar.add(new JMenu("도움말")); } return jMenuBar; } public JToolBar getJToolBar() { if(jToolBar==null) { jToolBar = new JToolBar(); jToolBar.add(getBtnNew()); jToolBar.add(getBtnSave()); jToolBar.addSeparator(); jToolBar.add(getBtnCopy()); jToolBar.add(getBtnPaste()); } return jToolBar; } public JButton getBtnNew() { if(btnNew==null) { btnNew = new JButton(); btnNew.setIcon(new ImageIcon(getClass().getResource("../image1.jpg"))); btnNew.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); btnNew.setToolTipText("새로만들기"); btnNew.addActionListener(actionListener); } return btnNew; } public JButton getBtnSave() { if(btnSave==null) { btnSave = new JButton(); btnSave.setIcon(new ImageIcon(getClass().getResource("../image2.jpg"))); btnSave.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); btnSave.setToolTipText("저장"); btnSave.addActionListener(actionListener); } return btnSave; } public JButton getBtnCopy() { if(btnCopy==null) { btnCopy = new JButton(); btnCopy.setIcon(new ImageIcon(getClass().getResource("../image3.jpg"))); btnCopy.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); btnCopy.setToolTipText("복사"); btnCopy.addActionListener(actionListener); } return btnCopy; } public JButton getBtnPaste() { if(btnPaste==null) { btnPaste = new JButton(); btnPaste.setIcon(new ImageIcon(getClass().getResource("../image.jpg"))); btnPaste.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); btnPaste.setToolTipText("붙여넣기"); btnPaste.addActionListener(actionListener); } return btnPaste; } public ActionListener actionListener = e->{ if(e.getSource()==btnNew) { JOptionPane.showMessageDialog(JToolBarExam.this, "[새로만들기]를 클릭"); }else if(e.getSource()==btnSave) { JOptionPane.showMessageDialog(JToolBarExam.this, "[저장]을 클릭"); }else if(e.getSource()==btnCopy) { JOptionPane.showMessageDialog(JToolBarExam.this, "[복사]를 클릭"); }else if(e.getSource()==btnPaste) { JOptionPane.showMessageDialog(JToolBarExam.this, "[붙여넣기]를 클릭"); } }; public static void main(String[] args) { SwingUtilities.invokeLater(()->{ JToolBarExam jFrame = new JToolBarExam(); jFrame.setVisible(true); }); } }
핵심 키워드
- 최상위 레벨 컨테이너에서 JToolBar를 상단에 배치하는 방법은 JToolBar를 생성한 뒤 BoardLayout을 이용해서 북쪽에 배치하면 된다. JToolBar 내부에 컴포넌트가 추가되지 않으면 JToolBar가 북쪽에 위치하더라도 보이지 않는다.
- 프로그램 실행 중에는 JToolBar의 요철 부분을 마우스로 끌어서 컨테이너의 동·서·남·북에 붙일 수 있다. 또한 JToolBar를 중앙에 끌어 놓으면 툴바 윈도우가 만들어진다. 이러한 JToolBar의 끌기 기능을 없애려면 setFloatable(false)를 호출하면 된다.
- JToolBar 내부 컴포넌트는 일반적으로 JButton으로 구성되나 다른 컴포넌트도 추가 가능하다.
JToolBar jToolBar = new JToolBar(); jFrame.getContentPane().add(jToolBar, BorderLayout.NORTH); jToolBar.setFloatable(false); JButton jButton = new JButton(); jButton.setIcon(new ImageIcon(getClass().getResource("image.gif"))); //아이콘 jButton.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); //돌출된 모양 jButton.setToolTipText("새로만들기"); //마우스를 올려놓았을 때 나오는 풍선 도움말 jToolBar.add(jButton);
결론!
해당 문제를 풀면서 자바에서의 Swing을 이용해서 UI 프로그램을 개발하는 법을 익힐 수 있었다.
Share article