Home > Articles > Programming > Java

  • Print
  • + Share This
Like this article? We recommend

Table Sorting and Filtering

Swing’s table component has been enhanced in several new ways. One enhancement enables you to sort a table’s rows into ascending/descending order and filter out those rows (from the table model) to appear in the component’s view. To prevent confusion, remember that sorting and filtering affect the view, not the model.

Sorting and filtering are based on the concept of a row sorter object that sorts (and filters) rows. The simplest way to introduce a row sorter to a table component is to invoke javax.swing.JTable’s new public void setAutoCreateRowSorter(boolean autoCreateRowSorter) method, which I demonstrate below:

TableModel model = createTableModel ();
JTable table = new JTable (model);
table.setAutoCreateRowSorter (true);

Passing true to setAutoCreateRowSorter() causes JTable to install a new javax.swing.table.TableRowSorter<M> instance as the row sorter every time the model changes. To prevent new row sorters from being created during future model changes, pass false in a subsequent method call.

You’ll probably call setAutoCreateRowSorter() when you don’t plan to customize the row sorter. But you can still customize the row sorter after calling this method, by first calling JTable’s new public RowSorter<? extends TableModel> getRowSorter() method to return the current row sorter.

Because the compiler spits out an unchecked warning message when you attempt to store the returned row sorter’s reference in a TableRowSorter, you might prefer to create the table row sorter yourself and use JTable’s new public void setRowSorter(RowSorter<? extends TableModel> sorter) method to install it:

TableRowSorter<TableModel> sorter;
sorter = new TableRowSorter<TableModel> (model);
table.setRowSorter (sorter);

The TableRowSorter customizations include the capability to install a row filter object (that accepts rows based on certain criteria) via its public void setRowFilter(RowFilter<? super M,? super I> filter) method. This method takes a javax.swing.RowFilter<M,I> argument, whose methods return different kinds of row filters.

One kind of row filter uses regular expressions. To obtain this row filter, invoke the RowFilter public static <M,I> RowFilter<M,I> regexFilter(String regex, int... indices) method. For example, sorter.setRowFilter (RowFilter.regexFilter ("^A")); establishes a row filter whose ^A regular expression accepts only rows beginning with A.

JTable provides additional new methods related to row sorting and row filtering. Two of these methods are public int convertRowIndexToModel(int viewRowIndex) and public int convertRowIndexToView(int modelRowIndex), which map a row's index (in terms of the view) to the underlying model and map a row's index (in terms of the underlying model) to the view, respectively.

To prove to you that only the view is affected by sorting and filtering, I employ the former "convert" method in a TableSortFilterDemo demonstration application. Apart from passing null to the TableRowSorter’s public void setSortKeys(List<? extends RowSorter.SortKey> sortKeys) method to unsort the view after changing the row filter, there’s nothing in Listing 4 not already discussed.

Listing 4 TableSortFilterDemo.java

// TableSortFilterDemo.java

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

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;

class TableSortFilterDemo extends JFrame
{
   JLabel lblStatus;

