본문 바로가기
학교생활!

[231222] 고급 자바프로그래밍 프로젝트: 캘린더

by Daybreak21 2024. 1. 1.

고급자바프로그래밍 프로젝트 과제

나는 gui를 활용한 캘린더를 만들기로 하였다. 

 

완성된 화면

 

 

※구현

  • 1. 달력화면 만들기
  • 2. 달력 넘기기 구현
  • 3. 저장, 로드 구현하기
  • 4. 배경색 바꾸는 버튼 구현

 


코드전체

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;

public class My_Calendar extends JFrame {
    private JFrame frame;
    private JPanel calendarPanel;
    private JButton saveButton;
    private JButton loadButton;

    private JButton prevButton;
    private JButton nextButton;
    private JLabel dateLabel;

    //private String folderPath = "src/calendar/";
    private String[] daysOfWeek = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    private JPanel[] datePanel = new JPanel[32];
    private JTextArea[] scheduleArea = new JTextArea[32];
    private Calendar calendar;
    private LocalDate currtentDate = LocalDate.now();

    private JButton[] btnR = new JButton[32];
    private JButton[] btnG = new JButton[32];
    private JButton[] btnB = new JButton[32];
    private JButton[] btnW = new JButton[32];
    private String[] ColorList = new String[32];
    private static Map<String, Color> ColorTable = new HashMap<>();

