<토이 프로젝트 회고록> 자바 스윙(Java Swing)을 이용한 GUI 프로그램(ft. DBeaver)
Java내에 내장되어있는 GUI인 스윙(swing)을 이용하여 간단한 토이 프로그램(가계부 프로젝트)을 작성하였습니다.
어떤 기능을 어떻게 만들었는지 생각해야할 부분이 어떤 게 있을지 회고하기 위해 기록합니다.
》》》바로가기
▶로그인 기능

아이디 혹은 비밀번호를 기입하는 텍스트필드(textfield)가 기입되지 않으면 하단 라벨에 경고문을 생성.

옳지 않은 아이디나 비밀번호를 입력했을 때, 경고문 GUI 생성.
로그인 버튼에 이벤트를 처리해주는 메서드 actionListener 객체를 생성해 더 해주고 텍스트가 비어있을 때, 정확히는 idHint, pwHint 문자열과 동일한 경우 라벨 경고문이 보일 수 있게 해 줬다.
추가로 유저 정보가 담겨있는 DB와 일치하지 않은 아이디나 비밀번호를 입력한다면 경고창을 띄어줬다.
※ 새로운 GUI 경고창을 띄우는 것보다는 하단 라벨의 문자열을 바꿔주는 것이 성능적 이점이 있지 않을까 생각한다.
ms로 아주 짧은 시간이지만 성능의 차이는 확실하다. (실제 속도 차이 약 20~30배)
▷ 로그인 버튼(jButton1)에 "login"이라는 이름의 actionListener 객체를 추가
jButton1.addActionListener(login); // login 이벤트 리스너
▷ "login" actionListener 객체 정의
// "로그인" 버튼 클릭 이벤트
private ActionListener login = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String id = textField1.getText();
String pw = textField2.getText();
if (textField1.getText().equals(idHint) || textField2.getText().equals(pwHint)) {
jLabel4.setVisible(true);
} else {
if (bd.checkIdPw(id, pw)) {
System.out.println("아이디 확인");
jLabel1.setVisible(false);
new SelectAllGui(id);
dispose();
} else {
System.out.println("로그인 오류");
jLabel4.setVisible(true);
JOptionPane.showMessageDialog(null, "아이디 또는 비밀번호를 확인하세요!");
}
}
}
}; // end login trigger
1. 로그인 버튼이 눌리는 순간 id와 pw 텍스트 필드를 가져온다.
2. 각 텍스트 필드의 값이 idHint(텍스트 필드에 기본으로 쓰여있는 문구) 혹은 pwHint와 일치한다면 숨겨 놓았던 하단 문자열 라벨(jLabel4)을 보여준다(". setVisible(true)").
3. 두 텍스트 필드에 값이 있다면 미리 만들어두었던 id와 pw를 DB와 확인하는 메서드를 실행한다.
-> 일치하는 데이터가 존재할 경우 : id를 인자로 필요한 데이터를 가져온다.
-> 일치하는 데이터가 존재하지 않는다면, 경고창을 띄어준다.(". showMessageDialog")
▶회원가입 기능

