JList - 단일 선택 모드(SingleSelectionMode)

(다중 선택 모드는 아래쪽에서 설명합니다.)

Swing 컴포넌트 중에서 자료를 추가하고 삭제할 수 있도록 만든 컴포넌트가 있습니다. 그게 JList인데요. 여러분들도 흔하게 접한 GUI이기도 할겁니다. 글로 설명하는 것보다는 아래의 캡처 화면으로 보시면 "아, 이거구나." 하실겁니다. JList는 하나의 항목을 선택할 수도 있고 다중 선택으로 여러 개의 항목을 선택할 수도 있습니다. 

 

그냥 JList의 메소드를 주구 장창 이야기하고 설명하는 것보다 코드를 구현하면서 그때 그때 필요한 메소드와 사용법을 설명하는 것이 재밌을 것 같습니다. 이제 아래의 프로그램을 Code를 일일히 해석해가면서 구현해볼까요?

 

 

JList Test Program

 

위의 프로그램은 이렇게 동작합니다.

1. 추가 -  JTextField를 통해서 문자열을 입력받아서 추가 또는 엔터를 누르면 JList에 추가가 됩니다. TextField가 비었으면 동작하지 않습니다.

2. 삭제 - 삭제를 누르게 되면 가장 위쪽의 항목이 삭제됩니다. 만약 항목을 선택한 상태에서 삭제 버튼을 누르면 선택된 항목이 삭제됩니다.

3. 선택 - 선택을 누르면 별 다른 것은 없고 컴퓨터 화면에 출력해줍니다. 

 

1. 초기 설정

public class JListTest extends JFrame implements MouseListener,KeyListener,ListSelectionListener{
	
	
	private JList list;				//리스트
	private JTextField inputField;	//테스트 입력 Field
	private JButton addBtn;		//추가 버튼
	private JButton delBtn;		//삭제 버튼
	
	private DefaultListModel model;	//JList에 보이는 실제 데이터
	private JScrollPane scrolled;
	
	public JListTest(String title) {
		super(title);
		init();
	}

	public void init() {
		model=new DefaultListModel();
		list=new JList(model);
		inputField=new JTextField(35);
		addBtn=new JButton("추가");
		delBtn=new JButton("삭제");
		
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);	//하나만 선택 될 수 있도록
		
		inputField.addKeyListener(this);	//엔터 처리
		addBtn.addMouseListener(this);		//아이템 추가
		delBtn.addMouseListener(this);		//아이템 삭제
		list.addListSelectionListener(this);	//항목 선택시
		
		this.setLayout(new BorderLayout());
		
		
		JPanel topPanel=new JPanel(new FlowLayout(10,10,FlowLayout.LEFT));
		topPanel.add(inputField);
		topPanel.add(addBtn);
		topPanel.add(delBtn);		//위쪽 패널 [textfield]  [add] [del]
		topPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));	//상, 좌, 하, 우 공백(Padding)
		
		scrolled=new JScrollPane(list);
		scrolled.setBorder(BorderFactory.createEmptyBorder(0,10,10,10)); 
		
		this.add(topPanel,"North");
		this.add(scrolled,"Center");	//가운데 list
		
		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setSize(620,400);
		this.setLocationRelativeTo(null);	//창 가운데 위치
		this.setVisible(true);
		
	}
}

 

우선 JFrame을 상속받고 Component들을 배치시켜줍니다. 그리고 필요한 EventListener를 지정해주죠. 여기서 필요한 Listener는 전부 JListTest에 구현하도록 하겠습니다.

JList를 사용할때는 단순 문자열같은 아이템을 추가하고자 한다면 DefaultListModel을 사용합니다. 이것을 JList의 생성자로 전달하면 JList는 DefaultListModel을 읽어서 리스트에 보여줍니다. 그리고 항목이 JList의 크기를 넘어갈때 마우스 스크롤이 되어야겠지요? 그래서 JScrollPane을 사용하여 스크롤되게 해줍니다. 이때 JList에 setPreferredSize를 사용하여 사이즈를 지정해버리면 JScrollPane이 정상동작하지 않습니다.

 

2. 이벤트 핸들링

 

 

