본문 바로가기
BOOKS/수월한 Java

swing으로 만들어본 멀티 메모장 #2/2

by IT여행자 2023. 3. 20.
728x90

안녕하세요 IT여행자입니다.

이번 여행지는 swing으로 만들어본 멀티 메모장 두 번째 부분입니다. 

첫 번째 영상에서도 언급한 것처럼 자바 GUI를 사용한 프로그램이라는 인터페이스가 많이 사용되는 인터페이스가 아니지만, 진입 장벽이 높지 않으며, 웹 인터페이스처럼 다양한 언어를 사용하지 않아도 만들어 볼 수 있는 프로그램이기에 두 편에 걸쳐 만들어 봤습니다.

개발에 필요한 보다 자세한 내용들은 영상을 참조하시면서 하나씩 개발해 보시는 걸 추천드립니다.

1편에서는 레이아웃에 관한 내용을 다루었고, 2편에서는 BACK-END 부분을 다루었습니다.

 

주요 내용은 아래와 같습니다.

 

  • MemoVo.java : 하나의 메모 내용을 저장하는 Value Object
  • config.xml : mybatis를 사용하기 위한 환결 설정
  • memo.xml : 메모와 관련된 CRUD 매퍼 파일
  • Factory.java : mybatis의 환결설정 파일과 매퍼를 읽어 들여 실제로 Sqlite 데이터베이스를 사용할 수 있게 해주는 클래스
  • Content.java : 메모 목차를 지정하는 인터널 프레임
  • Editor.java : 메모장을 작성할 때 사용되는 인터널 프레임
  • Main.java : 메모장의 메인 프레임

https://youtu.be/zdvKrKoCgug

 

영상에서 작성한 코드의 완성본을 하단에 적습니다.

 

MemoVo.java

 

하나의 메모 내용을 저장하는 클래스입니다.

package memo;

