Java 자바 기초

[Java] 그림판 코드 수정해서 개선해보기

vBest 2021. 11. 13. 15:18

그림판 프로그램을 찾아 원하는 방향으로 수정해보는 프로젝트를 진행했다.

이벤트리스너를 구현해보는 것이 프로젝트의 목표였다.

 

개발환경 : eclipse

개발언어 : java

 

기존 프로그램의 출처

https://m.blog.naver.com/PostView.nhn?blogId=hotkimchi13&logNo=221183898165&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

기존 프로그램의 클래스 구조 및 관계

기존 프로그램 

사용된 라이브러리 종류 및 의미

BasicStroke는 직선을 그리고 굵기를 조절한다.

Color는 연필의 색상을 정하거나 GUI패널의 색을 정한다.

Fonts는 버튼의 글씨 폰트를 정한다.

GraphicsGraphics2D에 초기화 시켜주기위해 선언된다.

Graphics2D는 펜의 굵기관련 기능 수행을 위해 선언되는 객체이다.

ActionEvent는 버튼 클릭등의 액션 이벤트를 의미한다.

ActionListener는 액션 이벤트를 처리하는 리스너이다. ToolActionListener 내부 클래스가 구현했다.

MouseEvent는 마우스 드래그나 클릭 이벤트를 의미한다.

MouseListener 마우스 이벤트를 처리하는 리스너이다. paintPanel에서 마우스 위치 값을 받아오는 동작을 위해 무명클래스로 구현해 사용되었다.

MouseMotionListener는 마우스 드래그 이벤트를 처리하는 리스너이다. PaintDraw 내부 클래스가 구현했다.

JButton 버튼을 만들 때 사용되었다.

JColorChooser 컬러를 고르는 버튼을 구현할 때 사용되었다.

JFrame을 상속해 Paint라는 클래스를 만들었다.

JLabel 도구 굵기라는 라벨을 만들기 위해서 사용되었다.

JPanel 패널을 만들기 위해 사용되었다.

JTextField 도구굵기를 정할 때 사용되었다.

 

프로그램의 흐름 및 동작 과정 

연필 버튼, 지우개 버튼, 선색상 고르는 버튼,

연필 굵기 조정하는 텍스트 필드 기능을 위해 기존의 graphics변수 gGraphics2D로 변환후 Graphics2D에 초기화했고 일반적인 Graphics가 아닌 Graphics2D를 사용한 이유는 펜의 굴기와 관련된 기능을 수행하기 위하여 Graphics2D 클래스를 객체화했다.

Paint_panel에서 마우스리스너의 이벤트 처리를 위해 무명클래스로 리스너를 구현해 추가해 주었다. 마우스리스너에서는 Paint_panel에 마우스 눌림이 있을 때 그때의 x,y좌표값으로 시작 변수들을 초기화했다. Paint_panel에는 PaintDraw라는 마우스 모션 리스너를 구현한 내부 클래스(리스너 구현)의 객체를 추가했다. 이때 동작은 마우스 드래그 액션이 처리 될 때, 굵기를 정하는 텍스트 필드의 값을 가지고와서 thickness변수에 대입한뒤 드래그 되는 시점에서 endx,y좌표가 저장되고 나중에 시작좌표와 끝좌표를 연결해주면 선이 그려진다. Graphics2DsetStroke메소드를 이용해 선 굵기를 정해주고 drawLine메소드를 이용해 라인을 그려 준다. 마지막으로 시작 부분에 마지막으로 드래그 된 x,y좌표로 찍혀야 다음에 선이 이어져서 그려질 수 있기 때문에 startx,yendx,y값을 줘야한다.

연필 버튼과 지우개 버튼에도 액션리스너를 구현한 ToolActonListener를 등록해준다. 펜슬 버튼이 눌렸을 때, tffalse라면 색상이 버튼 색을 그려지는색을 검정으로 지정한다. true라면 선택된 컬러로 변경한다. 지우개 버튼이 눌렸을 때 그려지는 색을 햐양색으로 정해서 펜이 지워지는 것처럼 보이게 한다. 선 색상 버튼 처리를 익명 클래스로 구현했다. 눌리면 tftrue로 바꾸고 JColorChooser를 객체화해서 선택된 색으로 초기화한다. 그래픽스에 선택된 색상으로 색상을 지정한다.

 