public class JListTest extends JFrame implements MouseListener,KeyListener,ListSelectionListener{
	//... 생략...//
	public void init() {
		//...생략...//
	}

	//MouseListener
	@Override
	public void mouseClicked(MouseEvent e) {
		
	}
	@Override
	public void mousePressed(MouseEvent e) {}
	@Override
	public void mouseReleased(MouseEvent e) {}
	@Override
	public void mouseEntered(MouseEvent e) {}
	@Override
	public void mouseExited(MouseEvent e) {}

	//KeyListener
	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void keyPressed(KeyEvent e) {}
	@Override
	public void keyReleased(KeyEvent e) {
		
	}

	
    //ListSelectionListener
	@Override
	public void valueChanged(ListSelectionEvent e) {
	}
	
}

 

우리가 구현할 것은 버튼이 클릭됐을때 추가, 삭제 기능이 되어야하기 때문에 mouseClicked 메소드를 구현하고, 키보드 엔터 처리가 되어야하기 때문에 keyReleased() 메소드, 아이템이 선택됐을때 처리를 하기 위해 valueChanged()를 구현해야합니다. 

2-1. 추가

버튼 클릭시에는 텍스트가 입력이 되었는지 안되었는지부터 확인하고 텍스트가 입력되었다면 list항목에 추가해주면 됩니다. 아까 실 데이터는 DefaultListModel 객체에 존재한다고 했었죠. DefaultListModel의 addElement 메소드를 사용하면 추가됩니다. 추가는 addItem()이라는 메소드로 처리하도록 합시다.

	public void addItem() {
		String inputText=inputField.getText();
		if(inputText==null||inputText.length()==0) return;
		model.addElement(inputText);
		inputField.setText("");		//내용 지우기
		inputField.requestFocus();	//다음 입력을 편하게 받기 위해서 TextField에 포커스 요청
		//가장 마지막으로 list 위치 이동
		scrolled.getVerticalScrollBar().setValue(scrolled.getVerticalScrollBar().getMaximum());
	}

 

나머지는 편하게 항목을 추가하기 위한 작업인데요. 내용을 지운 후 다음 입력을 바로 받기 위해서 포커싱을 요청합니다. 그리고 맨 아랫줄의 scrolled 메소드는 가장 마지막 항목으로 이동하기 위해서 추가했습니다.

이 메소드는 addBtn이 클릭했을때 호출하면 되겠네요.

@Override
	public void mouseClicked(MouseEvent e) {
		if(e.getSource() == addBtn) {
			addItem();
		}
		
	}

 

그리고 엔터가 눌렸을 경우도 역시 addItem()을 호출하면 됩니다.  엔터 처리는 별것 없죠?

 

 

	@Override
	public void keyReleased(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if(keyCode==KeyEvent.VK_ENTER) {
			addItem();
		}
	}

 

2-2. 삭제

이제 삭제를 구현해볼까요? 삭제하는 메소드는 removeItem(int index) 이라고 정의하도록 합시다. 만약 index가 0보다 작은 경우는 선택된 항목이 없다는 것으로 가정하겠습니다. 이때 선택된 항목이 없는 경우는 두가지가 존재하겠죠? 아예 추가된 항목이 없는 경우(1), 그리고 항목은 존재하지만 선택하지 않은 경우(2)입니다.

첫번째 경우(1)는 list의 항목들이 있는지 봐야하는데 이때 실데이터가 있는 DefaultListModel의 size() 메소드를 보면 됩니다. 만약 size()가 0이라면 추가된 항목이 없는것이니까 아무것도 하지 않고 빠져나옵니다. 

size가 0보다 큰 경우는 두번째 경우(2)이므로 가장 맨 위쪽의 index인 0을 지정하여 삭제합니다. 삭제하는 메소드는 DefaultListModel의 remove() 메소드를 사용하면 됩니다.

결국 removeItem() 메소드는 아래와 같이 구현됩니다.

 

	public void removeItem(int index) {
		if(index<0) {
			if(model.size()==0) return;	//아무것도 저장되어 있지 않으면 return
			index=0;	//그 이상이면 가장 상위 list index
		}
		
		model.remove(index);
	}

 