public class MemoVo {
	int id;
	String nal;
	String subject;
	String doc;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getNal() {
		return nal;
	}
	public void setNal(String nal) {
		this.nal = nal;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getDoc() {
		return doc;
	}
	public void setDoc(String doc) {
		this.doc = doc;
	}
	
}

 

config.xml

 

sqlite 데이터 베이스를 연결할 때 사용되는 정보들

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.sqlite.JDBC"/>
        <property name="url" value="jdbc:sqlite:db/memo.db"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
		<mapper resource="memo/myba/memo.xml"/> 
  </mappers>
</configuration>

 

memo.xml

 

메모장에 관한 CRUD 매퍼 파일

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="memo">
	<insert id='insert' parameterType="memo.MemoVo">
		insert into memo(nal, subject, doc) 
		values(#{nal}, #{subject}, #{doc})
	</insert>
  	<update id='update' parameterType="memo.MemoVo">
		update memo set  subject = #{subject}, doc = #{doc} 
		where id = #{id}
	</update>  
	<delete id='delete' parameterType="Integer">
		delete from memo where id = ${_parameter}
	</delete>	
	
	<select id='content'  resultType="memo.MemoVo">
		select id, nal, subject from memo order by nal desc
	</select>
	<select id='last_row' resultType="Integer">
		select last_insert_rowid()
	</select>
	<select id='select_one' parameterType="Integer" resultType="memo.MemoVo">
		select id, nal, subject, doc from memo where id = #{_parameter}
	</select>	
			
</mapper>

 

Factory.java

 

Sqlite 데이터 베이스 연결 정보 객체

package memo.myba;

import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Factory {
	private static SqlSessionFactory factory;
	static {
		try {
			Reader reader = Resources.getResourceAsReader("memo/myba/config.xml");
			factory = new SqlSessionFactoryBuilder().build(reader);

		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	public static SqlSessionFactory getFactory() {
		return factory;
	}

}

 

Content.java

 

메모장들의 제목 표시 인터널 프레임

package memo;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.swing.JInternalFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

import org.apache.ibatis.session.SqlSession;

import memo.myba.Factory;

public class Content extends JInternalFrame {
	Main main; // 메모목차클래스에서 Main객체의 멤버들을 접근하기 위한 요소
	List<Integer> idList; // 메모의 id
	protected DefaultListModel<String> model; // 메모제목들
	SqlSession session; // mybatis를 사용하기 위한 객체

	private JScrollPane scrollPane;
	private JList subjectList;


	/**
	 * Create the frame.
	 */
	public Content(Main main) {
		super("메모목차", false, true, false, false);
		this.main = main;
		
		addInternalFrameListener(new InternalFrameAdapter() {
			@Override
			public void internalFrameClosed(InternalFrameEvent e) {
				main.getDesktopPane().remove(Content.this);
				main.content= null;
				
			}
		});

		setBounds(100, 100, 291, 433);
		getContentPane().add(getScrollPane(), BorderLayout.CENTER);
		
		idList = new ArrayList<Integer>();
		makeAllSubject();
		setVisible(true);

	}

	
	public void makeAllSubject() {
		if (session == null) {
			session = Factory.getFactory().openSession();
		}

		List<MemoVo> list = session.selectList("memo.content");
		
		
		for (MemoVo m : list) {
			String temp = String.format("%-12s %s", m.getNal(), m.getSubject());
			model.addElement(temp);
			idList.add(m.getId());
		}

		session.close();
		session = null;
	}	
	
	public void load(int[] indexs) {

		for (int i : indexs) {
			int id = idList.get(i);
			Editor ed = null;
			// 기존창이 있는지 체크
			Component[] comps = main.getDesktopPane().getComponents();
			boolean exist = false;
			for (Component comp : comps) {
				if (comp instanceof Editor) {
					ed = (Editor) comp;
					if (ed.getId() == id) {
						exist = true;
						break;
					}
				}
			}
			if (exist) {
				ed.toFront();
			} else {
				//에디터창의 기본 위치를 지정하기 위해
				int x = (i * 22);
				int y = (i * 22);

				Point point = new Point(x, y);
				getData(idList.get(i), point);
			}
		}
	}
	
	public void getData(int id, Point point) {
		if (session == null) {
			session = Factory.getFactory().openSession();
		}
		
		MemoVo vo = session.selectOne("memo.select_one", id);

		Editor ed = new Editor(main);
		ed.setData(vo);
		ed.setLocation(point);
		main.getDesktopPane().add(ed);
		ed.toFront();

		session.close();
		session = null;
	} 
	
	public void delete(int index) {
		if (session == null) {
			session = Factory.getFactory().openSession();
		}
		int yn = JOptionPane.showConfirmDialog(Content.this, 
				"정말 삭제하시겠습니까?", "데이터 삭제", JOptionPane.YES_NO_OPTION);

		if (yn == JOptionPane.OK_OPTION) {
			int id = idList.get(index);
			int cnt = session.delete("memo.delete", id);
			if (cnt > 0) {
				session.commit();
				model.remove(index);
				idList.remove(index);
				Component[] comps = main.getDesktopPane().getComponents();
				for (Component comp : comps) {
					if (comp instanceof Editor) {
						Editor ed = (Editor) comp;
						if (ed.getId() == id) {
							main.getDesktopPane().remove(ed);
							main.getDesktopPane().updateUI();
						}
					}
				}

			}

			session.close();
			session = null;
		}

	}	
	
	protected JScrollPane getScrollPane() {
		if (scrollPane == null) {
			scrollPane = new JScrollPane();
			scrollPane.setViewportView(getSubjectList());
		}
		return scrollPane;
	}

	protected JList getSubjectList() {
		if (subjectList == null) {
			
			model = new DefaultListModel<String>();
			subjectList = new JList(model);
			subjectList.addKeyListener(new KeyAdapter() {
				@Override
				public void keyReleased(KeyEvent e) {
					
					if (e.getKeyCode() == KeyEvent.VK_ENTER) {
						int[] indexs = subjectList.getSelectedIndices();
						load(indexs);
					}

				}
			});
			
			subjectList.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					if (e.isMetaDown()) { // 마우스 우클릭하여 삭제
						int index = subjectList.locationToIndex(e.getPoint());

						if (index == -1) return;
						subjectList.setSelectedIndex(index);
						delete(index);
					} else if (e.getClickCount() == 2) {
						int[] indexs = subjectList.getSelectedIndices();
						load(indexs);
					} else {
						int index = subjectList.getSelectedIndex();
						if (index == -1) return;
						
						int id = idList.get(index);

						Component[] comps = main.getDesktopPane().getComponents();
						for (Component comp : comps) {
							if (comp instanceof Editor) {
								Editor ed = (Editor) comp;
								if (ed.getId() == id) {
									ed.toFront();

									break;
								}
							}
						}
					}					
					
					
				}
			});
		}
		return subjectList;
	}
}

 

Editor.java

 

메모 작성과 관련된 인터널 프레임

package memo;

import java.awt.BorderLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.swing.JInternalFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

import org.apache.ibatis.session.SqlSession;

import memo.myba.Factory;

public class Editor extends JInternalFrame implements DocumentListener{
	int id=0;// 에디터 자신의 아이디
	boolean editFlag=false; // 현재 편집 상태
	boolean load = false; //처음 로딩된 경우
	SqlSession session;
	Main main;
	