기존 프로그램 수정 및 개선 방안

- 수정

도구 굵기 정하는 메뉴를 만들고 얇은 굵기,보통 굵기, 두꺼운 굵기 중 택 할 수 있도록 수정할 것이다.

이미지가 들어간 버튼으로 수정해봤는데 정돈되어 보이지 않아서

일반적인 그림판 기능처럼 파일(저장,열기 기능 넣을 예정), 그리기 옵션(도형, , 색상 등..)이 있는 메뉴바를 만들 것이다.

타이틀 바의 아이콘을 그림판에 맞는 아이콘으로 바꿀 것이다.

 

- 개선

도형 선택 후 그려지는 (드래그에 따라 크기가 커지거나 작아지는 모습)이 보이도록 개선

그림판의 화면을 저장하는 기능을 추가할 것이다.

그림판의 저장된 파일을 불러와서 열어주는 기능을 추가할 것이다.

그림판에 사각형을 그릴 수 있는 기능을 추가할 것이다.

그림판에 원을 그릴 수 있는 기능을 추가할 것이다.

그림판에 직선을 그릴 수 있는 기능을 추가할 것이다.

그림판에 둥근 사각형을 그릴 수 있는 기능을 추가할 것이다.

그림판에 이미지를 불러오거나 캡쳐한 이미지 위에 그림을 그릴 수 있는 기능을 추가할 것이다.

한 번에 그린 것을 지워주는 버튼을 만들 것이다.

메뉴바, 메뉴, 체크박스메뉴아이템을 만들어서 선택된 항목에 따라서 실행 동작이 달라지도록 구현할 것이다.

 

소스 코드

import java.awt.BasicStroke;
import java.awt.CheckboxMenuItem;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/* * 도형을 관리하는 클래스 * 시작점과 끝점 그리고 도형의 모양, 색상, 채우기 속성 등이 관리되는 클래스 */
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JColorChooser;
import javax.swing.JPanel; 
class DrawInfo //implements Serializable
{ //직렬화 클래스 구현 --> DrawInfo imsi = (DrawInfo) vc.elementAt(i); //하향 형변환 해서 멤버에 접근해 정보를 사용할 것이다.
	private int x; // 눌린 첫 좌표
	private int y; 
	private int x1; //놓인 나중 좌표
	private int y1; 
	private int type;//몇 번째 메뉴항목 인지 
	private Color color; //무슨 색 인지
	private boolean fill;// 색 채우기 여부
	//////////////////////////////////////
	private boolean thick1; //선1
	private boolean thick2; //선2
	private boolean thick3; //선3
	
	private boolean eraseThick1; //지우개 1
	private boolean eraseThick2; //지우개 2
	private boolean eraseThick3; //지우개 3
	
	public DrawInfo(int x, int y, int x1, int y1, int type, Color color,
			boolean fill, boolean thick1, boolean thick2, boolean thick3 
			,boolean eraseThick1, boolean eraseThick2, boolean eraseThick3 ){
	this.x = x; this.y = y; this.x1 = x1; this.y1 = y1;
	this.type = type; 
	this.color = color;
	this.fill = fill;
	this.thick1 = thick1;
	this.thick2 = thick2;
	this.thick3 = thick3;
	this.eraseThick1 = eraseThick1;
	this.eraseThick2 = eraseThick2;
	this.eraseThick3 = eraseThick3;
}
	