1. 로그인과 동일하게 하나라도 비어있는 텍스트 필드가 존재(힌트와 동일한 문자열)할 경우 하단에 경고문구를 출력.
2. DB에서 정의한 필드의 크기보다 더 큰 데이터가 들어올 수 없게 텍스트 필드의 문자열을 제한.
3. "초기화" 버튼 클릭 시 텍스트 필드를 모두 "힌트"와 동일하게 변경.
▷ "회원가입" GUI에 포함된 "등록 버튼"과 "초기화 버튼" 이벤트
// "등록" 이벤트
private ActionListener userJoin = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (jTextField1.getText().equals(idHint) || jTextField2.getText().equals(pwHint)
|| jTextField3.getText().equals(phHint) || jTextField4.getText().equals(nameHint)) {
jLabel6.setText("모든 항목을 입력해주세요");
} else {
if (jTextField1.getText().length() > 10) {
jLabel6.setText("아이디가 너무 깁니다.\n(영문, 숫자 조합 10자 이내)");
} else if (jTextField2.getText().length() > 8) {
jLabel6.setText("비밀번호가 너무 깁니다.\n(영문, 숫자 조합 8자 이내)");
} else if (jTextField3.getText().length() > 11) {
jLabel6.setText("전화번호가 너무 깁니다.\n(숫자 11자 이내)");
} else if (jTextField4.getText().length() > 8) {
jLabel6.setText("이름이 너무 깁니다.\n(한글 8자 이내)");
} else {
vo = new UserinfoVo(jTextField1.getText(), jTextField2.getText(), jTextField3.getText(),
jTextField4.getText());
bd = UserinfoDao.getInstance();
if (!bd.idcheck(vo.getId())) {
bd.insert(vo);
dispose();
} else {
JOptionPane.showMessageDialog(null, "이미 존재하는 아이디입니다");
}
}
}
}
}; // userJoin end
// "초기화" 버튼 이벤트
private ActionListener initTextField = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jTextField1.setText(idHint);
jTextField2.setText(pwHint);
jTextField3.setText(phHint);
jTextField4.setText(nameHint);
}
}; // init end
▶텍스트 필드 힌트 삽입
텍스트 필드 힌트란? 텍스트 필드 속에 워터마크와 같이 실제 기입된 값은 아니지만(null 역할) 사용자가 확인할 수 있는 텍스트입니다. ex) "아이디를 입력해주세요."

텍스트 힌트는 실제는 존재하지 않는 값이 되어야 하기에 아래와 같은 기능을 수행해야 합니다.
1. 힌트와 동일한 문자열은 null로 취급.
2. 텍스트 필드에 값을 기입하려 할 때, 힌트 값이라면 비어있는 텍스트 필드(공백)가 되어야 함.
3. 텍스트 필드에 값을 기입하던 중이라면, 그대로 수정이 가능해야 함.
4. (옵션) 사용자 기입 값과 힌트를 구별하기 위해서 다른 폰트와 텍스트 색상을 사용함.

Font gainFont = new Font("Tahoma", Font.PLAIN, 11);
Font lostFont = new Font("Tahoma", Font.ITALIC, 11);
textField1.setText(idHint); // 텍스트 필드 힌트의 기본 문자
textField1.setFont(lostFont); // 텍스트 필드 힌트의 기본 폰트
textField1.setForeground(Color.GRAY); // 텍스트 필드 힌트의 기본 색상
textField1.addFocusListener(new FocusListener() { // 텍스트 필드 포커스 시 이벤트
@Override
public void focusLost(FocusEvent e) { // 포커스를 잃었을 때,
if (textField1.getText().equals("")) {
textField1.setText(idHint);
textField1.setFont(lostFont);
textField1.setForeground(Color.GRAY);
}
}
@Override
public void focusGained(FocusEvent e) { // 포커스를 얻었을 때,
if (textField1.getText().equals(idHint)) {
textField1.setText("");
textField1.setFont(gainFont);
textField1.setForeground(Color.BLACK);
}
}
});
이때, 텍스트 필드의 폰트가 변경되게 되는 게 인코딩과 관련된 문제가 발생할 수 있다.
텍스트 필드에 삽입된 데이터 중 한글을 가져와야 한다면, 한글을 지원하는 폰트를 사용해야 오류 발생이 없다. (한글을 직접 기입할 때, 깨질 수 있으니 확인해야 한다.)
나의 경우, 한글 데이터를 가져올 때, "Glum12" 폰트를 사용했다.
▶테이블 데이터 삽입