    public My_Calendar() {
        ColorTable.put("WHITE", Color.decode("#FFFFFF"));
        ColorTable.put("RED", Color.decode("#FCACAC"));
        ColorTable.put("GREEN", Color.decode("#98FB98"));
        ColorTable.put("BLUE", Color.decode("#87CEFA"));

        frame = new JFrame("myCalendar");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(1500, 1000);

        JPanel topPanel = new JPanel();
        JPanel infoCenterPanel = new JPanel();
        JPanel infoMenuPanel = new JPanel();


        //infoCenterPanel
        dateLabel = new JLabel();
        prevButton = new JButton("<");
        prevButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                saveButton.doClick();

                calendar.add(Calendar.MONTH, -1); //calendar날자를 이전달으로 설정
                updateCalendar(calendar.getTime());

                loadButton.doClick();
            }
        });

        nextButton = new JButton(">");
        nextButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                saveButton.doClick();

                calendar.add(Calendar.MONTH, 1); //calendar날자를 다음달 으로 설정
                updateCalendar(calendar.getTime());

                loadButton.doClick();
            }
        });

        infoCenterPanel.add(prevButton);
        infoCenterPanel.add(dateLabel);
        infoCenterPanel.add(nextButton);

        //infoMenuPanel
        saveButton = new JButton("Save");
        saveButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    File directory = new File(Paths.get("").toAbsolutePath() + "/CalendarDate"); //프로젝트가 저장된 상대경로에 CalendarDate디렉토리를 만듦
                    if (!directory.exists()) directory.mkdir();

                    File file = new File(directory, calendar.get(Calendar.YEAR) + "_" + (calendar.get(Calendar.MONTH) + 1) + ".txt");
                    BufferedWriter writer = new BufferedWriter(new FileWriter(file));

                    for (int i = 1; i <= calendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
                        writer.write(
                                i + "\n" +
                                ColorList[i] + "\n" +
                                scheduleArea[i].getText() + "\n");
                    }
                    writer.write("-1"); //마지막날자의 반복문을 끝내기 위한 넘버
                    writer.close();

                    //JOptionPane.showMessageDialog(frame, "파일이 성공적으로 저장되었습니다.", "Success", JOptionPane.INFORMATION_MESSAGE);
                } catch (IOException ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "파일열기에 실패하였습니다.\n파일명을 확인해주세요.", "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        loadButton = new JButton("load");
        loadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    File directory = new File(Paths.get("").toAbsolutePath() + "/CalendarDate");
                    File file = new File(directory, calendar.get(Calendar.YEAR) + "_" + (calendar.get(Calendar.MONTH) + 1) + ".txt");

                    if (file.exists()) { //저장한 적이 있는 달의 내용을 불러온다
                        BufferedReader reader = new BufferedReader(new FileReader(file));

                        String text = reader.readLine(); //맨 처음 적힌 숫자 1을 읽는다.
                        for (int i = 1; i<=calendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
                            text = reader.readLine(); //배경 색상 읽기
                            clickColorButton(i, text); //날자랑, 색상을 사용하여 ColorButton을 클릭한다

                            while (true) {
                                text = reader.readLine();
                                if (isNumberic(text)) break; //읽은 값이 숫자라면 다음 날자로 넘어감
                                if (!text.equals("")) scheduleArea[i].append(text+"\n"); //읽은값이 String형식 이라면 날자 칸에 데이터를 추가함
                            }
                        }
                        reader.close();
                    }

                } catch (IOException ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "파일 읽기 오류", "Error", JOptionPane.ERROR_MESSAGE);
                } catch (ArrayIndexOutOfBoundsException ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "파일 형식 오류", "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        });
        infoMenuPanel.add(saveButton);
        infoMenuPanel.add(loadButton);

        JPanel infoPanel = new JPanel(new BorderLayout());
        infoPanel.add(infoCenterPanel, BorderLayout.CENTER);
        infoPanel.add(infoMenuPanel, BorderLayout.EAST);


        JPanel weekPanel = new JPanel(new GridLayout(1, 7));
        for (String dayOfWeek : daysOfWeek) {
            weekPanel.add(new JLabel(dayOfWeek, SwingConstants.CENTER)); // 요일 레이블을 weekPanel에 추가
        }

        calendarPanel = new JPanel(new GridLayout(0, 7));

        topPanel.setLayout(new BorderLayout());
        topPanel.add(infoPanel, BorderLayout.NORTH);
        topPanel.add(weekPanel, BorderLayout.SOUTH);

        frame.add(topPanel, BorderLayout.NORTH);
        frame.add(new JScrollPane(calendarPanel), BorderLayout.CENTER);

        calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        updateCalendar(calendar.getTime());


        frame.setVisible(true);

        loadButton.doClick();
    }

    private void CalendarClear() {
        for (int i = 1; i < datePanel.length; i++) {
            if (datePanel[i] != null) {
                calendarPanel.remove(datePanel[i]);
                datePanel[i] = null;
            }
            if (scheduleArea[i] != null) {
                scheduleArea[i] = null;
            }
        }
        calendarPanel.removeAll();

        calendarPanel.revalidate();
        calendarPanel.repaint();
    }

    private void updateCalendar(Date date) {
        CalendarClear();

        currtentDate = LocalDate.now(); //
        SimpleDateFormat dateFormat;

        if (currtentDate.getMonthValue() == (date.getMonth()+1) && currtentDate.getYear() == (date.getYear()+1900)) {
            dateFormat = new SimpleDateFormat("yyyy년 MM월 dd일 E요일"); //현재 날자의 달력인 경우 (왜
        }
        else {
            dateFormat = new SimpleDateFormat("yyyy년 MM월"); //다른 달의 달력이라면 년도와 월만 라벨에 출력
        }
        dateLabel.setText(dateFormat.format(date));


        calendar.set(Calendar.DAY_OF_MONTH, 1); //일:1 월:2, 화:3, 수:4, 목:5, 금:6, 토:7
        int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); //달이 시작되는 요일을 저장
        for (int i = 1; i < firstDayOfWeek; i++) {
            calendarPanel.add(new JLabel("")); // 1일의 요일까지 빈칸을 채워넣는다
        }

        for (int i = 1; i <= calendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
            ColorList[i] = "White"; //ColorList를 흰색으로 초기화
            calendarPanel.add(createDatePanel(i)); //
        }
    }

    private JPanel createDatePanel(int date) {
        datePanel[date] = new JPanel();
        datePanel[date].setPreferredSize(new Dimension());
        JPanel dateTop = new JPanel();
        JPanel dateColor = new JPanel();

        btnR[date] = CreateColorSetButton(date, "RED");
        btnG[date] = CreateColorSetButton(date, "GREEN");
        btnB[date] = CreateColorSetButton(date, "BLUE");
        btnW[date] = CreateColorSetButton(date, "WHITE");

        dateColor.add(btnR[date]);
        dateColor.add(btnG[date]);
        dateColor.add(btnB[date]);
        dateColor.add(btnW[date]);

        dateTop.add(new JLabel(date + ""), BorderLayout.WEST);
        dateTop.add(dateColor, BorderLayout.EAST);

        datePanel[date].add(dateTop, BorderLayout.NORTH);
        scheduleArea[date] = new JTextArea(6, 21);
        datePanel[date].add(scheduleArea[date], BorderLayout.CENTER);

        return datePanel[date];
    }

    private boolean isNumberic(String s) {
        try {
            Double.parseDouble(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

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

    public JButton CreateColorSetButton(int date, String color) {
        JButton btn = new JButton();

        Dimension btnSize = new Dimension(10, 15);
        btn.setPreferredSize(btnSize);
        btn.setBackground(ColorTable.get(color));
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                scheduleArea[date].setBackground(ColorTable.get(color));
                ColorList[date] = color;
            }
        });

        return btn;
    }

    private void clickColorButton(int date, String color) {
        switch (color) {
            case "WHITE":
                btnW[date].doClick();
                break;
            case "RED":
                btnR[date].doClick();
                break;
            case "GREEN":
                btnG[date].doClick();
                break;
            case "BLUE":
                btnB[date].doClick();
                break;
        }
    }
}

 

 

