01.JAVA/Java2008. 11. 12. 17:23
반응형

Java SE 6.0(코드명 Mustang)에는 Swing JTable의 내용을 훨씬 쉽게 정렬하고 필터링할 수 있게 해주는 몇 가지 기능이 추가된다. (이 기능들이 최종적으로 포함되려면 JCP의 승인을 거쳐야 한다.) 최근의 테이블 중심 사용자 인터페이스는 대부분 사용자가 테이블 헤더를 클릭하여 칼럼을 정렬할 수 있도록 되어 있는데, 이는 Mustang 이전에 Swing JTable 지원을 통해 가능하게 되었다. 이 기능을 필요로 하는 각 테이블에 일일이 수동으로 기능을 추가해 주어야만 하는 불편이 따랐지만 Mustang은 최소한의 노력으로 이 기능을 사용할 수 있도록 해준다. 필터링은 사용자 인터페이스에서 일반적으로 이용할 수 있는 또 다른 옵션으로서, 테이블 내에서 사용자가 제공하는 기준에 부합하는 행만을 디스플레이할 수 있게 해준다. Mustang을 이용하면 JTable 컨텐츠 필터링이 훨씬 용이해진다.

행 정렬하기

Mustang에서 행을 정렬하고 필터링하는 기준이 되는 것이 바로 추상 RowSorter 클래스로서, 이 RowSorter는 두 가지 매핑-JTable 내의 한 행을 기본 모델의 엘리먼트로, 그리고 다시 반대로-을 유지한다. 이는 하나의 행이 정렬과 필터링을 수행할 수 있게 해준다. 이 클래스는 TableModelListModel 모두에 적용될 만큼 포괄적이긴 하지만 TableRowSorter에만 JTable에 적용되는 Mustang 라이브러리가 제공된다.

가장 간단한 경우를 예로 들면, TableModelTableRowSorter 생성자에 패스한 다음 생성된 RowSorterJTablesetRowSorter() 메소드로 패스한다. 다음은 이런 방식을 보여주는 예제 프로그램 SortTable이다.
   import javax.swing.*;
   import javax.swing.table.*;
   import java.awt.*;

   public class SortTable {
     public static void main(String args[]) {
       Runnable runner = new Runnable() {
        public void run() {
           JFrame frame = new JFrame("Sorting JTable");
           frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           Object rows[][] = {
               {"AMZN", "Amazon", 41.28},
               {"EBAY", "eBay", 41.57},
               {"GOOG", "Google", 388.33},
               {"MSFT", "Microsoft", 26.56},
               {"NOK", "Nokia Corp", 17.13},
               {"ORCL", "Oracle Corp.", 12.52},
               {"SUNW", "Sun Microsystems", 3.86},
               {"TWX",  "Time Warner", 17.66},
               {"VOD",  "Vodafone Group", 26.02},
               {"YHOO", "Yahoo!", 37.69}
             };
           String columns[] = {"Symbol", "Name", "Price"};
           TableModel model =
               new DefaultTableModel(rows, columns) {
             public Class getColumnClass(int column) {
               Class returnValue;
               if ((column >= 0) && (column < getColumnCount())) {
                 returnValue = getValueAt(0, column).getClass();
               } else {
                 returnValue = Object.class;
               }
               return returnValue;
             }
           };

           JTable table = new JTable(model);
           RowSorter<TableModel> sorter =
             new TableRowSorter<TableModel>(model);
           table.setRowSorter(sorter);
           JScrollPane pane = new JScrollPane(table);
           frame.add(pane, BorderLayout.CENTER);
           frame.setSize(300, 150);
           frame.setVisible(true);
         }
       };
       EventQueue.invokeLater(runner);
     }
   } 
Sort Table 1

디스플레이된 테이블의 특정 칼럼을 클릭하고 칼럼의 내용이 재정리되는 것을 살펴본다.

Sort Table 2