	//설정자, 접근자
	public void setX(int x){ this.x = x; }  
	public void setY(int y){ this.y = y; } 
	public void setX1(int x1){ this.x1 = x1;	} 
	public void setY1(int y1){ this.y1 = y1; }  
	public void setType(int type){ this.type = type; } 
	public void setColor(Color color){ this.color = color; } 
	public void setFill(boolean fill){ this.fill = fill; } 
	public int getX(){ return x; } 
	public int getY(){ return y; }  
	public int getX1(){ return x1; }  
	public int getY1(){ return y1; }  
	public int getType(){ return type; } 
	public Color getColor(){ return color; } 
	public boolean getFill(){ return fill; }
	/////////////////////////////////////////////
	public boolean getThick1(){ return thick1; }
	public boolean getThick2(){ return thick2; }
	public boolean getThick3(){ return thick3; }
	public boolean getEraseThick1(){ return eraseThick1; }
	public boolean getEraseThick2(){ return eraseThick2; }
	public boolean getEraseThick3(){ return eraseThick3; }
	
} 
///////////////////////////////////////////////////////////////////////////////////////////////
//GUI MENU BAR 구현은 frame에서만 가능!
//따라서 패널에 그리고 repaint하는 방식으로는 메뉴바가 있는 그림판 구현 불가 
class DrawFrame2 extends Frame implements MouseListener ,
MouseMotionListener ,ItemListener ,ActionListener{ 
	
	private MenuBar mb = new MenuBar();//메뉴바 생성
	private Menu file = new Menu("파일"); //상위 메뉴의 이름
	private MenuItem fnew = new MenuItem("모두 지우기"); //파일 의 하위 메뉴 이름 //모두 지우기 new
	private MenuItem fopen = new MenuItem("열기"); 
	private MenuItem fsave = new MenuItem("저장"); 
	private MenuItem fexit = new MenuItem("종료"); 
	private Menu option = new Menu("옵션"); //상위 메뉴의 이름  
	private Menu odraw = new Menu("그리기"); 
	private CheckboxMenuItem odpen = new CheckboxMenuItem("펜", true);  
	private CheckboxMenuItem odline = new CheckboxMenuItem("직선"); 
	private CheckboxMenuItem odrect = new CheckboxMenuItem("사각형");
	private CheckboxMenuItem odcircle = new CheckboxMenuItem("원");
	private CheckboxMenuItem odround = new CheckboxMenuItem("둥근 사각형");////
	private Menu oerase = new Menu("지우기"); 
	private CheckboxMenuItem oerasethick1 = new CheckboxMenuItem("작은 지우개");  /////
	private CheckboxMenuItem oerasethick2 = new CheckboxMenuItem("기본 지우개"); 
	private CheckboxMenuItem oerasethick3 = new CheckboxMenuItem("큰 지우개");
	private CheckboxMenuItem oeraseStop = new CheckboxMenuItem("지우기 중지");//나머지 박스아이템의 값이 참인 경우 펜과 마우스드래그 기능이 겹치므로.. 
	private Menu ocolor = new Menu("색상");
	private MenuItem ochoose = new MenuItem("색 고르기"); 
	private Menu oprop = new Menu("색 채우기"); 
	private CheckboxMenuItem opdraw = new CheckboxMenuItem("선 채우기", true);
	private CheckboxMenuItem opfill = new CheckboxMenuItem("면 채우기");
	private Menu othick = new Menu("펜,선 두께");//2D Graphics ///////////////선두께 기능
	private CheckboxMenuItem opthick1 = new CheckboxMenuItem("얇은 선");
	private CheckboxMenuItem opthick2 = new CheckboxMenuItem("기본 선", true);
	private CheckboxMenuItem opthick3 = new CheckboxMenuItem("두꺼운 선");
	private int x; private int y; private int x1; private int y1;
	private int dist; //type에 대응함, 이후 메뉴바 순서
	private boolean fill;
	///////////////////////
	private boolean thick1;
	private boolean thick2;
	private boolean thick3;
	
	private boolean eraseThick1;
	private boolean eraseThick2;
	private boolean eraseThick3;
	
	private int thickness;// 현 변수는 그려지는 선의 굴기를 변경할때 변경값이 저장되는 변수
	
	private int roundWidth = 30;
	private int roundHeight = 30;
	
	Color selectedColor; //
	//Vector는 서로 다른 타입을 가지는 참조형 데이터를 저장하는 가변 길이의 배열이다. 
	//Vector에는 기본형 데이터를 저장할 수 없다.-->따라서 벡터객체를 하향형변환 해서 쓸 클래스를 만든다. 
	// 도형을 저장하기 위한 객체 
	private Vector vc = new Vector(); //10개의 데이터를 저장할 수 있는 길이의 객체를 생성한다. 저장 공간이 부족한 경우 10개씩 증가한다. 
     
	public DrawFrame2(){ //Frame 생성자 -->GUI,LIStener 구현한 것 다 넣어서 객체 생성시 실행
    	 super("그림판");
    	 ImageIcon icon = new ImageIcon("images/drawIcon.png");
    	 setIconImage(icon.getImage());
    	 this.init(); //GUI 구현부를 따로 클래스 내부의 멤버 함수로 만듬
    	 this.start(); //listener 구현부를 따로 클래스 내부의 멤버 함수로 만듬 
    	//-->DrawFrame 클래스가 인터페이스를 구현해서 가능,이 방식이 멤버들에 접근하기 쉽기때문
    	 this.setSize(500,500);
    	 setLocationRelativeTo(null); // 프로그램 실행시 화면 중앙에 출력
    	 this.setVisible(true); 
    }//
	
    public void init(){ //GUI인 메뉴바 만들기
    	 this.setMenuBar(mb); 
    	 mb.add(file); //파일
    	 file.add(fnew); 
    	 file.addSeparator(); 
    	 file.add(fopen);
    	 file.add(fsave); 
    	 file.addSeparator();
    	 file.add(fexit); 
    	 mb.add(option); //옵션
    	 option.add(odraw);//
    	 odraw.add(odpen); 
    	 odraw.add(odline); 
    	 odraw.add(odrect); 
    	 odraw.add(odcircle);
    	 odraw.add(odround);///
    	 option.addSeparator();	//아래 선 두께
    	 option.add(othick);
    	 othick.add(opthick1);
    	 othick.add(opthick2);
    	 othick.add(opthick3);
    	 option.addSeparator();//지우개 
    	 option.add(oerase);
    	 oerase.add(oerasethick1);
    	 oerase.add(oerasethick2);
    	 oerase.add(oerasethick3);
    	 oerase.add(oeraseStop);
    	 option.addSeparator();//
    	 option.add(ocolor);
    	 ocolor.add(ochoose);//
    	 option.addSeparator();//	
    	 option.add(oprop); 
    	 oprop.add(opdraw); 
    	 oprop.add(opfill); 
//    	 option.addSeparator();	//아래 선 두께
//    	 option.add(othick);
//    	 othick.add(opthick1);
//    	 othick.add(opthick2);
//    	 othick.add(opthick3);
    }//
    
    public void start(){ 
    	 this.addMouseListener(this);
    	 this.addMouseMotionListener(this); 
    	 odpen.addItemListener(this);
    	 odline.addItemListener(this);
    	 odrect.addItemListener(this); 
    	 odcircle.addItemListener(this);
    	 odround.addItemListener(this);/////////
    	 opthick1.addItemListener(this);/////////선두께
    	 opthick2.addItemListener(this);
    	 opthick3.addItemListener(this);
    	 oerasethick1.addItemListener(this); ///지우개 두께
    	 oerasethick2.addItemListener(this);
    	 oerasethick3.addItemListener(this);
    	 oeraseStop.addItemListener(this);
    	 ochoose.addActionListener(this);//
    	 fnew.addActionListener(this); 
    	 fopen.addActionListener(this);
    	 fsave.addActionListener(this); 
    	 fexit.addActionListener(this); 
    	 opdraw.addItemListener(this); 
    	 opfill.addItemListener(this); 
    	 //창닫기
    	 this.addWindowListener(new WindowAdapter(){ 
    		 public void windowClosing(WindowEvent e){ System.exit(0); } 
    		 });	
    }//
     
    //Paint()
    public void paint(Graphics g){ //graphics 객체를 제공하는 JFrame의 paint 메소드를 오버라이딩해서 선택된 메뉴에 맞게 사용한다.
    	 //super.paint(g);//
    	 //현재 마우스가 드래그된 지점까지의 그림을 표현 
    	 Color selectedColor = new Color(0,0,0); //디폴트 검정
    	 g.setColor(selectedColor); 
    	if (dist == 1 || dist == 0){
       		if(thick1) {
       			thickness = 3;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}else if(thick2) { 
       			thickness = 10;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}else if(thick3) { 
       			thickness = 20;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}			
    	}
    	else if( dist == 4 || dist == 5 || dist == 6) {
    	
			if(eraseThick1) {
       			thickness = 3;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}else if(eraseThick2) { 
       			thickness = 10;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}else if(eraseThick3) { 
       			thickness = 20;
       			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0)); //선굵기 사용하려면 Graphics2D로 하향형 변환
       			g.drawLine(x, y, x1, y1);
       		}
    	}
    	else if(dist == 2){ 
    		 if(fill){ 
    			 g.fillRect(x, y, x1 - x, y1 - y); 
    		}
    		else { 
    			 g.drawRect(x, y, x1 - x, y1 - y); 
    		} 
    	}
    	else if(dist == 3){
    		 if(fill){ 
    			 g.fillOval(x, y, x1 - x, y1 - y); 
    		 } 
    		 else{ 
    			 g.drawOval(x, y, x1 - x, y1 - y); 
    		 } 
    	}
    	else if(dist == 7){
   		 	if(fill){ 
   		 		g.fillRoundRect(x, y, x1 - x, y1 - y,roundWidth,roundHeight); 
   		 	} 
   		 	else{ 
   		 		g.drawRoundRect(x, y, x1 - x, y1 - y,roundWidth,roundHeight);
   		 	} 
    	}
    	
    	// Vector에 저장된 내용을 전부 표현 
    	for(int i = 0; i < vc.size(); i++){ //벡터에 저장된 인덱스 사이즈 만큼 반복
    		
    		DrawInfo imsi = (DrawInfo) vc.elementAt(i); //도형 정보를 저장하는 임시(imsi) 벡터 객체의 index 위치의 객체를 반환
    		//그 객체를 하향 형변환
    	 	g.setColor(imsi.getColor());  //벡터 객체 색 
    	 	if(imsi.getType() == 1 || imsi.getType() == 0 ){ //타입은  dist 에 대응 //또는 으로 지우개 버튼 눌리는 경우만 만들기
    	 		
    	 		if(imsi.getThick1()) {
    	 			thickness = 3;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}else if(imsi.getThick2()) {
    	 			thickness = 10;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}else if(imsi.getThick3()) {
    	 			thickness = 20;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}
    	 	//	g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1()); 
    	 	}
    	 	else if(imsi.getType() == 4 || imsi.getType() == 5 || imsi.getType() == 6 ){ //타입은  dist 에 대응 //또는 으로 지우개 버튼 눌리는 경우만 만들기
    	 		
    	 		if(imsi.getEraseThick1()) {
    	 			thickness = 3;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}else if(imsi.getEraseThick2()) {
    	 			thickness = 10;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}else if(imsi.getEraseThick3()) {
    	 			thickness = 20;
           			((Graphics2D) g).setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND,0));
    	 			g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1());
    	 		}
    	 		
    	 	//	g.drawLine(imsi.getX(), imsi.getY(), imsi.getX1(), imsi.getY1()); 
    	 	}
    	 	else if (imsi.getType() == 2){ 
    	 		if(imsi.getFill()){ 
    	 			g.fillRect(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY()); 
    	 		}
    	 		else{ 
    	 			g.drawRect(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY()); 
    	 			} 
    	 	}else if (imsi.getType() == 3){ 
    			 	if(imsi.getFill()){ 
    			 		g.fillOval(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY()); 
    				}else{ 
    				 	g.drawOval(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY()); 
    				} 
    	 	}else if (imsi.getType() == 7){ 
			 		if(imsi.getFill()){ 
			 			g.fillRoundRect(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY(),roundWidth,roundHeight); 
			 		}else{ 
			 			g.drawRoundRect(imsi.getX(), imsi.getY(), imsi.getX1() - imsi.getX(), imsi.getY1() - imsi.getY(),roundWidth,roundHeight); 
			 		} 
    	 	}
    	} 
    }// 
     