**코드설명(조잡함)

더보기


<MyCalendar(메인함수)>
-infoPanel오늘 날자를 표시하는 라벨, 달력 넘기는 버튼, save, load버튼 ->topPanel
-월~일 요일 라벨을 weekPanel에 붙임-> topPanel
-calendarPanel생성 
-topPanel프레임 상단 / calendarPanel프레임 중앙
등 판넬생성, 프레임 설정


**calendar변수: 현재 달력의 날자에 맞게 Calendar날자를 업데이트 해주기 위한 변수
현재날자로 calendar를 업데이트해주고 시작한다. -> load버튼을 눌러 저장된 정보를 가져옴
calendar = Calendar.getInstance();
calendar.setTime(new Date());
updateCalendar(calendar.getTime());
frame.setVisible(true);

loadButton.doClick();


<달력화면 업데이트: updateCalendar()>
-원래 있던 달력을 지움
-상단의 오늘의 날자 업데이트(다른달의 달력이라면 그 달력에 맞는 날자) : dataLabel
currtentDate = LocalDate.now(); //
SimpleDateFormat dateFormat;

if (currtentDate.getMonthValue() == (date.getMonth()+1) && currtentDate.getYear() == (date.getYear()+1900)) {
    dateFormat = new SimpleDateFormat("yyyy년 MM월 dd일 E요일"); //현재 날자의 달력인 경우 (왜
}
else {
    dateFormat = new SimpleDateFormat("yyyy년 MM월"); //다른 달의 달력이라면 년도와 월만 라벨에 출력
}
dateLabel.setText(dateFormat.format(date));

-달력에 날자칸 집어넣기
1일이 시작되는 요일 전까지 빈 라벨을 집어넣는다. 
calendar.set(Calendar.DAY_OF_MONTH, 1); //일:1 월:2, 화:3, 수:4, 목:5, 금:6, 토:7
int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); //달이 시작되는 요일을 저장
for (int i = 1; i < firstDayOfWeek; i++) {
    calendarPanel.add(new JLabel("")); // 1일의 요일까지 빈칸을 채워넣는다
}
1일부터 현재 달의 마지막 날 까지 날자 칸(datePanel)을 집어넣는다. 
for (int i = 1; i <= calendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
    ColorList[i] = "White"; //ColorList를 흰색으로 초기화
    calendarPanel.add(createDatePanel(i)); //
}


