Sun Java Solaris Communities My SDN Account Join SDN
 
Swing Introduction

Swing

 

Bookshelf Index


Introduction | Download Chapter 18| Download Chapter 22
<< NEXT >>

18.5 Stocks Table: part IV - Sorting Columns

Note: This and the following StocksTable examples require Java 2 as they make use of the new java.util.Collections functionality.

In this section we add the ability to sort any column in ascending or descending order. The most suitable graphical element for selection of sort order are the column headers. We adopt the following model for our sorting functionality:

  • A single click on the header of a certain column causes the table to re-sort based on this column.


  • A repeated click on the same column changes the sort direction from ascending to descending and vice versa.


  • The header of the column which provides the current sorting should be marked.
  • To do this we add a mouse listener to the table header to capture mouse clicks and trigger a table sort. Fortunately sorting can be accomplished fairly easily using the new Collections functionality in Java 2.

    Note: Class java.util.Collections contains a set of static methods used to manipulate Java collections, including java.util.Vector which is used in this example.

    We use the java.util.Collections.sort(List lst, Comparator c) method to sort any collection implementing the java.util.List interface based on a given Comparator. A Comparator implementation requires two methods:

  • int compare(Object o1, Object o2):Compares two objects and returns the result as an int (zero if equal, negative value if the first is less than the second, positive value if the first is more than the second).


  • boolean equals(Object obj):Returns true if the given object is equal to this Comparator.

  • Click Figure 18.4
    for full-scale image. JTable with ascending and descending sorting of all columns.

    The Code: StocksTable.java

    see \Chapter18\4

    
    import java.awt.*;
    import java.awt.event.*;
    import java.util.*;
    import java.io.*;
    import java.text.*;
    
    import javax.swing.*;
    import javax.swing.border.*;
    import javax.swing.event.*;
    import javax.swing.table.*;
    
    public class StocksTable
                extends JFrame 
    {
      // Unchanged code from 
      //section 18.4
    
      public StocksTable() {
        // Unchanged code from 
        //section 18.4
    
        JTableHeader header = 
           m_table.getTableHeader();
        header.setUpdateTableInRealTime(
                                   true);
        header.addMouseListener(
              m_data.new ColumnListener(
                                m_table));
        header.setReorderingAllowed(true);
        
        // Unchanged code from section 18.4
      }
    
      public static void main(String argv[]) {
        new StocksTable();
      }
    }
    
    // Unchanged code from section 18.4
    
    class StockTableData 
         extends AbstractTableModel
    {
      // Unchanged code from 
      //section 18.2
    
      protected SimpleDateFormat m_frm;
      protected Vector m_vector;
      protected Date   m_date;
    
      protected int m_sortCol = 0;
      protected boolean m_sortAsc = true;
    
      public StockTableData() {
        m_frm = new SimpleDateFormat(
                        "MM/dd/yyyy");
        m_vector = new Vector();
        setDefaultData();
      }
    
      public void setDefaultData() {
        // Unchanged code from 
        //section 18.4
    
        Collections.sort(m_vector, new 
          StockComparator(m_sortCol, 
                          m_sortAsc));
      }
    
      // Unchanged code from section 18.4 
    
      public String getColumnName(
                         int column) {
        String str = 
              m_columns[column].m_title;
        if (column==m_sortCol)
          str += m_sortAsc ? " " : " ";
        return str;
      }
     
      // Unchanged code from section 18.4
    
      class ColumnListener 
                   extends MouseAdapter
      {
        protected JTable m_table;
    
        public ColumnListener(JTable table) {
          m_table = table;
        }
    
        public void mouseClicked(MouseEvent e) {
          TableColumnModel colModel = 
                        m_table.getColumnModel();
          int columnModelIndex = 
            colModel.getColumnIndexAtX(e.getX());
          int modelIndex = 
               colModel.getColumn(
                 columnModelIndex).getModelIndex();
    
          if (modelIndex < 0)
            return;
          if (m_sortCol==modelIndex)
            m_sortAsc = !m_sortAsc;
          else
            m_sortCol = modelIndex;
    
          for (int i=0; i < m_columns.length; 
                                       i++) {
            tablecolumn column = 
                        colmodel.getcolumn(i);
            column.setheadervalue(
                         getcolumnname(
                          column.getmodelindex()));    
          }
          m_table.gettableheader().repaint();  
    
          collections.sort(m_vector, new 
            stockcomparator(modelindex, m_sortasc));
          m_table.tablechanged(
            new tablemodelevent(
                             stocktabledata.this)); 
          m_table.repaint();  
        }
      }
    }
    
    class stockcomparator 
                  implements comparator
    {
      protected int     m_sortcol;
      protected boolean m_sortasc;
    
      public stockcomparator(int 
          sortcol, boolean sortasc) {
        m_sortcol = sortcol;
        m_sortasc = sortasc;
      }
    
      public int compare(object o1, 
                         object o2) {
        if(!(o1 instanceof stockdata) || 
                !(o2 instanceof stockdata))
          return 0;
        stockdata s1 = (stockdata)o1;
        stockdata s2 = (stockdata)o2;
        int result = 0;
        double d1, d2;
        switch (m_sortcol) {
          case 0:    // symbol
            string str1 = 
               (string)s1.m_symbol.m_data;
            string str2 = 
               (string)s2.m_symbol.m_data;
            result = str1.compareto(str2);
            break;
          case 1:    // name
            result = 
             s1.m_name.compareto(s2.m_name);
            break;
          case 2:    // last
            d1 = s1.m_last.doublevalue();
            d2 = s2.m_last.doublevalue();
            result = d1<d2 ? -1 : 
                      (d1>d2 ? 1 : 0);
            break;
          case 3:    // open
            d1 = s1.m_open.doublevalue();
            d2 = s2.m_open.doublevalue();
            result = d1<d2 ? -1 : (
                       d1>d2 ? 1 : 0);
            break;
          case 4:    // change
            d1 = ((fraction)
               s1.m_change.m_data).doublevalue();
            d2 = ((fraction)
               s2.m_change.m_data).doublevalue();
            result = d1<d2 ? 
                      -1 : (d1>d2 ? 1 : 0);
            break;
          case 5:    // change %
            d1 = ((double)s1.m_changepr.m_data)
                                 .doublevalue();
            d2 = ((double)s2.m_changepr.m_data)
                                 .doublevalue();
            result = d1<d2 ? -1 : (
                              d1>d2 ? 1 : 0);
            break;
          case 6:    // volume
            long l1 = s1.m_volume.longvalue();
            long l2 = s2.m_volume.longvalue();
            result = l1<l2 ? -1 : 
                          (l1>l2 ? 1 : 0);
            break;
        }
    
        if (!m_sortasc)
          result = -result;
        return result;
      }
    
      public boolean 
             equals(object obj) {
        if (obj instanceof 
                stockcomparator) {
          stockcomparator compobj = 
                (stockcomparator)obj;
          return (
           compobj.m_sortcol==m_sortcol) && 
            (compobj.m_sortasc==m_sortasc);
        }
        return false;
      }
    }
    
    

    Understanding the Code

    Class StocksTable

    In the StocksTable constructor we set the updateTableInRealTime property to show column contents while columns are dragged, and we add an instance of the ColumnListener class (see below) as a mouse listener to the table's header.

    Class StockTableData

    Here we declare two new instance variables: int m_sortCol to hold the index of the current column chosen for sorting, and boolean m_sortAsc, which is true when sorting in ascending order, and false when sorting in descending order. These variables determine the initial sorting order. To be consistent we sort our table initially by calling the Collections.sort() method in our setDefaultData() method (which is called from the StockTableData constructor).

    We also add a special marker for the sorting column's header: ' for ascending and ' for descending sorting. This changes the way we retrieve a column's name, which no longer is a constant value:

    public String getColumnName(int column) {
        String str = m_columns[column].m_title;
        if (column==m_sortCol)
          str += m_sortAsc ? "  " : " ";
        return str;
      }
    

    Class StockTableData.ColumnListener

    Because this class interacts heavily with our table data, it is implemented as an inner class in StockTableData. ColumnListener takes a reference to a JTable in its constructor and stores that reference in its m_table instance variable.

    The mouseClicked() method is invoked when the user clicks on a header. First it determines the index of the TableColumn clicked based on the coordinate of the click. If for any reason the returned index is negative (such as the column cannot be determined) the method cannot continue and we return. Otherwise, we check whether this index corresponds to the column which already has been selected for sorting. If so, we invert the m_sortCol flag to reverse the sorting order. If the index corresponds to newly selected column we store the new sorting index in the m_sortCol variable.

    Next, we refresh the header names by iterating through the TableColumns and assinging them a name corresponding to the column they represent in the TableModel. To do this we pass each TableColumn's modelIndex property to our getColumnName() method (see above). Finally our table data is re-sorted by calling the Collections.sort() method and passing in a new StockComparator object. We then refresh the table by calling tableChanged() and repaint().

    Class StockComparator

    This class implements the rule of comparison for two objects, which in our case are StockDatas. Instances of the StockComparator class are passed to the Collections.sort() method to perform data sorting.

    Two instance variables are defined:

  • int m_sortCol represents the index of the column which performs the comparison
  • boolean m_sortAsc is true for ascending sorting and false for descending.
  • The StockComparator constructor takes two parameters and stores them in these instance variables.

    The compare() method takes two objects to be compared and returns an integer value according to the rules determined in the Comparator interface:

  • 0 if object 1 equals 2
  • A positive number if object 1 is greater than object 2.
  • A negative number if object 1 is less than object 2.
  • Since we are dealing only with StockData objects, first we cast both objects and return 0 if this cast isn't possible. The next issue is to define what it means when one StockData objects is greater, equal, or less than another. This is done in a switch-case structure, which, depending on the index of the comparison column, extracts two fields and forms an integer result of the comparison. When the switch-case structure finishes, we know the result of an ascending comparison. For descending comparison we simply need to invert the sign of the result.

    The equals() method takes another Comparator instance as parameter and returns true if that parameter represents the same Comparator. We determine this by comparing Comparator instance variables: m_sortCol and m_sortAsc.

    Running the Code

    Figure 18.4 shows StocksTable sorted by decreasing Change %. Click different column headers and note that resorting occurs as expected. Click the same column header twice and note that sorting order flips from ascending to descending and vice versa. Also note that the currently selected sorting column header is marked by the or symbol. This sorting functionality is very useful. Particularly, for stock market data we can instantly determine which stocks have the highest price fluctuations or the most heavy trade.

    Sort by header selection idiom: Introducing table sorting using the column headers is introducing another design idiom to the User Interface. This design idiom is becoming widely accepted and widely used in many applications. It is a useful and powerful technique which you can introduce when sorting table data is a requirement. The technique is not intuitive and their is little visual affordance to suggest that clicking a column header will have any effect. So consider that the introduction of this technique may require additional User training.

    Page 1 2 3 4 5 6 7 8 9 10 11

    << NEXT >>
    Download Chapter 18| Download Chapter 22]