(c)도경구 version 1.0 (2022/11/2)

9. GUI와 이벤트 구동 프로그래밍

9-1. 사례 학습 : 카운터

코딩 따라하기

1단계 : 초기 버전

public class Counter {
    private int count;

    /** Counter - 카운터 초기 설정
     * @param start - 카운터의 초기값 */
    public Counter(int start) {
        count = start;
    }

    /** increment - 카운터 값 증가 */
    public void increment() {
        count = count + 1;
    }

    /** count - 카운터 값 리턴
     * @return 카운터 현재 값  */
    public int count() {
        return count;
    }

}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CounterFrame extends JFrame implements ActionListener {
    private Counter counter;
    private JLabel label = new JLabel("count = 0");

    public CounterFrame(Counter c) {
        counter = c;
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(label);
        JButton button = new JButton("OK");
        cp.add(button);
        button.addActionListener(this); // 자신을 버튼에 연결 (버튼 이벤트가 발생하면 처리 가능하도록)
        setTitle("Counter");
        setSize(200,70);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    /** actionPerformed - '버튼 누르기' 액션 이벤트를 처리 */
    public void actionPerformed(ActionEvent e) {
        counter.increment();
        label.setText("count = " + counter.count());
    }

}
public class CounterStarter {
    public static void main(String[] args) {
        Counter model = new Counter(0);
        new CounterFrame(model);
    }

}

2단계 : 컨트롤러와 뷰를 분리

import java.awt.event.*;

public class CounterController implements ActionListener {
    private CounterFrame view;
    private Counter model;

    public CounterController(Counter m, CounterFrame v) {
        view = v;
        model = m;
    }

    /** actionPerformed - '버튼 누르기' 액션 이벤트를 처리 */
    public void actionPerformed(ActionEvent e) {
        model.increment();
        view.update();
    }

}
import java.awt.*;
import javax.swing.*;

public class CounterFrame extends JFrame {
    private Counter counter;
    private JLabel label = new JLabel("count = 0");

    public CounterFrame(Counter c) {
        counter = c;
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(label);
        JButton button = new JButton("OK");
        cp.add(button);
        button.addActionListener(new CounterController(counter, this)); // 자신을 버튼에 연결 (버튼 이벤트가 발생하면 처리 가능하도록)
        setTitle("Counter");
        setSize(200,70);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    /** update - 뷰 갱신 */
    public void update() {
        label.setText("count = " + counter.count());
    }

}

3단계 : 버튼 전용 컨트롤러

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CountButton extends JButton implements ActionListener {
    private CounterFrame view;
    private Counter model;

    /** CountButton - 버튼 컨트롤러
     * @param label - 버튼에 붙는 라벨
     * @param m - 협조할 모델
     * @param v - 갱신할 뷰 */
    public CountButton(String label, Counter m, CounterFrame v) {
        super(label);
        view = v;
        model = m;
        addActionListener(this);
    }

    /** actionPerformed - '버튼 누르기' 액션 이벤트를 처리
     * @param e - 이벤트 */
    public void actionPerformed(ActionEvent e) {
        model.increment();
        view.update();
    }

}
import java.awt.*;
import javax.swing.*;

public class CounterFrame extends JFrame {
    private Counter counter;
    private JLabel label = new JLabel("count = 0");

    public CounterFrame(Counter c) {
        counter = c;
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(label);
        cp.add(new CountButton("OK", counter, this));
        setTitle("Counter");
        setSize(200,70);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    /** update - 뷰 갱신 */
    public void update() {
        label.setText("count = " + counter.count());
    }

}

4단계 : 그래픽 카운터, 구역 정리, 버튼 추가

import java.awt.event.*;
import javax.swing.*;

public class ExitButton extends JButton implements ActionListener {

    /** ExitButton - 종료 컨트롤러
     * @param label - 버튼에 붙는 라벨 */
    public ExitButton(String label) {
        super(label);
        addActionListener(this);
    }

    /** actionPerformed - '버튼 누르기' 액션 이벤트를 처리
     * @param e - 이벤트  */
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }

}
import java.awt.*;
import javax.swing.*;

public class Drawing extends JPanel {

    private Counter counter;

    public Drawing(Counter c) {
        counter = c;
        setSize(200, 200);
    }

    public void paintComponent(Graphics g) {
        g.setColor(Color.white);
        g.fillRect(0, 0, 200, 200);
        g.setColor(Color.red);
        int x = 0, y = 0;
        for (int i = 0; i != counter.count(); i++) {
            g.fillOval(x*25, y*25, 20, 20);
            x++;
            if (x > 7) {
                x = 0;
                y++;
            }
        }
    }
}
import java.awt.*;
import javax.swing.*;

public class CounterFrame extends JFrame {
    private Counter counter;
    private JLabel label = new JLabel("count = 0");
    private Drawing panel;

    public CounterFrame(Counter c, Drawing p) {
        counter = c;
        panel = p;
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        JPanel p1 = new JPanel(new FlowLayout());
        p1.add(label);
        JPanel p2 = new JPanel(new FlowLayout());
        p2.add(new CountButton("OK", counter, this));
        p2.add(new ExitButton("Exit"));
        cp.add(p1, BorderLayout.NORTH);
        cp.add(panel, BorderLayout.CENTER);
        cp.add(p2,BorderLayout.SOUTH);
        setTitle("Counter");
        setSize(200,280);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    /** update - 뷰 갱신 */
    public void update() {
        label.setText("count = " + counter.count());
        panel.repaint();
    }

}
public class CounterStarter {

    public static void main(String[] args) {
        Counter model = new Counter(0);
        Drawing panel = new Drawing(model);
        new CounterFrame(model,panel);
    }

}

9-2. 사례 학습 : 슬라이드 퍼즐 (GUI 버전 1.0)

코딩 따라하기

/** PuzzlePiece - 슬라이드 퍼즐 게임 조각  */
public class PuzzlePiece { 
    private int face;
    
    /** Constructor - PuzzlePiece 퍼즐 조각을 만듬 
     * @param value - 퍼즐 조각 위에 표시되는 값  */
    public PuzzlePiece(int value) { 
        face = value; 
    }
    
    /** face - 조각의 액면 값을 리턴  */ 
    public int face() { 
        return face; 
    }
}
public class SlidePuzzleBoard { 
    
    private PuzzlePiece[][] board;
    // 빈칸의 좌표 
    private int empty_row;
    private int empty_col;
    // representation invariant: board[empty_row][empty_col] == null
    
    /** Constructor - SlidePuzzleBoard 초기 퍼즐 보드 설정 - 감소하는 순으로 나열 
     *  */
    public SlidePuzzleBoard() {
        // 4 x 4 보드 만들기
        board = new PuzzlePiece[4][4];
        // 퍼즐 조각 1~15를 보드에 역순으로 끼우기 
        int number = 15;
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                board[row][col] = new PuzzlePiece(number);
                number -= 1;
            }
        board[3][3] = null;
        empty_row = 3;
        empty_col = 3;
    }

    /** getPuzzlePiece - 퍼즐 조각을 리턴 
     * @param row - 가로줄 인덱스 
     * @param col - 세로줄 인덱스 
     * @return 퍼즐 조각  */
    public PuzzlePiece getPuzzlePiece(int row, int col) { 
        return board[row][col];
    }
    
    /** 이동이 가능하면, 퍼즐 조각을 빈칸으로 이동 
     * @param w - 이동하기 원하는 퍼즐 조각의 번호 
     * @return 이동 성공하면 true를 리턴하고, 이동이 불가능하면 false를 리턴 */
    public boolean move(int w) { 
        int row, col; // w의 위치 
        // 빈칸에 주변에서 w의 위치를 찾음 
        if (found(w, empty_row - 1, empty_col)) {
            row = empty_row - 1;
            col = empty_col;
        }
        else if (found(w, empty_row + 1, empty_col)) {
            row = empty_row + 1;
            col = empty_col;
        }
        else if (found(w, empty_row, empty_col - 1)) {
            row = empty_row;
            col = empty_col - 1;
        }
        else if (found(w, empty_row, empty_col + 1)) {
            row = empty_row;
            col = empty_col + 1;
        }
        else
            return false;
        // w를 빈칸에 복사
        board[empty_row][empty_col] = board[row][col];
        // 빈칸 위치를 새로 설정하고, w를 제거
        empty_row = row;
        empty_col = col;
        board[empty_row][empty_col] = null;
        return true;
    }
    
    /** found - board[row][col]에 퍼즐 조각 v가 있는지 확인  */ 
    private boolean found(int v, int row, int col) { 
        if (row >= 0 && row <= 3 && col >= 0 && col <= 3)
            return board[row][col].face() == v; 
        else
            return false;
    }
}
import java.awt.event.*;
import javax.swing.*;

public class PuzzleButton extends JButton implements ActionListener {

    private SlidePuzzleBoard board;
    private PuzzleFrame frame;
    
    public PuzzleButton(SlidePuzzleBoard b, PuzzleFrame f) {
        board = b;
        frame = f;
        addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent e) {
        String s = getText();
        if (! s.equals("") && board.move(Integer.parseInt(s)))
            frame.update();
    }
    
}
import java.awt.*;
import javax.swing.*;

public class PuzzleFrame extends JFrame {
    
    private SlidePuzzleBoard board;
    private PuzzleButton[][] button_board;
    
    public PuzzleFrame(SlidePuzzleBoard b) {
        board = b;
        button_board = new PuzzleButton[4][4];
        Container cp = getContentPane();
        cp.setLayout(new GridLayout(4,4));
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                button_board[row][col] = new PuzzleButton(board,this);
                cp.add(button_board[row][col]);
            }
        update();
        setTitle("Slide Puzzle");
        setSize(250,250);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }
    
    public void update() {
        PuzzlePiece pp;
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                pp = board.getPuzzlePiece(row, col);
                if (pp != null)
                    button_board[row][col].setText(Integer.toString(pp.face()));
                else
                    button_board[row][col].setText("");
            }
    }

}
public class PuzzleStarter {
    