<날자 칸 생성: createDatePanel()>
-datePanel(dateTop, 일정 입력 창TextArea), dateTop(날자, 색상 변경 버튼), 색상 변경 버튼(빨, 초, 파, 흰) 생성
Label에 매개변수로 받은 날자를 집어넣고 요소들을 추가해 datePanel반환

<날자칸의 배경색 변경: CreateColorSetButton>
버튼 생성->버튼의 크기와 배경 색 변경
버튼 클릭액션: 매개변수로 받은 날자와 색상으로 날자칸의 배경을 변경한다. 
배경색을 변경한 뒤, ColorList를 업데이트 해준다. 
* Map<String, Color> ColorTable : 색상이름으로 색상코드를 저장해두고 사용함


<Save>
-프로젝트가 저장된 상대경로에 CalendarDate디렉토리를 만든다. 
File directory = new File(Paths.get("").toAbsolutePath() + "/CalendarDate"); 
if (!directory.exists()) directory.mkdir();
-저장하기 전, 경로에 현재 날자의 데이터가 있는지 확인 하고, 없다면 새로운 파일을 만든다. 
-파일명은 현재 달력(calendar)의 정보를 사용한다. 
-파일에 날자, 칸의 배경 색, 날자 칸(ScheduleArea)의 일정데이터를 차례대로 한줄 씩 쓴다. 
1
WHITE
일정1
일정2


<Load>
-현재 달력의 정보(calendar)를 사용하여 파일을 찾고 불러온다
-1부터 현재달력의 마지막 날까지 쓰기반복
String text = reader.readLine(); //맨 처음 적힌 숫자 1을 읽는다.
for (int i = 1; i<=calendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
    text = reader.readLine(); //배경 색상 읽기
    clickColorButton(i, text); //날자랑, 색상을 사용하여 ColorButton을 클릭한다

    while (true) {
        text = reader.readLine();
        if (isNumberic(text)) break; //읽은 값이 숫자라면 다음 날자로 넘어감
        if (!text.equals("")) scheduleArea[i].append(text+"\n"); //읽은값이 String형식 이라면 날자 칸에 데이터를 추가함
    }
}
-ArrayIndexOutOfBoundsException 오류가나는 경우(scheduleArea[i])에 쓰지 못하기 때문에 발생)-> 파일 형식 오류
} catch (IOException ex) {
    ex.printStackTrace();
    JOptionPane.showMessageDialog(frame, "파일 읽기 오류", "Error", JOptionPane.ERROR_MESSAGE);
} catch (ArrayIndexOutOfBoundsException ex) {
    ex.printStackTrace();
    JOptionPane.showMessageDialog(frame, "파일 형식 오류", "Error", JOptionPane.ERROR_MESSAGE);
}


<다음 달(next), 이전 달(prev) 버튼>
-달력을 넘기기 전에 현재 달력의 정보 저장 saveButton.doClick();
calendar.add(Calendar.MONTH, 1); 
updateCalendar(calendar.getTime());
-> calendar변수를 다음달로 바꿈 (이전달: -1) 
->바꾼 calendar값으로 캘린더 업데이트
-달력을 넘긴 후 laod버튼으로 넘긴 달의 정보 불러오기 loadButton.doClick();

 

 

개선할점

-일정 체크박스(-> textArea의 기능으로는 하루 일정이 다 끝나면 그 날 전체에 취소선을 긋는 방법을 사용할 수 있지않을까)

-맨 위 현재날자를 알려주는 dateLabel이 다른달로 넘어 갔다가 현재 달로 넘어오면 날자가 1일으로 고정됨 (대체왜??ㅠ)

-달력 넘길때 딜레이