///////////////////마우스 리스너 구현/////////////////////////////////////////////////////////////
     public void mouseClicked(MouseEvent e){
    	 
     } 
     //마우스를 누른 지점을 시작점으로 등록 
     public void mousePressed(MouseEvent e){ 
    	 x = e.getX(); y = e.getY(); 
    } 
     //마우스를 뗀 지점을 끝점으로 등록한다. repaint()메서드를 호출하여 다시 그림을 그린다.
     public void mouseReleased(MouseEvent e){
    	 x1 = e.getX(); y1 = e.getY();
    	 //지우개 드래그 이후에 마우스를 놓았을때 검은 점이 안찍히려면 !
    	 if(dist == 4 || dist == 5 || dist == 6) { //지우개에 해당하는 것이 선택 되어 눌렸다면,
    		 Color c = new Color(255,255,255); //흰색
    		 DrawInfo di = new DrawInfo(x, y, x1, y1, dist, c, opfill.getState(),
    				 opthick1.getState(),opthick2.getState(),opthick3.getState(),
    				 oerasethick1.getState(), oerasethick2.getState(), oerasethick3.getState()); 
    		 vc.add(di); //그리기 정보 객체를 벡터에 저장한다.
    		 x = x1;  //끝난 지점에서 다시 그려져야하므로
    		 y = y1;
    	 }
    	 else {// 지우개 외에는 다 선택되어있는 색으로 바꿔주는 동작이 같음
    	 Color c = new Color(0,0,0); 
		 c = selectedColor; //선택된 색으로 바꿔 줌
    	 DrawInfo di = new DrawInfo(x, y, x1, y1, dist, c, opfill.getState(),
    			 opthick1.getState(),opthick2.getState(),opthick3.getState(),
    			 oerasethick1.getState(), oerasethick2.getState(), oerasethick3.getState());  //DrawInfo 클래스의 생성자를 초기화 해주고 그리기 정보갖는 객체 생성
    	 vc.add(di); //벡터에는 위 객체를 저장 //Vector내에 di객체를 저장한다.
    	 }
    	 this.repaint(); //paint메소드 호출해 다시 그림
     } 
     public void mouseEntered(MouseEvent e){ } 
     public void mouseExited(MouseEvent e){ }
     