    public static void main(String[] args) {
        new PuzzleFrame(new SlidePuzzleBoard());
    }
}

9-3. 사례 학습 : 슬라이드 퍼즐 (GUI 버전 2.0)

완성 코드

public class PuzzlePiece {
    private int face;

    /** Constructor - PuzzlePiece 퍼즐 조각을 만듬
     * @param value - 퍼즐 조각 위에 표시되는 값  */
    public PuzzlePiece(int value) {
        face = value;
    }

    /** face - 조각의 액면 값을 리턴  */
    public int face() {
        return face;
    }
}

import java.util.*;

public class SlidePuzzleBoard {

    private PuzzlePiece[][] board;
    // 빈칸의 좌표
    private int empty_row;
    private int empty_col;
    private boolean on = false;
    // representation invariant: board[empty_row][empty_col] == null

    /** Constructor - SlidePuzzleBoard 초기 퍼즐 보드 설정 - 감소하는 순으로 나열
     *  */
    public SlidePuzzleBoard() {
        // 4 x 4 보드 만들기
        board = new PuzzlePiece[4][4];
        // 퍼즐 조각 1~15를 보드에 역순으로 끼우기
        int number = 1;
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                board[row][col] = new PuzzlePiece(number);
                number += 1;
            }
        board[3][3] = null;
        empty_row = 3;
        empty_col = 3;
    }

    /** getPuzzlePiece - 퍼즐 조각을 리턴
     * @param row - 가로줄 인덱스
     * @param col - 세로줄 인덱스
     * @return 퍼즐 조각  */
    public PuzzlePiece getPuzzlePiece(int row, int col) {
        return board[row][col];
    }

    /** 이동이 가능하면, 퍼즐 조각을 빈칸으로 이동
     * @param w - 이동하기 원하는 퍼즐 조각의 번호
     * @return 이동 성공하면 true를 리턴하고, 이동이 불가능하면 false를 리턴 */
    public boolean move(int w) {
        int row, col; // w의 위치
        // 빈칸에 주변에서 w의 위치를 찾음
        if (found(w, empty_row - 1, empty_col)) {
            row = empty_row - 1;
            col = empty_col;
        }
        else if (found(w, empty_row + 1, empty_col)) {
            row = empty_row + 1;
            col = empty_col;
        }
        else if (found(w, empty_row, empty_col - 1)) {
            row = empty_row;
            col = empty_col - 1;
        }
        else if (found(w, empty_row, empty_col + 1)) {
            row = empty_row;
            col = empty_col + 1;
        }
        else
            return false;
        // w를 빈칸에 복사
        board[empty_row][empty_col] = board[row][col];
        // 빈칸 위치를 새로 설정하고, w를 제거
        empty_row = row;
        empty_col = col;
        board[empty_row][empty_col] = null;
        return true;
    }

    /** found - board[row][col]에 퍼즐 조각 v가 있는지 확인  */
    private boolean found(int v, int row, int col) {
        if (row >= 0 && row <= 3 && col >= 0 && col <= 3)
            return board[row][col].face() == v;
        else
            return false;
    }
    
    /** createPuzzleBoard - 퍼즐 게임 초기 보드 생성 */
    public void createPuzzleBoard() {
        int[] numbers = generateRandomPermutation(15);
        int i = 0;
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                if (row != 3 || col != 3) {
                    board[row][col] = new PuzzlePiece(numbers[i]+1);
                    i += 1;
                }
                else {
                    board[row][col] = null;
                    empty_row = 3;
                    empty_col = 3;
                }
                    
            }
        on = true;
    }
    
    public boolean on() {
        return on;
    }
    
    public boolean gameOver() {
        if (empty_row != 3 || empty_col != 3) 
            return false;
        else {
            int number = 1;
            for (int row = 0; row < 4; row++)
                for (int col = 0; col < 4; col++) {
                    if (row != 3 || col != 3) {
                        if (board[row][col].face() != number)
                            return false;
                        else
                            number += 1;
                    }
                }
            on = false;
            return true;
        }
    }

    /** generateRandomPermutation - 0~n-1 범위의 정수 수열을 무작위로 섞은 배열을 리턴 한다.
     * @param n - 수열의 길이
     * @return 0~n-1 범위의 정수를 무작위로 섞어 만든 배열
     */
    private int[] generateRandomPermutation(int n) {
        Random random = new Random();
        int[] permutation = new int[n];
        for (int i = 0; i < n; i++) {
            int d = random.nextInt(i+1);
            permutation[i] = permutation[d];
            permutation[d] = i;
        }
        return permutation;
    }
}
import java.awt.event.*;
import javax.swing.*;