삭제버튼이 눌렸을때 처리해야하는데, 선택된 아이템의 index를 가져오는 메소드는 JList의 getSelectedIndex()메소드를 사용하면 됩니다. 만약 선택된 index가 없다면 이 메소드는 -1을 반환하게 됩니다. 이 반환값을 그대로 removeItem()에 전달하면 되겠네요.

그래서 삭제 버튼이 눌렸을때는 아래와 같이 구현됩니다.

	@Override
	public void mouseClicked(MouseEvent e) {
		if(e.getSource() == addBtn) {
			addItem();
		}
		if(e.getSource() == delBtn) {
			int selected=list.getSelectedIndex();
			removeItem(selected);
		}
	}

 

만약 -1의 값을 처리하지 않을 경우 아래와 같이 ArrayIndexOutOfBoundsException 예외가 발생합니다.

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
...

 

2-3. 항목이 눌렸을때

항목이 새롭게 선택되면 ListSelectionListener의 valueChanged()메소드가 호출됩니다. 그래서 여기서 처리를 해주면 되는데, 우리는 단순히 시스템 output stream(화면)에 출력하기로 했죠. 

 

 

	@Override
	public void valueChanged(ListSelectionEvent e) {
		if(!e.getValueIsAdjusting()) {	//이거 없으면 mouse 눌릴때, 뗄때 각각 한번씩 호출되서 총 두번 호출
			System.out.println("selected :"+list.getSelectedValue());
		}
	}

 

 

e.getValueIsAdjusting()이라는 메소드를 통해서 값 조정 중이 아닐때에만 출력하도록 구현했습니다. vaueChanged는 여러번 호출되는데, 그렇기 때문에 만약 if 조건문이 없다면 여러번 출력됩니다.

 

3. 전체 구현 코드

이제 전부 구현했습니다. JListTest 전체 코드는 아래에 존재합니다. import는 코드의 간결함을 위해 모두 포함했습니다.

 

- JListTest.java

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

public class JListTest extends JFrame implements MouseListener,KeyListener,ListSelectionListener{
	
	
	private JList list;				//리스트
	private JTextField inputField;	//테스트 입력 Field
	private JButton addBtn;		//추가 버튼
	private JButton delBtn;		//삭제 버튼
	
	private DefaultListModel model;	//JList에 보이는 실제 데이터
	private JScrollPane scrolled;
	
	public JListTest(String title) {
		super(title);
		init();
	}

	public void init() {
		model=new DefaultListModel();
		list=new JList(model);
		inputField=new JTextField(35);
		addBtn=new JButton("추가");
		delBtn=new JButton("삭제");
		
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);	//하나만 선택 될 수 있도록
		
		inputField.addKeyListener(this);	//엔터 처리
		addBtn.addMouseListener(this);		//아이템 추가
		delBtn.addMouseListener(this);		//아이템 삭제
		list.addListSelectionListener(this);	//항목 선택시
		
		this.setLayout(new BorderLayout());
		
		
		JPanel topPanel=new JPanel(new FlowLayout(10,10,FlowLayout.LEFT));
		topPanel.add(inputField);
		topPanel.add(addBtn);
		topPanel.add(delBtn);		//위쪽 패널 [textfield]  [add] [del]
		topPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));	//상, 좌, 하, 우 공백(Padding)
		
		scrolled=new JScrollPane(list);
		scrolled.setBorder(BorderFactory.createEmptyBorder(0,10,10,10)); 
		
		this.add(topPanel,"North");
		this.add(scrolled,"Center");	//가운데 list
		
		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setSize(620,400);
		this.setLocationRelativeTo(null);	//창 가운데 위치
		this.setVisible(true);
		
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		if(e.getSource() == addBtn) {
			addItem();
		}
		if(e.getSource() == delBtn) {
			int selected=list.getSelectedIndex();
			removeItem(selected);
		}
	}
	
	public void removeItem(int index) {
		if(index<0) {
			if(model.size()==0) return;	//아무것도 저장되어 있지 않으면 return
			index=0;	//그 이상이면 가장 상위 list index
		}
		
		model.remove(index);
	}

	public void addItem() {
		String inputText=inputField.getText();
		if(inputText==null||inputText.length()==0) return;
		model.addElement(inputText);
		inputField.setText("");		//내용 지우기
		inputField.requestFocus();	//다음 입력을 편하게 받기 위해서 TextField에 포커스 요청
		//가장 마지막으로 list 위치 이동
		scrolled.getVerticalScrollBar().setValue(scrolled.getVerticalScrollBar().getMaximum());
	}
	//MouseListener 
	@Override
	public void mousePressed(MouseEvent e) {}
	@Override
	public void mouseReleased(MouseEvent e) {}
	@Override
	public void mouseEntered(MouseEvent e) {}
	@Override
	public void mouseExited(MouseEvent e) {}

	//KeyListener
	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void keyPressed(KeyEvent e) {}

	@Override
	public void keyReleased(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if(keyCode==KeyEvent.VK_ENTER) {
			addItem();
		}
	}

	
	//ListSelectionListener
	@Override
	public void valueChanged(ListSelectionEvent e) {
		if(!e.getValueIsAdjusting()) {	//이거 없으면 mouse 눌릴때, 뗄때 각각 한번씩 호출되서 총 두번 호출
			System.out.println("selected :"+list.getSelectedValue());
		}
	}
	
}

 