///////////////////마우스 모션 리스너 구현//////////////////////////////////////////////////////////
     public void mouseMoved(MouseEvent e){ } 
   //마우스 드래그 시에 그림이 실시간으로 바뀌어야 하므로 repaint함
     public void mouseDragged(MouseEvent e){
    	 x1 = e.getX(); 
    	 y1 = e.getY();
    	//펜 선택시의 모든 움직임을 벡터에 저장한다. 
    	 if (dist == 0){ //펜
    		 Color c = new Color(0,0,0); 
    		 c = selectedColor; //선택된 색으로 바꿔 줌
    		 DrawInfo di = new DrawInfo(x, y, x1, y1, dist, c, opfill.getState(),
    				 opthick1.getState(),opthick2.getState(),opthick3.getState(),
    				 oerasethick1.getState(), oerasethick2.getState(), oerasethick3.getState()); 
    		 vc.add(di); //그리기 정보 객체를 벡터에 저장한다.
    		 x = x1;  //끝난 지점에서 다시 그려져야하므로
    		 y = y1; 
    	 }
    	 //지우개는 하얀색 펜! , JFrame의 디폴트 Background 값이 흰색
    	 //만약 지우개가 선택되면 Color c = new Color흰색; 으로만 바꿔주기 c = selectedColor; 는 빼기
    	 if(dist == 4 || dist == 5 || dist == 6) { //지우개에 해당하는 것이 선택 되어 눌렸다면,
    		 Color c = new Color(255,255,255); //흰색
    		 DrawInfo di = new DrawInfo(x, y, x1, y1, dist, c, opfill.getState(),
    				 opthick1.getState(),opthick2.getState(),opthick3.getState(),
    				 oerasethick1.getState(), oerasethick2.getState(), oerasethick3.getState()); 
    		 vc.add(di); //그리기 정보 객체를 벡터에 저장한다.
    		 x = x1;  //끝난 지점에서 다시 그려져야하므로
    		 y = y1;
    	 }
    	 
    	 this.repaint(); //그림과 지우개 등이 그때 그때 표현되어야 하기 때문에 해당 그림 그려줌
     } 
     
