step15. Swing
이번 단계에서 배울 것은 GUI (Graphic User Interface)이다. 이것을 배우기는 하지만, 이 친구를 이용해서 무언가를 할 일은 거의 없을 것이다. 그냥 java를 연습해본다고 생각하면 된다고 했다.
public static void main(String[] args) {
// JFrame 객체 생성하고
JFrame f=new JFrame("Hello Frame");
// 초기 위치와 크기 지정
f.setBounds(100, 100, 500, 500);
// 프레임을 닫았을때 자동으로 프로세스 종료 되도록
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 레이아웃 매니저를 사용 하지 않도록 설정
f.setLayout(null);
// 버튼 객체 생성
JButton btn=new JButton("눌러보셈");
// 버튼의 위치와 크기 지정
btn.setBounds(10, 10, 100, 40);
// 프레임에 버튼 추가하기
f.add(btn);
// 프레임이 보이도록 한다.
f.setVisible(true);
new MyFrame("나만의 프레임");
}
public static class MyFrame extends JFrame{
// 생성자
public MyFrame(String title) {
super(title);
// 초기화 작업
setBounds(200, 200, 500, 500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
setLayout(null);
JButton btn=new JButton("눌러보셈");
btn.setBounds(10, 10, 100, 40);
add(btn);
}
}
예시의 두 가지는 모두 같은 결과를 낸다. 즉, 한 번의 run에 frame이 두 개 나타나게 된다. 그리고 frame을 하나라도 닫으면 두 frame 모두 닫히게 된다. 이는 frame 관련 process가 동일 process라는 것을 의미한다.
먼저 main method로 만들어진 예시는 frame 객체의 참조값에 점을 붙여서, 두 번째 inner class로 만들어두고 해당 객체를 new로 만들어낸 예시는 this에 점을 붙여서 method를 사용했다. 두 번째의 경우는 이 'this.' 이 생략되었다고 할 수 있다.
이러한 별 기능도 없는 frame 하나를 만들기 위해서 C 언어의 경우는 1000줄쯤 필요하다고 했다. 어떻게 만들어지는지는 몰라도 된댔다. 알 수도 없다고 했다. 하지만 java에서는 JFrame을 상속받아서 다 해주는 것이다.
- 01
import javax.swing.JButton;
import javax.swing.JFrame;
public class MyFrame extends JFrame{
// 생성자
public MyFrame(String title) {
super(title);
setBounds(100, 100, 500, 500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// 프레임의 레이아웃 메니저를 사용하지 않기 때문에 UI 를 절대 좌표에 직접 배치해야 한다.
setLayout(null);
JButton btn1=new JButton("버튼1");
// 버튼의 위치
btn1.setLocation(10, 10);
// 버튼의 크기
btn1.setSize(100, 30);
add(btn1); // 프레임에 btn1 추가 하기
JButton btn2=new JButton("버튼2");
// 버튼의 위치와 크기를 한번에 지정하기
btn2.setBounds(120, 10, 100, 30);
add(btn2); // 프레임에 btn2 추가 하기
JButton btn3=new JButton("버튼3");
// 버튼의 위치와 크기를 한번에 지정하기
btn3.setBounds(230, 10, 100, 30);
add(btn3); // 프레임에 btn3 추가 하기
JButton btn4=new JButton("버튼4");
// 버튼의 위치와 크기를 한번에 지정하기
btn4.setBounds(340, 10, 100, 30);
add(btn4); // 프레임에 btn3 추가 하기
setVisible(true);
}
public static void main(String[] args) {
new MyFrame("나의 프레임");
}
}
버튼의 위치와 크기를 따로 지정하려고 한다면 setLocation( ), setSize( ) method를 사용해도 좋지만, 굳이 번거롭게 그럴 필요 없이 동시에 setBountds( ) method를 이용하면 좋다. 해당 객체의 위치와 크기를 직접 지정한다.
- 02
// 레이 아웃 메니저를 FlowLayout 으로 지정한다.
// setLayout(new FlowLayout(0));
setLayout(new FlowLayout(FlowLayout.LEFT));
JButton btn1=new JButton("버튼1");
add(btn1);
JButton btn2=new JButton("버튼2");
add(btn2);
JButton btn3=new JButton("버튼3");
add(btn3);
JButton btn4=new JButton("버튼4");
add(btn4);
Layout을 FlowLayout 객체를 만들어서 넣어줄 수도 있다. 코드 이미지에 보이는 파란색 대문자로 된 것은 static final constant이다.
해당 class에 대한 정보를 찾아보면 각 static final constant에 숫자가 배정되어 있음을 볼 수 있다. 이는 아무런 의미가 없을 숫자에 의미를 부여하는 것이다.
setBounts( ) method의 경우 non-static method로 앞에 'this.' 이 생략된 경우이다. 그런데 이미지를 자세히 보면(잘 안보이겠지만) this.EXIT_ON_CLOSE 아래에 희미한 붉은 줄이 보이고, 해당 코드 줄 왼쪽에 메시지가 하나 있음을 확인할 수 있다. 이 메시지는 위 이미지와 같이 뜬다. 왜 그럴까?
사실 여기에서 this는 MyFrame 객체를 가리킨다. 당연하다. 물론 MyFrame에도 EXIT_ON_CLOSE가 있긴 한데, 앞서 말했듯 파란색 친구는 static final constant field이다. 즉 static 영역에 있는 것이기 때문에 this로 접근하는 것이 아니다. 그래도 봐주는 것이긴 한데, 봐주면서도 겁을 주려고 하는 것이기에 붉은 표시를 하는 것이라고 했다.
- 03
// 레이아웃 메니저를 GridLayout 으로 지정하기 (2행 2열)
setLayout(new GridLayout(2, 2));
Layout을 GridLayout 객체를 넣어줄 수도 있다. 이 경우 2행 2열 격자 배열이 된다.
- 04
// 레이아웃 메니저를 BorderLayout 으로 지정하기
setLayout(new BorderLayout());
JButton btn1=new JButton("버튼1");
add(btn1, BorderLayout.NORTH);
JButton btn2=new JButton("버튼2");
add(btn2, BorderLayout.WEST);
JButton btn3=new JButton("버튼3");
add(btn3, BorderLayout.EAST);
JButton btn4=new JButton("버튼4");
add(btn4, BorderLayout.SOUTH);
Layout에 BorderLayout 객체를 넣어줄 수 있다. 이때 BorderLayout에는 예시의 네 가지에 center까지 더해서 총 다섯 가지의 옵션이 있다. BorderLayout.NORTH의 자리는 "North" 가 된다.
- 05
// default 생성자
public MyFrame() {
// MyFrame 의 레이아웃 메니저 지정하기
setLayout(new FlowLayout());
// 버튼
JButton sendBtn=new JButton("전송");
JButton removeBtn=new JButton("삭제");
// 프레임에 버튼 추가하기 ( FlowLayout 의 영향을 받는다 )
add(sendBtn);
add(removeBtn);
JOptionPane.showMessageDialog(this, "오잉?");
// ActionListener 인터페이스 type 의 참조값
ActionListener listener=new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// System.out.println("전송 합니다~");
JOptionPane.showMessageDialog(MyFrame.this, "전송합니다~");
}
};
sendBtn.addActionListener(listener);
// lambda 식 활용해 보기
removeBtn.addActionListener((e)->{
JOptionPane.showMessageDialog(MyFrame.this, "삭제합니다~");
});
}
public static void main(String[] args) {
// MyFrame 클래스를 이용해서 객체 생성하고 참조값을 지역변수 frame 에 담기
MyFrame frame=new MyFrame();
// 프레임의 제목 설정
frame.setTitle("나의 프레임");
// 프레임을 닫으면 자동으로 프로세스가 종료 되도록 한다.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(100, 100, 500, 500);
frame.setVisible(true);
}
이 예시의 경우 알림 dialog가 뜨고, 버튼이 반응한다. 알림 dialog는 showMessageDialog( ) method를 사용한 것인데, ActionListener 객체 안과 밖에서 인자로 넣는 this의 꼴이 다른 것을 볼 수 있다. 그것은 ActionListener 객체 안에서의 this는 해당 객체를 가리키기 때문에, MyFrame 객체를 참조하기 위해서는 그냥 this를 쓰면 안되기 때문에 다른 꼴이 된 것이다. 그리고 이 ActionListener 객체는 anonymous class의 꼴로 만들어져 있다.
javascript에서도 비슷한 내용인 addEventListener를 다뤘었다. 다른 점은 javascript에서는 function이 단독으로 존재할 수 있었지만 java에서는 method 혼자 단독으로 존재하지 못하고 class 안에 존재해야 한다는 점이다.
Lambda 식 표현도 있는데, 이는 웹 프로그래밍에서는 잘 안쓸 것이고, 안드로이드 개발이라면 자주 쓸 것이라고 했다.
- 06
public class MyFrame extends JFrame implements ActionListener{
// default 생성자
public MyFrame() {
// MyFrame 의 레이아웃 메니저 지정하기
setLayout(new FlowLayout());
// 버튼
JButton sendBtn=new JButton("전송");
JButton removeBtn=new JButton("삭제");
// 버튼에 command 등록하기(어떤 기능을 하는 버튼인지)
sendBtn.setActionCommand("send");
removeBtn.setActionCommand("remove");
// 프레임에 버튼 추가하기 ( FlowLayout 의 영향을 받는다 )
add(sendBtn);
add(removeBtn);
// MyFrame 객체의 참조값을 전달해서 리스너 등록하기
sendBtn.addActionListener(this);
removeBtn.addActionListener(this);
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
// JOptionPane.showMessageDialog(this, "버튼을 눌렀네?");
// 액션 command 읽어오기
String command=e.getActionCommand();
if(command.equals("send")) {// 전송 버튼을 눌렀을때
JOptionPane.showMessageDialog(this, "전송합니다.");
}else if(command.equals("remove")){// 삭제 버튼을 눌렀을때
JOptionPane.showMessageDialog(this, "삭제합니다.");
}
}
}
이번 예시는 이전 예시와 같은 동작이다. 여기서 짚고 넘어가고자 하는 것은 비교를 할 때 사용하는 equals( ) 이다. 문자열이 같은지 비교를 하기 위해서는 '==' 와 같은 동등 비교는 불가능하다. 그래서 method를 사용해서 비교를 하는 것이다. 이를 이용해서 알아서 actionListener의 actionPerformed를 수행하게 하는 예시이다. 이 actionPerformed method는 ActionListener를 implements 했기 때문에 강제로 override 해야한다.
- 07
public class MyFrame extends JFrame implements ActionListener{
// 필드 정의하기
JButton sendBtn;
JButton removeBtn;
// default 생성자
public MyFrame() {
// MyFrame 의 레이아웃 메니저 지정하기
setLayout(new FlowLayout());
// 버튼의 참조값을 필드에 저장하기
this.sendBtn=new JButton("전송");
removeBtn=new JButton("삭제");
// 프레임에 버튼 추가하기 ( FlowLayout 의 영향을 받는다 )
add(sendBtn);
add(removeBtn);
// MyFrame 객체의 참조값을 전달해서 리스너 등록하기
sendBtn.addActionListener(this);
removeBtn.addActionListener(this);
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
// 이벤트가 발생한 UI(JButton) 의 참조값을 얻어온다.
Object obj=e.getSource();
if(obj == sendBtn) {// 전송 버튼을 눌렀다면
JOptionPane.showMessageDialog(this, "전송합니다.");
}else if(obj == removeBtn) {// 삭제 버튼을 눌렀다면
JOptionPane.showMessageDialog(this, "삭제합니다.");
}
}
}
마찬가지로 같은 결과를 낸다. 이번에는 action command 없이 같은 동작을 하게끔 한다. 먼저 field에 버튼의 참조값을 저장해두고, 해당 참조값을 비교하는 방식의 예시이다.
- 08
public class MyFrame extends JFrame implements ActionListener{
JTextField inputMsg;
// default 생성자
public MyFrame() {
setLayout(new FlowLayout());
// 문자열 한줄을 입력할수 있는 JTextField
inputMsg=new JTextField(10);
add(inputMsg);
JButton sendBtn=new JButton("전송");
sendBtn.addActionListener(this);
add(sendBtn);
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
// JTextField 에 입력한 문자열을 읽어와야한다.
String msg=inputMsg.getText();
JOptionPane.showMessageDialog(this, msg);
}
}
문자열을 입력할 수 있는 JTextField 객체를 사용한 예시이다. 예시에서처럼 입력받은 메시지를 field에 담아야 하는데, local variable에 담게되면 method에서 들고 올 수 없게 된다. 이 field와 local variable의 차이를 기억하자.
- 09
public class MyFrame extends JFrame
implements ActionListener, KeyListener{
// 필드
JTextField inputMsg;
JLabel lab1;
//default 생성자
public MyFrame() {
setLayout(new FlowLayout(FlowLayout.LEFT));
// 문자열 한줄을 입력할수 있는 JTextField
inputMsg=new JTextField(10);
add(inputMsg);
inputMsg.addKeyListener(this);
JButton sendBtn=new JButton("전송");
sendBtn.addActionListener(this);
add(sendBtn);
// JLabel 객체 생성
lab1=new JLabel("label입니다.");
add(lab1);
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
// JTextField 에 입력한 문자열을 읽어와야한다.
String msg=inputMsg.getText();
// 읽어온 문자열을 JLabel 에 추가하기
lab1.setText(msg);
// 입력창 문자열 삭제 하기
inputMsg.setText("");
}
// 키를 눌렀을때 호출되는 메소드
@Override
public void keyPressed(KeyEvent e) {
System.out.println("pressed");
// 눌러진 키보드의 코드값을 읽어온다.
int code=e.getKeyCode();
if(code == KeyEvent.VK_ENTER) {// 엔터키를 눌렀다면
// JTextField 에 입력한 문자열을 읽어와야한다.
String msg=inputMsg.getText();
// 읽어온 문자열을 JLabel 에 추가하기
lab1.setText(msg);
// 입력창 문자열 삭제 하기
inputMsg.setText("");
}
}
@Override
public void keyReleased(KeyEvent e) {
System.out.println("released");
}
@Override
public void keyTyped(KeyEvent e) {
System.out.println("typed");
}
}
이번 예시에서는 키보드 타이핑까지 가능한 KeyListener를 implements 했다. 그래서 강제로 keyPressed( )와 keyReleased( ), keyTyped( ) method를 override 했다.
- 10
public class MyFrame extends JFrame
implements ActionListener, KeyListener{
// 필드
JTextField inputMsg;
JLabel lab1;
DefaultListModel<String> model;
JList<String> list;
// static final 상수
static final String COMMAND_SEND="send";
static final String COMMAND_REMOVE="remove";
// default 생성자
public MyFrame() {
setLayout(new BorderLayout());
// 문자열 한줄을 입력할수 있는 JTextField
inputMsg=new JTextField(10);
inputMsg.addKeyListener(this);
JButton sendBtn=new JButton("전송");
sendBtn.setActionCommand(COMMAND_SEND);
sendBtn.addActionListener(this);
// 삭제 버튼
JButton removeBtn=new JButton("선택 삭제");
removeBtn.setActionCommand(COMMAND_REMOVE);
removeBtn.addActionListener(this);
// 삭제 버튼 하단에 배치하기
add(removeBtn, BorderLayout.SOUTH);
// JLabel 객체 생성
lab1=new JLabel("label입니다.");
// JPanel 객체 생성
JPanel panel=new JPanel();
// 패널도 레이아웃을 지정할수 있다( 기본값은 FlowLayout 가운데 정렬이다 )
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
// JPanel 에 UI 추가 하고
panel.add(inputMsg);
panel.add(sendBtn);
panel.add(lab1);
// 패널에 배경색 지정하기
panel.setBackground(Color.YELLOW);
// JFrame 에 JPanel 을 북쪽에 배치하기
add(panel, BorderLayout.NORTH);
// 목록을 출력할수 있는 JList
list=new JList<String>();
// 기본 모델 객체 ( 목록에 출력할 data 를 가지고 있는 객체 )
model=new DefaultListModel<String>();
model.addElement("김구라");
model.addElement("해골");
model.addElement("원숭이");
// 목록에 모델 연결하기
list.setModel(model);
// 스크롤 패널에 목록 넣어주기
JScrollPane sc=new JScrollPane(list,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// 스크롤 패널을 프레임의 가운데에 배치하기
add(sc, BorderLayout.CENTER);
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
// 눌러진 버튼의 command 를 읽어온다.
String command=e.getActionCommand();
if(command.equals(COMMAND_SEND)) {// 전송 버튼을 눌렀을때
send();
}else if(command.equals(COMMAND_REMOVE)) {// 삭제 버튼을 눌렀을때
// JList 객체에게 선택된 item 이 있는지 있다면 몇번째 아이템이 선택되었는지
// 물어 봐야 한다. (메소드를 이용해서 알아낸다)
int selectedIndex=list.getSelectedIndex();
if(selectedIndex >= 0) {// 선택된 cell 이 있을때
// 정말로 삭제 할것인지 물어본다.
int result=JOptionPane.showConfirmDialog(this, "삭제 할겨?");
if(result==JOptionPane.YES_OPTION) {
// JList 에 연결된 모델에서 해당 인덱스를 삭제한다.
model.remove(selectedIndex);
}
}else {//선택된 cell 이 없을때
JOptionPane.showMessageDialog(this, "삭제할 cell 을 선택하세요");
}
}
}
// 메소드 추가
public void send() {
// JTextField 에 입력한 문자열을 읽어와야한다.
String msg=inputMsg.getText();
// 읽어온 문자열을 JLabel 에 추가하기
lab1.setText(msg);
// 입력창 문자열 삭제 하기
inputMsg.setText("");
// 모델에 입력한 문자열 추가하기
model.addElement(msg);
}
// 키를 눌렀을때 호출되는 메소드
@Override
public void keyPressed(KeyEvent e) {
// System.out.println("pressed");
// 눌러진 키보드의 코드값을 읽어온다.
int code=e.getKeyCode();
if(code == KeyEvent.VK_ENTER) {//엔터키를 눌렀다면
send();
}
}
@Override
public void keyReleased(KeyEvent e) {
// System.out.println("released");
}
@Override
public void keyTyped(KeyEvent e) {
// System.out.println("typed");
}
}
중간의 기본 데이터를 넣어주는 코드를 보면, model 객체에 data를 넣어주고, 또 그 model 객체를 list에 연결해주고 있다. 즉, 어떤 기능 추가도 객체로 인식한다는 것이라고 했다. 객체 안에 기능(method)이 있고, 객체의 참조값을 전달해주면 그 기능을 사용하게끔 하는 것이라 했다.
그리고 send( ) method의 경우 field의 것들을 부품으로 활용하고 있다.
앞으로 코딩을 할 때 nullPointerException이 많이 발생할 것이다. 어떤 변수가 null인데 method를 사용하려 한다든지, 참조값을 미처 넣지 않아서 null일 수도 있다. 이렇게 내용을 정리했지만, java에서 중요한 부분은 step04. ~ 14. 까지이다.
- 11
public class MyFrame extends JFrame implements ActionListener{
// 필드
JTextField tf_num1, tf_num2;
JLabel label_result;
// default 생성자
public MyFrame() {
// 프레임의 레이아웃 법칙 설정하기
setLayout(new BorderLayout());
// JPanel
JPanel topPanel=new JPanel();
topPanel.setBackground(Color.YELLOW);
//Panel 을 북쪽에 배치하기
add(topPanel, BorderLayout.NORTH);
// JTextField 객체를 만들에서 JPanel 에 추가하기
tf_num1=new JTextField(10);
topPanel.add(tf_num1);
// 기능 버튼 객체를 만들어서 JPanel 에 추가하기
JButton plusBtn=new JButton("+");
JButton minusBtn=new JButton("-");
JButton multiBtn=new JButton("*");
JButton divideBtn=new JButton("/");
topPanel.add(plusBtn);
topPanel.add(minusBtn);
topPanel.add(multiBtn);
topPanel.add(divideBtn);
// 두번째 JTextField 만들어서 패널에 추가 하기
tf_num2=new JTextField(10);
topPanel.add(tf_num2);
// JLabel
JLabel label1=new JLabel("=");
label_result=new JLabel("0");
// 패널에 라벨 추가하기
topPanel.add(label1);
topPanel.add(label_result);
// 버튼에 리스너 등록하기
plusBtn.addActionListener(this);
minusBtn.addActionListener(this);
multiBtn.addActionListener(this);
divideBtn.addActionListener(this);
// 버튼에 액션 command 지정하기
plusBtn.setActionCommand("plus");
minusBtn.setActionCommand("minus");
multiBtn.setActionCommand("multi");
divideBtn.setActionCommand("divide");
}
(main method 중략)
// ActionListener 인터페이스를 구현 해서 강제 오버라이드된 메소드
@Override
public void actionPerformed(ActionEvent e) {
try {
// JTextField 에 입력한 문자열을 읽어와서 숫자(실수)로 바꿔준다.
double num1=Double.parseDouble(tf_num1.getText());
double num2=Double.parseDouble(tf_num2.getText());
// 연산의 결과값을 담을 지역 변수
double result=0;
// 눌러진 버튼의 command 읽어오기
String command=e.getActionCommand();
if(command.equals("plus")) {
result=num1+num2;
}else if(command.equals("minus")) {
result=num1-num2;
}else if(command.equals("multi")) {
result=num1*num2;
}else if(command.equals("divide")) {
result=num1/num2;
}
// 결과 값을 JLabel 에 출력하기
label_result.setText(Double.toString(result));
}catch(Exception exe) {
JOptionPane.showMessageDialog(this, "숫자 형식으로 입력해 주세요");
}
}
}
앞선 예시들과 비슷한 전개를 통한 계산기 예시이다.
'뒷북 정리 (국비 교육) > java' 카테고리의 다른 글
[Java] step17. InputOutput (0) | 2022.04.25 |
---|---|
[Java] step16. Thread (0) | 2022.04.25 |
[Java] step14. Exception (0) | 2022.04.24 |
[Java] step13. Util Class (0) | 2022.04.22 |
[Java] step12. Generic Class (0) | 2022.04.22 |
댓글