public class PuzzleButton extends JButton implements ActionListener {
    
    private SlidePuzzleBoard board;
    private PuzzleFrame frame;
    
    public PuzzleButton(SlidePuzzleBoard b, PuzzleFrame f) {
        board = b;
        frame = f;
        addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent e) {
        if (board.on()) {
            String s = getText();
            if (! s.equals("") && board.move(Integer.parseInt(s))) {
                frame.update();
                if (board.gameOver())
                    frame.finish();
            }
        }
        
        
    }
}
import java.awt.event.*;
import javax.swing.*;

public class StartButton extends JButton implements ActionListener {
    
    private SlidePuzzleBoard board;
    private PuzzleFrame frame;
    
    public StartButton(SlidePuzzleBoard b, PuzzleFrame f) {
        super("Start");
        board = b;
        frame = f;
        addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent e) {
        board.createPuzzleBoard();
        frame.update();
    }
}
import java.awt.*;
import javax.swing.*;

public class PuzzleFrame extends JFrame {
    
    private SlidePuzzleBoard board;
    private PuzzleButton[][] button_board;
    
    public PuzzleFrame(SlidePuzzleBoard b) {
        board = b;
        button_board = new PuzzleButton[4][4];
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        JPanel p1 = new JPanel(new FlowLayout());
        p1.add(new StartButton(board,this));
        JPanel p2 = new JPanel(new GridLayout(4,4));
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                button_board[row][col] = new PuzzleButton(board,this);
                p2.add(button_board[row][col]);
            }
        cp.add(p1,BorderLayout.NORTH);
        cp.add(p2,BorderLayout.CENTER);
        update();
        setTitle("Slide Puzzle");
        setSize(250,300);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }
    
    public void update() {
        for (int row = 0; row < 4; row++)
            for (int col = 0; col < 4; col++) {
                PuzzlePiece pp = board.getPuzzlePiece(row, col);
                if (pp != null) {
                    String n = Integer.toString(pp.face());
                    button_board[row][col].setText(n);
                }
                else
                    button_board[row][col].setText("");
            }
    }
    
    public void finish() {
        button_board[3][3].setText("Done");
    }

}
public class PuzzleStarter {

    public static void main(String[] args) {
        new PuzzleFrame(new SlidePuzzleBoard());
    }

}