//////////////아이템 리스너 구현 ,CheckBoxMenuItem 객체 선택되는 경우 이벤트는 ItemEvent이므로
     public void itemStateChanged(ItemEvent e){ //체크박스메뉴아이템의 상태 추상메소드 1개 임
    	 if(e.getSource() == odpen){ //체크박스메뉴아이템 중 체크된 경우 이 이벤트는 ItemEvent 인스턴스이므로 setState로 체크된 상태를 바꿔 줄 수 있다.
    		 dist = 0; //메뉴바 하위 항목 순서대로 0번 인덱스 라고 도형정보를 갖는 객체 생성시에 type초기화 값으로 대응 된다. 그리고 그 값에 따라 paint하는 동작이 달라진다.
    		 odpen.setState(true);
    		 odline.setState(false); 
    		 odrect.setState(false);
    		 odcircle.setState(false);
    		 odround.setState(false);
    	}else if(e.getSource() == odline){ 
    		 dist = 1;
    		 odpen.setState(false); 
    		 odline.setState(true); 
    		 odrect.setState(false); 
    		 odcircle.setState(false);
    		 odround.setState(false);
    	}else if(e.getSource() == odrect){
    		 dist = 2;
    		 odpen.setState(false); 
    		 odline.setState(false); 
    		 odrect.setState(true); 
    		 odcircle.setState(false);
    		 odround.setState(false);
    	}else if(e.getSource()== odcircle){ 
    		 dist = 3; 
    		 odpen.setState(false); 
    		 odline.setState(false); 
    		 odrect.setState(false);
    		 odcircle.setState(true);
    		 odround.setState(false);
    	}else if(e.getSource()== odround){ 
   		 	dist = 7; 
   		 	odpen.setState(false); 
   		 	odline.setState(false); 
   		 	odrect.setState(false);
   		 	odcircle.setState(false);
   		 	odround.setState(true);
    	}else if(e.getSource()== opdraw){ 
    		 opdraw.setState(true); 
    		 opfill.setState(false);	
    	}else if(e.getSource()== opfill){
    		 opdraw.setState(false); 
    		 opfill.setState(true);	
    	}else if(e.getSource()== opthick1){
			 opthick1.setState(true); 
			 opthick2.setState(false);
			 opthick3.setState(false);
    	}else if(e.getSource()== opthick2){
			 opthick1.setState(false); 
			 opthick2.setState(true);
			 opthick3.setState(false);
    	}else if(e.getSource()== opthick3){
			 opthick1.setState(false); 
			 opthick2.setState(false);
			 opthick3.setState(true);
    	}else if(e.getSource()== oerasethick1){
    		 dist = 4;
    		 oerasethick1.setState(true); 
    		 oerasethick2.setState(false);
    		 oerasethick3.setState(false);
    		 oeraseStop.setState(false);
   	    }else if(e.getSource()== oerasethick2){
   	    	dist = 5;
   		    oerasethick1.setState(false); 
   		    oerasethick2.setState(true);
   		    oerasethick3.setState(false);
   		    oeraseStop.setState(false);
  	    }else if(e.getSource()== oerasethick3){
  	    	dist = 6;
   		    oerasethick1.setState(false); 
   		    oerasethick2.setState(false);
   		    oerasethick3.setState(true);
   		    oeraseStop.setState(false);
  	    }else if(e.getSource()== oeraseStop){
  	    	Color c = new Color(0,0,0);
  	    	selectedColor = c;
   		    oerasethick1.setState(false); 
   		    oerasethick2.setState(false);
   		    oerasethick3.setState(false);
   		    oeraseStop.setState(true);
  	    }       
    } 