   TableSortFilterDemo (String title)
   {
      // Assign title to the frame window's title bar.

      super (title);

      // Tell application to automatically exit when the user selects the Close
      // menu item from the frame window's system menu.

      setDefaultCloseOperation (EXIT_ON_CLOSE);

      // Create the table's model and a table based on this model. Also set
      // the selection mode to allow only a single row to be selected.

      TableModel model = createTableModel ();
      final JTable table = new JTable (model);
      table.setSelectionMode (ListSelectionModel.SINGLE_SELECTION);

      // Attach a list selection listener to the table's selection model, to
      // be notified whenever the user selects a row. Use this opportunity to
      // output the view and model indices on the status label.

      ListSelectionListener lsl;
      lsl = new ListSelectionListener ()
            {
                public void valueChanged (ListSelectionEvent lse)
                {
                   int index = table.getSelectedRow ();
                   if (index != -1)
                   {
                       String status;
                       status = "View index = " + index + ", Model index = ";
                       status += table.convertRowIndexToModel (index);
                       lblStatus.setText (status);
                   }
                }
            };
      table.getSelectionModel ().addListSelectionListener (lsl);

      // Create and install the table's row sorter.

      final TableRowSorter sorter;
      sorter = new TableRowSorter (model);
      table.setRowSorter (sorter);

      // Place the table in a JScrollPane object to allow the table to be
      // vertically scrolled and display scrollbars, as necessary.

      JScrollPane jsp = new JScrollPane (table);
      jsp.setPreferredSize (new Dimension (300, 100));

      // Fill northern region of GUI with scrollpane.

      getContentPane ().add (jsp, BorderLayout.NORTH);

      // Create and populate a panel for establishing row filter components.

      JPanel pnlFilter = new JPanel ();

      pnlFilter.add (new JLabel ("Filter expression:"));

      final JTextField txtFilter = new JTextField (20);
      pnlFilter.add (txtFilter);

      JButton btnSetFilter = new JButton ("Set Filter");
      ActionListener al;
      al = new ActionListener ()
           {
               public void actionPerformed (ActionEvent e)
               {
                  // Install a new row filter.

                  String expr = txtFilter.getText ();
                  sorter.setRowFilter (RowFilter.regexFilter (expr));

                  // Unsort the view.

                  sorter.setSortKeys (null);
               }
           };
      btnSetFilter.addActionListener (al);
      pnlFilter.add (btnSetFilter);

      // Fill center region of GUI with filter panel.

      getContentPane ().add (pnlFilter, BorderLayout.CENTER);

      // Create a status label for presenting the view and model indices for
      // the selected row.

      lblStatus = new JLabel (" ", JLabel.CENTER);

      // Fill southern region of GUI with status label.

      getContentPane ().add (lblStatus, BorderLayout.SOUTH);

      // Wrap an empty border around the GUI for aesthetic purposes.

      Border border = BorderFactory.createEmptyBorder (10, 10, 10, 10);
      getRootPane ().setBorder (border);

      // Resize the GUI to its preferred size.

      pack ();

      // Display GUI and start the AWT's event-dispatching thread.

      setVisible (true);
   }

   TableModel createTableModel ()
   {
      // Create a model consisting of 12 rows by 2 columns.

      DefaultTableModel model = new DefaultTableModel (12, 2);

      // Assign column identifiers (headers) to the columns.

      String [] columnTitles =
      {
         "Month",
         "Days",
      };

      model.setColumnIdentifiers (columnTitles);

      // Populate all cells in the model.

      String [] months =
      {
         "January",
         "February",
         "March",
         "April",
         "May",
         "June",
         "July",
         "August",
         "September",
         "October",
         "November",
         "December"
      };

      String [] days =
      {
         "31",
         "28 (29 on leap year)",
         "31",
         "30",
         "31",
         "30",
         "31",
         "31",
         "30",
         "31",
         "30",
         "31"
      };

      int nrows = model.getRowCount ();
      int ncols = model.getColumnCount ();

      for (int i = 0; i < nrows; i++)
      {
           model.setValueAt (months [i], i, 0);
           model.setValueAt (days [i], i, 1);
      }

      return model;
   }

   public static void main (String [] args)
   {
      Runnable r = new Runnable ()
                   {
                       public void run ()
                       {
                          new TableSortFilterDemo ("Table Sorting and "+
                                                   "Filtering Demo");
                       }
                   };
      EventQueue.invokeLater (r);
   }
}

After compiling and running this application, initiate a sort by clicking a column’s header. In response, all rows are rearranged into the ascending or descending order of the clicked column’s values (each click toggles between ascending/descending). The selected column and the sort order are indicated by an up/down arrow on the column’s header, as Figure 3 reveals.

Figure 3

Figure 3 An up arrow reflects an ascending order sort.

In addition to sorting, you can install a filter to determine which rows are shown in the view. Accomplish this task by typing a regular expression (such as ^J or J) into the text field and clicking the Set Filter button. In response, all rows matching the regexp will be displayed in unsorted order, which is shown in Figure 4. You can then sort these rows.

Figure 4

Figure 4 Clicking Set Filter establishes an unsorted view of filtered rows.

  • + Share This
  • 🔖 Save To Your Account