	private JScrollPane scrollPane;
	private JTextArea textArea;

	

	/**
	 * Create the frame.
	 */
	public Editor(Main m) {
		super("새 메모", true, true, true, true);
		this.main = m;
		setVisible(true);
		
		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent e) {
				activate();
			}
		});
		addInternalFrameListener(new InternalFrameAdapter() {
			@Override
			public void internalFrameActivated(InternalFrameEvent e) {
				activate();
			}
		});
		
		setBounds(100, 100, 450, 300);
		getContentPane().add(getScrollPane(), BorderLayout.CENTER);

	}

	public void activate() {
		//열러진 메모장의 id목록을 가져옴.
		if(main.getContent() == null) return;
		List<Integer> idList = main.getContent().idList;
		
		//메모목차(Content)에 들어있는 JLIST 항목들
		JList jlist = main.getContent().getSubjectList();
		
		for(int i=0; i<idList.size() ; i++) {
			int id = idList.get(i);
			if(id == this.id) {
				jlist.setSelectedIndex(i);
			}
		}
	}
		
	
	public void setId(int id) {
		this.id = id;
	}
	public int getId() {
		return id;
	}
	
	public void setData(MemoVo vo) {
		this.setId(vo.getId());
		this.setTitle(vo.getSubject());
		this.getTextArea().setText(vo.getDoc());
	}
	

	public void save() {
		if(session == null) {
			session = Factory.getFactory().openSession();
		}
		
		String doc = getTextArea().getText();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		MemoVo vo = new MemoVo();
		
		//엔터없이 한줄인 경우 
		int length = doc.indexOf("\n");
		if(length == -1) {
			length = doc.length();
		}
		
		// 제목으로 사용할 문자열을 15자 아내로 
		length = (length<15)? length : 15;
		
		// 테이블에 저장할 데이터를 vo 객체에 담음.
		String subject = doc.substring(0,length);
		vo.setNal(sdf.format(new Date()));
		vo.setId(id);
		vo.setSubject(subject);
		vo.setDoc(doc);
		
		try {
			int cnt = 0;
			// 새글인 경우 insert
			if(this.id == 0) {
				cnt = session.insert("memo.insert", vo);
				
				// 저장에 성공했다면 저장된 마지막 행번호를 가져와
				// 에디터창의 id로 사용하고
				// 메모 목차 설정, 에디터창 타이틀 설정
				if(cnt>0) {
					this.id = session.selectOne("memo.last_row");
					String temp = String.format("%-12s %s",vo.getNal(), subject);
					if(main.getContent() != null) {
						main.getContent().model.addElement(temp);
						main.getContent().idList.add(this.id);
					}
					setTitle(subject);
				}
				
			}else { // 수정인 경우 update
				cnt = session.update("memo.update", vo);
				setTitle(subject);
				String temp = String.format("%-12s %s",vo.getNal(), subject);
				int index = main.getContent().idList.indexOf(this.id);
				main.getContent().model.set(index, temp);
			}
			
			if(cnt>0) {
				session.commit();
				editFlag=false;
			}else {
				session.rollback();
				throw new Exception("저장되지 않았습니다.");
			}
		}catch(Exception ex) {
			ex.printStackTrace();
		}
		
		session.close();
		session = null;
	}


	public void changed() {
		if( !editFlag && load) {
			setTitle(getTitle() + " *");
			editFlag = true;
		}
		load = true;  // 이미로딩되었다.
	}		
		
	
	

	protected JScrollPane getScrollPane() {
		if (scrollPane == null) {
			scrollPane = new JScrollPane();
			scrollPane.setViewportView(getTextArea());
		}
		return scrollPane;
	}
	protected JTextArea getTextArea() {
		if (textArea == null) {
			textArea = new JTextArea();
			
			textArea.getDocument().addDocumentListener(this);
			
			textArea.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseEntered(MouseEvent e) {
					activate();
				}
			});
			textArea.addKeyListener(new KeyAdapter() {
				@Override
				public void keyPressed(KeyEvent e) {
					// Ctrl +s 키를 누를 때
					if(e.isControlDown() && e.getKeyCode()== KeyEvent.VK_S) {
						save();
					}
					
				}
			});
		}
		return textArea;
	}

	@Override
	public void insertUpdate(DocumentEvent e) {
		// TODO Auto-generated method stub
		changed();
	}

	@Override
	public void removeUpdate(DocumentEvent e) {
		// TODO Auto-generated method stub
		changed();

	}

	@Override
	public void changedUpdate(DocumentEvent e) {
		// TODO Auto-generated method stub
		changed();

	}
	
	
	
	
}

 