/////////////////////액션 이벤트 리스너 구현////////////////////////////////////////////////////////
     public void actionPerformed(ActionEvent e){
    	 if (e.getSource() == fnew){
    		 vc.clear(); //모두 지우기
    		 x = 0; y = 0; x1 = 0; y1 = 0; dist = 0; //그리기 정보를 초기화해주는 값 모두 리셋
    		 this.repaint(); //위 상태 (백지로) 다시 그려주기
    		 }else if(e.getSource() == fopen){ 
    			 //열기
    			 FileDialog fdlg = new FileDialog(this, "열기", FileDialog.LOAD); 
    			 fdlg.setVisible(true); 
    			 String dir = fdlg.getDirectory();
    			 String file = fdlg.getFile(); 
    			 if(dir == null || file == null) return; 
    			 try{ ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File(dir, file))));
    			 vc = (Vector)ois.readObject(); ois.close();	
    			 }catch(IOException ee){	}catch(ClassNotFoundException ee){} 
    		}else if(e.getSource() == fsave){ 
    				 //저장 
    				 FileDialog fdlg = new FileDialog(this,"저장",FileDialog.SAVE); fdlg.setVisible(true); 
    				 String dir = fdlg.getDirectory();
    				 String file = fdlg.getFile(); 
    				 if (dir == null || file == null) return;
    				 try{ ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(new File(dir, file))));
    				 oos.writeObject(vc); oos.close(); }
    				 catch(IOException ee){	}	
    				 
    		}else if(e.getSource() == fexit){ System.exit(0); } 
    		else if(e.getSource() == ochoose){ //컬러 고르는 창 띄우기
    			
    			JColorChooser chooser = new JColorChooser(); // JColorChooser 클래스객체화
                selectedColor = chooser.showDialog(null, "Color", Color.BLACK); 
                // selectedColor에 선택된색으로 초기화
    		}
    	 } 
 
	   public static void main(String[] args){ 
		   DrawFrame2 a = new DrawFrame2();
	   }
	   
}

 