커스텀 서브클래스를 생성하느니 차라리 DefaultTableModel을 이용하면 안 되느냐고 질문을 던질지도 모른다. 그 대답은, TableRowSorter가 칼럼 정렬 시 적용되는 일련의 규칙을 가진다는 것이다. 기본값으로, 테이블 내의 모든 칼럼은 Object 타입으로 간주된다. 따라서, toString()을 호출함으로써 정렬이 수행되는 것이다. DefaultTableModel의 기본값 getColumnClass() 비헤이비어를 오버라이드함으로써, RowSorterComparable을 구현하는 것으로 가정하고 해당 클래스의 규칙에 따라 정렬한다. 또한, setComparator(int column, Comparator comparator)를 호출하여 특정 칼럼을 위한 커스텀 Comparator를 설치할 수도 있다.

다음은 정렬과 관련이 있는 SortTable 프로그램 내의 세 가지 주요 라인이다.
           JTable table = new JTable(model);
           RowSorter<TableModel> sorter =
             new TableRowSorter<TableModel>(model);
           table.setRowSorter(sorter);
첫 번째 라인은 모델을 테이블에 연결시키고, 두 번째 라인은 모델에 특정 RowSorter를 생성한다. 세 번째 라인은 RowSorterJTable에 연결시킨다. 이로써 사용자는 칼럼 헤더를 클릭하여 해당 칼럼을 정렬할 수 있다. 같은 칼럼을 두 번 클릭하면 정렬 순서가 반대로 된다.

정렬 순서가 바뀔 때 각자의 액션을 추가하고 싶으면 RowSorterRowSorterListener를 첨부하면 된다. 인터페이스는 다음과 같은 하나의 메소드를 가진다.
   void sorterChanged(RowSorterEvent e)
이 메소드는 상태 바에서 텍스트를 업데이트하거나 몇 가지 추가 태스크를 수행할 수 있게 해준다. 이 액션에 대한 RowSorterEventRowSorter가 뷰 안팎의 행을 필터링한 경우, 정렬 전에 얼마나 많은 행이 존재했었는지 알아낼 수 있게 해준다.

테이블 행 필터링하기

RowFilterTableRowSorter에 연결시켜 테이블의 내용을 필터링하는 데 사용할 수 있다. 예를 들어, RowFilter를 이용하여 이름이 A 자로 시작하거나 주가가 $50를 넘는 행만 테이블에 디스플레이되도록 하는 경우가 그것이다. 추상 RowFilter 클래스의 경우 다음과 같이 필터링에 사용되는 하나의 메소드를 가진다.
   boolean include(RowFilter.Entry<? extends M,? extends I> entry)
RowSorter에 연결된 모델 내의 각 엔트리에 대해, 메소드는 지정된 엔트리가 모델의 현재 뷰에 표시되어야 할지 여부를 알려준다. 대개의 경우 여러분은 각자의 RowFilter 구현을 생성할 필요는 없으나, 대신 RowFilter는 필터 생성을 위한 여섯 개의 정적 메소드를 제공한다.
  • andFilter(Iterable<? extends RowFilter<? super M,? super I>> filters)
  • dateFilter(RowFilter.ComparisonType type, Date date, int... indices)
  • notFilter(RowFilter<M,I> filter)
  • numberFilter(RowFilter.ComparisonType type, Number number, int... indices)
  • orFilter(Iterable<? extends RowFilter<? super M,? super I>> filters)
  • regexFilter(String regex, int... indices)
인덱스의 인자(dateFilter, numberFilter, regexFilter)를 가지는 RowFilter 팩토리 메소드의 경우에는 모델에서 지정된 인덱스에 일치하는 일련의 칼럼만을 확인하고, 지정된 인덱스가 없으면 모든 칼럼에 대해 일치 여부를 확인한다.