JListTest를 Main함수에서 생성하면 됩니다. 어렵지 않죠.

 

 

- Main.java

public class Main {
	
	public static void main(String[] ar) {
		JListTest frame=new JListTest("JList Test");
	}
}

 

 

JList - 다중 선택 모드(Multiple Selection Mode)

다중 선택을 할수도 있습니다. 아래와 같이 JList에 setSelectionMode를 MULTIPLE_INTERVAL_SELECTION으로 변경하면 가능하죠. 

list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);	//여러개 선택 될 수 있도록

 

1. 선택된 항목들 읽어오기 

만약 여러개 선택된 값을 확인하고 싶다면 JList의 getSelectedValuesList() 메소드를 통해 List Collection으로 선택된 항목들을 가져올 수 있습니다. 그래서 다중 선택의 valueChanged() 버전은 아래와 같이 구현됩니다.

	//ListSelectionListener
	@Override
	public void valueChanged(ListSelectionEvent e) {
		if(!e.getValueIsAdjusting()) {	
			List<String> ls=list.getSelectedValuesList();
			for(String value:ls) {
				System.out.println("selected : "+value);
			}
		}
	}

 

2. 선택된 항목들 삭제하기

삭제하는 것은 조금 생각해봐야하는데요. getSelectedIndices() 메소드로 선택된 인덱스들을 정수형 배열로 가져올 수 있습니다. 이 배열은 오름차순으로 정렬된 배열입니다. 그 배열은 removeItem() 전달해주도록 하겠습니다.

	if(e.getSource() == delBtn) {
		int []selected=list.getSelectedIndices();
		removeItem(selected);
	}

 

만약 선택되지 않았다면 배열의 길이는 0이 되니까 선택안된 경우에는 size()와 비교해서 처리하면 되구요. 그것보다는 여러개가 분산적으로 처리됐을때 삭제하는 것이 문제인데, 이때 삭제하는 방법은 거꾸로 인덱스를 돌면서 삭제해야 이런 예외가 발생하지 않습니다.

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 11 >= 10
...

 

왜냐면 remove하면 길이가 하나씩 줄어드니, for문으로 계속 remove를 하면 사이즈가 계속 줄어들기 때문에 발생하게 됩니다. 이렇게 되면 index가 size()를 넘어설 수가 있기 때문에 size()가 줄어드는 것은 허용하되 index를 넘어가지 않게 거꾸로(내림차순) 돌아주는 겁니다. 

	public void removeItem(int []indices) {
		if(indices.length==0) {
			if(model.size()==0) return;	//아무것도 저장되어 있지 않으면 return
			model.remove(0);	//그 이상이면 가장 상위 list index
			return;
		}
		for(int i=indices.length-1;i>=0;i--) {
			model.removeElementAt(indices[i]);
		}
	}

 

완성된 다중선택 모드의 실행화면입니다.

multiple selected
multiple removed

 

너무 길었네요. 이상으로 JList에 대한 포스팅을 마치도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,