소스 코드 분석

DrawInfo 클래스를 만들어서 도형을 그릴 때 필요한 정보 (클릭된 좌표,놓은 좌표,몇번째 메뉴 항목, 선 굵기, 무슨 색인지, 색을 면에 채울 것인지,지우개 굵기 등의 정보)를 받을 수 있는 객체를 생성할 수 있게 한뒤 벡터에 객체를 담아서 도형을 그릴 때 필요한 정보를 접근자 메소드로 받아서 사용한다.

DrawFrame2는 프레임을 상속받고 MouseListener, MouseMotionListener, ItemListener, ActionListener 리스너들을 구현한다. 메뉴바는 JFrame에서만 구현 할 수 있기 때문에 메뉴바 클릭에 따른 이벤트 처리를 Frame 내부에서 했다. 메뉴바의 체크박스 아이템은 ItemListener로 체크 여부를 확인해야 한다. 이 과정에서 메뉴항목 번호를 부여해 주어서 어떤 항목이 눌렸는지에 따라 다른 동작을 할 수 있게 했다. DrawFrame2는 프레임은 생성시에 init함수로 GUI를 만들고 start함수로 구성요소들에게 적절한 리스너들을 달아 주었다. 시작 될 때부터 마우스 좌표를 받는 일을 수행해야 하므로 this.addMouseListener(this) , this.addMouseMotionListener(this) .

그려지게하는 것은 JFramegraphics객체를 제공하는 paint함수 내부에서 구현했다. 색상을 선택 할때는 JColorChooser 객체를 이용했다. 선택된 색상으로 DrawInfo객체의 컬러 값을 초기화해주어서 그렸다. graphics가 제공하는 그리기 함수들로 메뉴항목 번호에 따라 그리도록했다. 펜과 직선의 굵기는 Graphics2D객체로 하향 형변환해서 setStroke 메소드를 이용했다.

마우스가 클릭되면 그 지점의 좌표값을 얻어오도록 마우스 리스너를 구현했다.

마우스가 떼지면 뗀 지점을 끝점으로 등록했다. 그리고 동시에

DrawInfo 객체를 만들어서 벡터에 객체를 저장해준다. 그 후 repaint해준다.

메뉴바의 메뉴아이템은 이벤트 처리가 액션 이벤트이므로 눌린 소스 객체를 받아서 이벤트 처리를 한다.

 

 

실행 결과 이미지

 

펜, 직선을 3단계 굵기 중 택하고 원하는 색으로 그린 화면

직선 원하는 3단계 굵기 중 택하고 색으로 그린 화면

사각형 원하는 색으로 그리고 면을 색으로 채운 화면

원 원하는 색으로 그리고 면을 색으로 채운 화면

모서리가 둥근 사각형 원하는 색으로 그리고 면을 색으로 채운 화면

지우개 크기 조절이 가능한 걸 보여주는 화면