dateFilter는 날짜의 일치 여부를 확인할 수 있게 해주고, numberFilter는 일치하는 수를 확인한다. notFilter는 다른 필터를 반전시키는 데 사용된다. 즉, 제공 필터에 포함되지 않는 엔트리를 포함한다는 말인데, 이 필터는 가령 2005년 12월 25일까지 완료되지 않은 엔트리를 찾는다든지 하는 일에 사용할 수 있다. andFilterorFilter는 다른 필터들을 논리적으로 결합하는 데 사용되고, regexFilter는 정규 표현식을 사용하여 필터링을 수행한다. 다음은 regexFilter를 이용하여 테이블의 내용을 필터링하는 프로그램 FilterTable이다.
   import javax.swing.*;
   import javax.swing.table.*;
   import java.awt.*;
   import java.awt.event.*;
   import java.util.regex.*;

   public class FilterTable {
     public static void main(String args[]) {
       Runnable runner = new Runnable() {
         public void run() {
           JFrame frame = new JFrame("Sorting JTable");
           frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           Object rows[][] = {
             {"AMZN", "Amazon", 41.28},
             {"EBAY", "eBay", 41.57},
             {"GOOG", "Google", 388.33},
             {"MSFT", "Microsoft", 26.56},
             {"NOK", "Nokia Corp", 17.13},
             {"ORCL", "Oracle Corp.", 12.52},
             {"SUNW", "Sun Microsystems", 3.86},
             {"TWX",  "Time Warner", 17.66},
             {"VOD",  "Vodafone Group", 26.02},
             {"YHOO", "Yahoo!", 37.69}
           };
           Object columns[] = {"Symbol", "Name", "Price"};
           TableModel model =
              new DefaultTableModel(rows, columns) {
             public Class getColumnClass(int column) {
               Class returnValue;
               if ((column >= 0) && (column < getColumnCount())) {
                 returnValue = getValueAt(0, column).getClass();
               } else {
                 returnValue = Object.class;
               }
               return returnValue;
             }
           };
           JTable table = new JTable(model);
           final TableRowSorter<TableModel> sorter =
                   new TableRowSorter<TableModel>(model);
           table.setRowSorter(sorter);
           JScrollPane pane = new JScrollPane(table);
           frame.add(pane, BorderLayout.CENTER);
           JPanel panel = new JPanel(new BorderLayout());
           JLabel label = new JLabel("Filter");
           panel.add(label, BorderLayout.WEST);
           final JTextField filterText =
               new JTextField("SUN");
           panel.add(filterText, BorderLayout.CENTER);
           frame.add(panel, BorderLayout.NORTH);
           JButton button = new JButton("Filter");
           button.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
               String text = filterText.getText();
               if (text.length() == 0) {
                 sorter.setRowFilter(null);
               } else {
                 try {
                   sorter.setRowFilter(
                       RowFilter.regexFilter(text));
                 } catch (PatternSyntaxException pse) {
                   System.err.println("Bad regex pattern");
                 }
               }
             }
           });
           frame.add(button, BorderLayout.SOUTH);
           frame.setSize(300, 250);
           frame.setVisible(true);
         }
       };
       EventQueue.invokeLater(runner);
     }
   }
디스플레이는 어딘가에 SUN이라는 문자가 포함된 모든 문자열에 대해 필터를 설정하는데, 이는 문자열 "SUN"’에 의해 명시된다. 일치 여부를 정확하게 검사하려면 문자열의 시작과 끝 각각에 '^'와 '$'의 문자를 이용한다.
Filter Table 1

사용자가 아래쪽의 "Filter" 버튼을 누르면 필터는 자체적으로 Matcher.find()를 사용하여 포함 여부를 검사한다.

Filter Table 2

테이블에 표시된 일련의 행을 변경하려면 필터 텍스트를 변경하고, 테이블 내의 모든 행을 보고 싶으면 필터 텍스트를 삭제한다.

마지막으로 빼놓을 수 없는 것은, 정렬이나 필터링 시 선택은 뷰의 관점에서 이루어진다는 점이다. 따라서, 기본 모델에 매핑할 필요가 있다면 convertRowIndexToModel() 메소드를 호출해야 한다. 마찬가지로, 모델에서 뷰로 전환할 경우에는 convertRowIndexToView()를 사용해야 한다.

RowSorter, TableRowSorter, RowFilter 등에 관한 자세한 내용은 각 클래스에 관한 javadoc을 참조하기 바란다.

RowSorter
TableRowSorter
RowFilter

"Java SE" 카테고리의 다른 글

Posted by 1010