Main.java

 

메모장의 메인 프레임

package memo;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

public class Main extends JFrame {
	protected Content content;	

	private JPanel contentPane;
	private JMenuBar menuBar;
	private JMenu mnNewMenu;
	private JMenuItem mntmNewMenuItem;
	private JMenuItem mntmNewMenuItem_1;
	private JMenuItem mntmNewMenuItem_2;
	private JMenuItem mntmNewMenuItem_3;
	private JMenuItem mntmNewMenuItem_4;
	private JDesktopPane desktopPane;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Main frame = new Main();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	
	public void exit() {
		int yn = JOptionPane.showConfirmDialog(Main.this, 
				"프로그매을 종료하시겠습니까?", "프로그램 종료", 
				JOptionPane.YES_NO_OPTION);
		if(yn == JOptionPane.OK_OPTION) {
			setDefaultCloseOperation(EXIT_ON_CLOSE);
			System.exit(0);
		}else {
			setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		}
	}	
	
	public Main() {
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				exit();
			}
			
		});
		setTitle("메모장");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 761, 460);
		setJMenuBar(getMenuBar_1());
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

		setContentPane(contentPane);
		contentPane.setLayout(new BorderLayout(0, 0));
		contentPane.add(getDesktopPane(), BorderLayout.CENTER);
		
	}
	
	
	
	public Content getContent() {
		return content;
	}


	protected JMenuBar getMenuBar_1() {
		if (menuBar == null) {
			menuBar = new JMenuBar();
			menuBar.add(getMnNewMenu());
		}
		return menuBar;
	}
	protected JMenu getMnNewMenu() {
		if (mnNewMenu == null) {
			mnNewMenu = new JMenu("관리");
			mnNewMenu.add(getMntmNewMenuItem());
			mnNewMenu.add(getMntmNewMenuItem_1());
			mnNewMenu.add(getMntmNewMenuItem_2());
			mnNewMenu.add(getMntmNewMenuItem_3());
			mnNewMenu.add(getMntmNewMenuItem_4());
		}
		return mnNewMenu;
	}
	protected JMenuItem getMntmNewMenuItem() {
		if (mntmNewMenuItem == null) {
			mntmNewMenuItem = new JMenuItem("목차보기");
			mntmNewMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {

					if(content == null) {
						content = new Content(Main.this);
						contentPane.add(content,BorderLayout.WEST);
					}
				}
			});
		}
		return mntmNewMenuItem;
	}
	protected JMenuItem getMntmNewMenuItem_1() {
		if (mntmNewMenuItem_1 == null) {
			mntmNewMenuItem_1 = new JMenuItem("새로만들기");
			mntmNewMenuItem_1.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					
					Editor ed = new Editor(Main.this);
					desktopPane.add(ed);
					ed.toFront();
					
				}
			});
		}
		return mntmNewMenuItem_1;
	}
	protected JMenuItem getMntmNewMenuItem_2() {
		if (mntmNewMenuItem_2 == null) {
			mntmNewMenuItem_2 = new JMenuItem("저장");
			mntmNewMenuItem_2.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					
					Editor ed = (Editor) desktopPane.getSelectedFrame();
					ed.save();
					
				}
			});
		}
		return mntmNewMenuItem_2;
	}
	protected JMenuItem getMntmNewMenuItem_3() {
		if (mntmNewMenuItem_3 == null) {
			mntmNewMenuItem_3 = new JMenuItem("모두저장");
			mntmNewMenuItem_3.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					
					Component[] comps = desktopPane.getComponents();
					for(Component comp : comps) {
						if(comp instanceof Editor) {
							Editor ed = (Editor)comp;
							ed.save();
						}
					}
					
					
				}
			});
		}
		return mntmNewMenuItem_3;
	}
	protected JMenuItem getMntmNewMenuItem_4() {
		if (mntmNewMenuItem_4 == null) {
			mntmNewMenuItem_4 = new JMenuItem("종료");
			mntmNewMenuItem_4.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					
					exit();
					
				}
			});
		}
		return mntmNewMenuItem_4;
	}
	protected JDesktopPane getDesktopPane() {
		if (desktopPane == null) {
			desktopPane = new JDesktopPane();
		}
		return desktopPane;
	}
}