private DefaultTableModel model;
private JTable jTable1;
private JScrollPane jScrollPane1;
List<String[]> rows;
String[][] data;
String[] columns;
// ArrayList인 rows를 2차원 배열 data로 변환 후 매핑
rows = dao.getListById(id); // 데이터 튜플(ArrayList)
columns = new String[] { "수입|지출", "날짜", "금액", "분류", "메모", "계좌번호" , "IDX"}; // 컬럼
data = rows.stream().toArray(String[][]::new); // ArrayList -> Array
// jTable1 생성
model = new DefaultTableModel(data, columns); // 테이블에 붙일 모델 객체 생성(데이터, 컬럼)
jTable1 = new javax.swing.JTable(model); // 테이블 객체 생성
jScrollPane1.setViewportView(jTable1); // 패널에 테이블 붙이기
// jTable1 컬럼 별 정렬 기능 추가
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(jTable1.getModel());
jTable1.setRowSorter(sorter);
스윙에서 테이블을 보여주기 위해선 3가지 단계를 거친다.
1. 테이블에 삽일할 행(튜플)과 열(칼럼)을 인자로 받은 모델(model) 객체 생성
2. 생성한 모델을 인자로 받아 테이블(JTable) 객체 생성
3. 생성한 객체를 패널에 삽입
여기서 모델(mode) 객체의 인자로 들어가는 행 데이터는 배열(Array) 객체여야 하는데 나의 경우 쿼리로 데이터를 ArrayList로 가져오기 때문에 한번 더 Array로 변환(ArrayList -> Array)해야 하는 불합리가 있었다.(ArrayList 인 "rows"를 Array인 "data"로 캐스팅(casting) 필요)
=> 추가적으로 수정을 고려해야 할 사항 (성능적 측면)
▶테이블 새로고침
// "새로고침" 이벤트
private void jButton2ActionPerformed(ActionEvent evt, String id) {
// query 데이터를 가져오기 위한 DB와 연결된 객체
InexDao dao = InexDao.getInstance();
// 새 list를 가져오기
rows = dao.getListById(id);
// columns = new String[] { "수입|지출", "날짜", "금액", "분류", "메모", "계좌번호" , "IDX"};
data = rows.stream().toArray(String[][]::new);
// 새 모델 생성 후 테이블 생성
model = new DefaultTableModel(data, columns);
jTable1 = new JTable(model);
// jTable1 sort
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(jTable1.getModel());
jTable1.setRowSorter(sorter);
jScrollPane1.setViewportView(jTable1);
}
"새로고침" 버튼 이벤트의 경우 변경된 테이블의 리스트를 가져와 새로운 모델(model) 객체를 생성한 후 또다시 테이블(JTable)을 생성하는 것이다.
cloumn의 경우 전역 변수로 생성했다면 다시 생성할 필요는 없다.
칼럼 별 정렬을 담당하는 "TableRowSorter" 역시 하나의 객체이기 때문에 모델 객체와 함께 다시 생성해준다.
새로 생성한 테이블을 기존에 있던 패널에 다시 세팅해줌으로써 새로고침 기능으로 사용할 수 있다.
▶테이블 행 삭제

▷삭제 기능
1. 삭제를 원하는 튜플을 선택한다.
2. 삭제 버튼 클릭 -> 테이블에서 삭제된 후 DB에 적용.
// "삭제" 이벤트
private void jButton3ActionPerformed(ActionEvent evt) {
int row = jTable1.getSelectedRow(); // 선택된 행 번호
int idx = Integer.valueOf((String)jTable1.getValueAt(row, 6)); // 선택된 행, 열의 데이터 값
InexDao dao = new InexDao(); // DB 조작을 위한 객체 생성
model.removeRow(row); // 모델의 행 삭제
dao.delete(idx); // DB삭제 메서드
System.out.println(row +" "+ idx); // 행, 삭제 조건 데이터 값 출력(확인용)
}
현재 모델(model)에서 행을 삭제하는 내장 메서드(removeRow(삭제할 행))을 사용하면 GUI 테이블에 바로 적용된다.
(만약, 필터 기능을 이용해서 칼럼 별 필터를 사용하고 있다면, 바로 적용되지 않는다.)
getSelectedRow() 함수를 이용하면 jTable에서 선택된 행을 반환한다. 만약, 테이블이 setEnable(false)로 설정되어있다면 행이 선택되지 않으므로 setEnable(true)로 변경해 준다.
remoeRow은 DB에는 적용되는 것이 아니기에 미리 생성해 놓은 DB삭제를 위한 메서드를 실행한다.
'Programming > Java, Kotlin' 카테고리의 다른 글
| [Java] 자바에서 인자의 실제 값이 변경되는 이유 (0) | 2022.04.05 |
|---|---|
| [Java] XML파일과 자바(Java)로 파싱(parsing)하기 (0) | 2021.12.29 |
| 자바[Java] 입출력 Stream 이해하기 (0) | 2021.12.29 |
| 자바[Java] 인터페이스[Interface] 사용과 이유 (1) | 2021.12.29 |
| 자바[Java] 접근 제어자[Access modifier] (0) | 2021.12.29 |
































