Tables


The JTable component displays a two-dimensional grid of objects. Of course, tables are common in user interfaces. The Swing team has put a lot of effort into the table control. Tables are inherently complex, butperhaps more successfully than with other Swing classesthe JTable component hides much of that complexity. You can produce fully functional tables with rich behavior by writing a few lines of code. Of course, you can write more code and customize the display and behavior for your specific applications.

In this section, we explain how to make simple tables, how the user interacts with them, and how to make some of the most common adjustments. As with the other complex Swing controls, it is impossible to cover all aspects in complete detail. For more information, look in Graphic Java 2 by David Geary or Core Java Foundation Classes by Kim Topley.

A Simple Table

As with the tree control, a JTable does not store its own data but obtains its data from a table model. The JTable class has a constructor that wraps a two-dimensional array of objects into a default model. That is the strategy that we use in our first example. Later in this chapter, we turn to table models.

Figure 6-26 shows a typical table, describing properties of the planets of the solar system. (A planet is gaseous if it consists mostly of hydrogen and helium. You should take the "Color" entries with a grain of saltthat column was added because it will be useful in a later code example.)

Figure 6-26. A simple table


As you can see from the code in Example 6-9, the data of the table is stored as a two-dimensional array of Object values:

 Object[][] cells = {    { "Mercury", 2440.0, 0, false, Color.yellow },    { "Venus", 6052.0, 0, false, Color.yellow },    . . . } 

NOTE

Here, we take advantage of autoboxing. The entries in the second, third, and fourth column are automatically converted into objects of type Double, Integer, and Boolean.


The table simply invokes the toString method on each object to display it. That's why the colors show up as java.awt.Color[r=...,g=...,b=...].

You supply the column names in a separate array of strings:

 String[] columnNames = {  "Planet", "Radius", "Moons", "Gaseous", "Color" }; 

Then, you construct a table from the cell and column name arrays. Finally, add scroll bars in the usual way, by wrapping the table in a JScrollPane.

 JTable table = new JTable(cells, columnNames); JScrollPane pane = new JScrollPane(table); 

The resulting table already has surprisingly rich behavior. Resize the table vertically until the scroll bar shows up. Then, scroll the table. Note that the column headers don't scroll out of view!

Next, click on one of the column headers and drag it to the left or right. See how the entire column becomes detached (see Figure 6-27). You can drop it to a different location. This rearranges the columns in the view only. The data model is not affected.

Figure 6-27. Moving a column


To resize columns, simply place the cursor between two columns until the cursor shape changes to an arrow. Then, drag the column boundary to the desired place (see Figure 6-28).

Figure 6-28. Resizing columns


Users can select rows by clicking anywhere in a row. The selected rows are highlighted; you will see later how to get selection events. Users can also edit the table entries by clicking on a cell and typing into it. However, in this code example, the edits do not change the underlying data. In your programs, you should either make cells uneditable or handle cell editing events and update your model. We discuss those topics later in this section.

As of JDK 5.0, you can print a table with the print method:

 table.print(); 

A print dialog box appears, and the table is sent to the printer. We discuss custom printing options in Chapter 7.

Example 6-9. PlanetTable.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import javax.swing.*;   4. import javax.swing.table.*;   5.   6. /**   7.    This program demonstrates how to show a simple table   8. */   9. public class PlanetTable  10. {  11.    public static void main(String[] args)  12.    {  13.       JFrame frame = new PlanetTableFrame();  14.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  15.       frame.setVisible(true);  16.    }  17. }  18.  19. /**  20.    This frame contains a table of planet data.  21. */  22. class PlanetTableFrame extends JFrame  23. {  24.    public PlanetTableFrame()  25.    {  26.       setTitle("PlanetTable");  27.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  28.       final JTable table = new JTable(cells, columnNames);  29.       add(new JScrollPane(table), BorderLayout.CENTER);  30.       JButton printButton = new JButton("Print");  31.       printButton.addActionListener(new  32.          ActionListener()  33.          {  34.             public void actionPerformed(ActionEvent event)  35.             {  36.                try  37.                {  38.                   table.print();  39.                }  40.                catch (java.awt.print.PrinterException e)  41.                {  42.                   e.printStackTrace();  43.                }  44.             }  45.          });  46.       JPanel buttonPanel = new JPanel();  47.       buttonPanel.add(printButton);  48.       add(buttonPanel, BorderLayout.SOUTH);  49.    }  50.  51.    private Object[][] cells =  52.    {  53.       { "Mercury", 2440.0,  0, false, Color.yellow },  54.       { "Venus", 6052.0, 0, false, Color.yellow },  55.       { "Earth", 6378.0, 1, false, Color.blue },  56.       { "Mars", 3397.0, 2, false, Color.red },  57.       { "Jupiter", 71492.0, 16, true, Color.orange },  58.       { "Saturn", 60268.0, 18, true, Color.orange },  59.       { "Uranus", 25559.0, 17, true, Color.blue },  60.       { "Neptune", 24766.0, 8, true, Color.blue },  61.       { "Pluto", 1137.0, 1, false, Color.black }  62.    };  63.  64.    private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous", "Color" };  65.  66.    private static final int DEFAULT_WIDTH = 400;  67.    private static final int DEFAULT_HEIGHT = 200;  68. } 


 javax.swing.JTable 1.2 

  • JTable(Object[][] entries, Object[] columnNames)

    constructs a table with a default table model.

  • void print() 5.0

    displays a print dialog box and prints the table.

Table Models

In the preceding example, the table-rendered objects were stored in a two-dimensional array. However, you should generally not use that strategy in your own code. If you find yourself dumping data into an array to display it as a table, you should instead think about implementing your own table model.

Table models are particularly simple to implement because you can take advantage of the AbstractTableModel class that implements most of the required methods. You only need to supply three methods:

 public int getRowCount(); public int getColumnCount(); public Object getValueAt(int row, int column); 

There are many ways for implementing the getValueAt method. You can simply compute the answer. Or you can look up the value from a database or some other repository. Let us look at a couple of examples.

In the first example, we construct a table that simply shows some computed values, namely, the growth of an investment under different interest rate scenarios (see Figure 6-29).

Figure 6-29. Growth of an investment


The getValueAt method computes the appropriate value and formats it:

 public Object getValueAt(int r, int c) {    double rate = (c + minRate) / 100.0;    int nperiods = r;    double futureBalance = INITIAL_BALANCE * Math.pow(1 + rate, nperiods);    return String.format("%.2f", futureBalance); } 

The getrowCount and getColumnCount methods simply return the number of rows and columns.

 public int getRowCount() { return years; } public int getColumnCount() {  return maxRate - minRate + 1; } 

If you don't supply column names, the getColumnName method of the AbstractTableModel names the columns A, B, C, and so on. To change column names, override the getColumnName method. You will usually want to override that default behavior. In this example, we simply label each column with the interest rate.

 public String getColumnName(int c) { return (c + minRate) + "%"; } 

You can find the complete source code in Example 6-10.

Example 6-10. InvestmentTable.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.text.*;   4. import javax.swing.*;   5. import javax.swing.table.*;   6.   7. /**   8.    This program shows how to build a table from a table model.   9. */  10. public class InvestmentTable  11. {  12.    public static void main(String[] args)  13.    {  14.       JFrame frame = new InvestmentTableFrame();  15.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  16.       frame.setVisible(true);  17.    }  18. }  19.  20. /**  21.    This frame contains the investment table.  22. */  23. class InvestmentTableFrame extends JFrame  24. {  25.    public InvestmentTableFrame()  26.    {  27.       setTitle("InvestmentTable");  28.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  29.  30.       TableModel model = new InvestmentTableModel(30, 5, 10);  31.       JTable table = new JTable(model);  32.       add(new JScrollPane(table));  33.    }  34.  35.    private static final int DEFAULT_WIDTH = 600;  36.    private static final int DEFAULT_HEIGHT = 300;  37. }  38.  39. /**  40.    This table model computes the cell entries each time they  41.    are requested. The table contents shows the growth of  42.    an investment for a number of years under different interest  43.    rates.  44. */  45. class InvestmentTableModel extends AbstractTableModel  46. {  47.    /**  48.       Constructs an investment table model.  49.       @param y the number of years  50.       @param r1 the lowest interest rate to tabulate  51.       @param r2 the highest interest rate to tabulate  52.    */  53.    public InvestmentTableModel(int y, int r1, int r2)  54.    {  55.       years = y;  56.       minRate = r1;  57.       maxRate = r2;  58.    }  59.  60.    public int getRowCount() { return years; }  61.  62.    public int getColumnCount() { return maxRate - minRate + 1; }  63.  64.    public Object getValueAt(int r, int c)  65.    {  66.       double rate = (c + minRate) / 100.0;  67.       int nperiods = r;  68.       double futureBalance = INITIAL_BALANCE * Math.pow(1 + rate, nperiods);  69.       return String.format("%.2f", futureBalance);  70.    }  71.  72.    public String getColumnName(int c) { return (c + minRate) + "%"; }  73.  74.    private int years;  75.    private int minRate;  76.    private int maxRate;  77.  78.    private static double INITIAL_BALANCE = 100000.0;  79. } 

Displaying Database Records

Probably the most common information to be displayed in a table is a set of records from a database. If you use a professional development environment, it almost certainly includes convenient JavaBeans components (beans) for accessing database information. However, if you don't have database beans or if you are simply curious about what goes on under the hood, you will find the next example interesting. Figure 6-30 shows the outputthe result of a query for all rows in a database table.

Figure 6-30. Displaying a query result in a table


In the example program, we define a ResultSetTableModel that fetches data from the result set of a database query. (See Chapter 4 for more information on Java database access and result sets.)

You can obtain the column count and the column names from the ResultSetMetaData object:

 public String getColumnName(int c) {    try    {       return rsmd.getColumnName(c + 1);    }    catch (SQLException e)    {       . . .    } } public int getColumnCount() {    try    {       return rsmd.getColumnCount();    }    catch (SQLException e)    {       . . .    } } 

If the database supports scrolling cursors, then it is particularly easy to get a cell value: Just move the cursor to the requested row and fetch the column value.

 public Object getValueAt(int r, int c) {    try    {       ResultSet rs = getResultSet();       rs.absolute(r + 1);       return rs.getObject(c + 1);    }    catch (SQLException e)    {       e.printStackTrace();       return null;    } } 

It makes a lot of sense to use this data model instead of the DefaultTableModel. If you created an array of values, then you would duplicate the cache that the database driver is already managing.

If the database does not support scrolling cursors, our example program puts the data in a row set instead.

Example 6-11. ResultSetTable.java
   1. import com.sun.rowset.*;   2. import java.awt.*;   3. import java.awt.event.*;   4. import java.io.*;   5. import java.sql.*;   6. import java.util.*;   7. import javax.swing.*;   8. import javax.swing.table.*;   9. import javax.sql.rowset.*;  10.  11. /**  12.    This program shows how to display the result of a  13.    database query in a table.  14. */  15. public class ResultSetTable  16. {  17.    public static void main(String[] args)  18.    {  19.       JFrame frame = new ResultSetFrame();  20.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  21.       frame.setVisible(true);  22.    }  23. }  24.  25. /**  26.    This frame contains a combo box to select a database table  27.    and a table to show the data stored in the table  28. */  29. class ResultSetFrame extends JFrame  30. {  31.    public ResultSetFrame()  32.    {  33.       setTitle("ResultSet");  34.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  35.  36.       /* find all tables in the database and add them to  37.          a combo box  38.       */  39.  40.       tableNames = new JComboBox();  41.       tableNames.addActionListener(new  42.          ActionListener()  43.          {  44.             public void actionPerformed(ActionEvent event)  45.             {  46.                try  47.                {  48.                   if (scrollPane != null) remove(scrollPane);  49.                   String tableName = (String) tableNames.getSelectedItem();  50.                   if (rs != null) rs.close();  51.                   String query = "SELECT * FROM " + tableName;  52.                   rs = stat.executeQuery(query);  53.                   if (scrolling)  54.                      model = new ResultSetTableModel(rs);  55.                   else  56.                   {  57.                      CachedRowSet crs = new CachedRowSetImpl();  58.                      crs.populate(rs);  59.                      model = new ResultSetTableModel(crs);  60.                   }  61.  62.                   JTable table = new JTable(model);  63.                   scrollPane = new JScrollPane(table);  64.                   add(scrollPane, BorderLayout.CENTER);  65.                   validate();  66.                }  67.                catch (SQLException e)  68.                {  69.                   e.printStackTrace();  70.                }  71.             }  72.          });  73.       JPanel p = new JPanel();  74.       p.add(tableNames);  75.       add(p, BorderLayout.NORTH);  76.  77.       try  78.       {  79.          conn = getConnection();  80.          DatabaseMetaData meta = conn.getMetaData();  81.          if (meta.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE))  82.          {  83.             scrolling = true;  84.             stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,  85.                ResultSet.CONCUR_READ_ONLY);  86.          }  87.          else  88.          {  89.             stat = conn.createStatement();  90.             scrolling = false;  91.          }  92.          ResultSet tables = meta.getTables(null, null, null, new String[] { "TABLE" });  93.          while (tables.next())  94.             tableNames.addItem(tables.getString(3));  95.          tables.close();  96.       }  97.       catch (IOException e)  98.       {  99.          e.printStackTrace(); 100.       } 101.       catch (SQLException e) 102.       { 103.          e.printStackTrace(); 104.       } 105. 106.       addWindowListener(new 107.          WindowAdapter() 108.          { 109.             public void windowClosing(WindowEvent event) 110.             { 111.                try 112.                { 113.                   if (conn != null) conn.close(); 114.                } 115.                catch (SQLException e) 116.                { 117.                   e.printStackTrace(); 118.                } 119.             } 120.          }); 121.    } 122. 123.    /** 124.       Gets a connection from the properties specified in 125.       the file database.properties. 126.       @return the database connection 127.     */ 128.    public static Connection getConnection() 129.       throws SQLException, IOException 130.    { 131.       Properties props = new Properties(); 132.       FileInputStream in = new FileInputStream("database.properties"); 133.       props.load(in); 134.       in.close(); 135. 136.       String drivers = props.getProperty("jdbc.drivers"); 137.       if (drivers != null) System.setProperty("jdbc.drivers", drivers); 138.       String url = props.getProperty("jdbc.url"); 139.       String username = props.getProperty("jdbc.username"); 140.       String password = props.getProperty("jdbc.password"); 141. 142.       return DriverManager.getConnection(url, username, password); 143.    } 144. 145.    private JScrollPane scrollPane; 146.    private ResultSetTableModel model; 147.    private JComboBox tableNames; 148.    private ResultSet rs; 149.    private Connection conn; 150.    private Statement stat; 151.    private boolean scrolling; 152. 153.    private static final int DEFAULT_WIDTH = 400; 154.    private static final int DEFAULT_HEIGHT = 300; 155. } 156. 157. /** 158.    This class is the superclass for the scrolling and the 159.    caching result set table model. It stores the result set 160.    and its metadata. 161. */ 162. class ResultSetTableModel extends AbstractTableModel 163. { 164.    /** 165.       Constructs the table model. 166.       @param aResultSet the result set to display. 167.    */ 168.    public ResultSetTableModel(ResultSet aResultSet) 169.    { 170.       rs = aResultSet; 171.       try 172.       { 173.          rsmd = rs.getMetaData(); 174.       } 175.       catch (SQLException e) 176.       { 177.          e.printStackTrace(); 178.       } 179.    } 180. 181.    public String getColumnName(int c) 182.    { 183.       try 184.       { 185.          return rsmd.getColumnName(c + 1); 186.       } 187.       catch (SQLException e) 188.       { 189.          e.printStackTrace(); 190.          return ""; 191.       } 192.    } 193. 194.    public int getColumnCount() 195.    { 196.       try 197.       { 198.          return rsmd.getColumnCount(); 199.       } 200.       catch (SQLException e) 201.       { 202.          e.printStackTrace(); 203.          return 0; 204.       } 205.    } 206. 207.    public Object getValueAt(int r, int c) 208.    { 209.       try 210.       { 211.          rs.absolute(r + 1); 212.          return rs.getObject(c + 1); 213.       } 214.       catch(SQLException e) 215.       { 216.          e.printStackTrace(); 217.          return null; 218.       } 219.    } 220. 221.    public int getRowCount() 222.    { 223.       try 224.       { 225.          rs.last(); 226.          return rs.getRow(); 227.       } 228.       catch(SQLException e) 229.       { 230.          e.printStackTrace(); 231.          return 0; 232.       } 233.    } 234. 235.    private ResultSet rs; 236.    private ResultSetMetaData rsmd; 237. } 

A Sort Filter

The last two examples drove home the point that tables don't store the cell data; they get them from a model. The model need not store the data either. It can compute the cell values or fetch them from somewhere else.

In this section, we introduce another useful technique, a filter model that presents information from another table in a different form. In our example, we sort the rows in a table. Run the program in Example 6-12 and double-click on one of the column headers. You will see how the rows are rearranged so that the column entries are sorted (see Figure 6-31).

Figure 6-31. Sorting the rows of a table


However, we don't physically rearrange the rows in the data model. Instead, we use a filter model that keeps an array with the permuted row indexes.

The filter model stores a reference to the actual table model. When the JTable needs to look up a value, the filter model computes the actual row index and gets the value from the model. For example,


public Object getValueAt(int r, int c) { return model.getValueAt(actual row index, c); }

All other methods are simply passed on to the original model.

 public String getColumnName(int c) {  return model.getColumnName(c); } 

Figure 6-32 shows how the filter sits between the JTable object and the actual table model.

Figure 6-32. A table model filter


There are two complexities when you implement such a sort filter. First, you need to be notified when the user double-clicks on one of the column headers. We don't want to go into too much detail on this technical point. You can find the code in the addMouseListener method of the SortFilterModel in Example 6-12. Here is the idea behind the code. First, get the table header component and attach a mouse listener. When a double click is detected, you need to find out in which table column the mouse click fell. Then, you need to translate the table column to the model columnthey can be different if the user moved the table columns around. Once you know the model column, you can start sorting the table rows.

The second complexity lies in sorting the table rows. We don't want to physically rearrange the rows. What we want is a sequence of row indexes that tells us how we would rearrange them if they were being sorted. However, the sort algorithms in the Arrays and Collections classes don't tell us how they rearrange the elements. Of course, you could reimplement a sorting algorithm and keep track of the object rearrangements, but there is a much smarter way. The trick is to come up with custom objects and a custom comparison method so that the library sorting algorithm can be pressed into service.

We will sort objects of type Row. A Row object contains the index r of a row in the model. Compare two such objects as follows: Find the elements in the model and compare them. In other words, the compareTo method for Row objects computes


model.getValueAt(r1, c).compareTo(model.getValueAt(r2, c))

Here r1 and r2 are the row indexes of the Row objects, and c is the column whose elements should be sorted.

If the entries of a particular column aren't comparable, we simply compare their string representations. That way, the column with Color values can still be sorted. (The Color class does not implement the Comparable interface.)

We make the Row class into an inner class of the SortFilterModel because the compareTo method needs to access the current model and column. Here is the code:

 class SortFilterModel extends AbstractTableModel {    . . .    private class Row implements Comparable<Row>    {       public int index;       public int compareTo(Row other)       {          Object a = model.getValueAt(index, sortColumn);          Object b = model.getValueAt(other.index, sortColumn);          if (a instanceof Comparable)             return ((Comparable) a).compareTo(b);          else             return a.toString().compareTo(b.toString());       }    }    private TableModel model;    private int sortColumn;    private Row[] rows; } 

In the constructor, we build an array rows, initialized such that rows[i].index is set to i:

 public SortFilterModel(TableModel m) {    model = m;    rows = new Row[model.getRowCount()];    for (int i = 0; i < rows.length; i++)    {       rows[i] = new Row();       rows[i].index = i;    } } 

In the sort method, we invoke the Arrays.sort algorithm. It sorts the Row objects. Because the comparison criterion looks at the model elements in the appropriate column, the elements are arranged so that afterward row[0] contains the index of the smallest element in the column, row[1] contains the index of the next-smallest element, and so on.

When the array is sorted, we notify all table model listeners (in particular, the JTable) that the table contents have changed and must be redrawn.

 public void sort(int c) {    sortColumn = c;    Arrays.sort(rows);    fireTableDataChanged(); } 

Finally, we can show you the exact computation of the getValueAt method of the filter class. It simply translates a row index r to the model row index rows[r].index:

 public Object getValueAt(int r, int c) { return model.getValueAt(rows[r].index, c); } 

The sort model filter shows again the power of the model-view-controller pattern. Because the data and the display are separated, we are able to change the mapping between the two.

TIP

You can find a more elaborate version of a table sorter at http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#sorting. That implementation listens to the table model and updates the sorted view when the table model changes.


Example 6-12. TableSortTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.util.*;   4. import javax.swing.*;   5. import javax.swing.event.*;   6. import javax.swing.table.*;   7.   8. /**   9.    This program demonstrates how to sort a table column.  10.    Double-click on a table column's header to sort it.  11. */  12. public class TableSortTest  13. {  14.    public static void main(String[] args)  15.    {  16.       JFrame frame = new TableSortFrame();  17.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  18.       frame.setVisible(true);  19.    }  20. }  21.  22. /**  23.    This frame contains a table of planet data.  24. */  25. class TableSortFrame extends JFrame  26. {  27.    public TableSortFrame()  28.    {  29.       setTitle("TableSortTest");  30.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  31.  32.       // set up table model and interpose sorter  33.  34.       DefaultTableModel model = new DefaultTableModel(cells, columnNames);  35.       final SortFilterModel sorter = new SortFilterModel(model);  36.  37.       // show table  38.  39.       final JTable table = new JTable(sorter);  40.       add(new JScrollPane(table), BorderLayout.CENTER);  41.  42.       // set up double-click handler for column headers  43.  44.       table.getTableHeader().addMouseListener(new  45.          MouseAdapter()  46.          {  47.             public void mouseClicked(MouseEvent event)  48.             {  49.                // check for double click  50.                if (event.getClickCount() < 2) return;  51.  52.                // find column of click and  53.                int tableColumn = table.columnAtPoint(event.getPoint());  54.  55.                // translate to table model index and sort  56.                int modelColumn = table.convertColumnIndexToModel(tableColumn);  57.                sorter.sort(modelColumn);  58.             }  59.          });  60.    }  61.  62.    private Object[][] cells =  63.    {  64.       { "Mercury", 2440.0,  0, false, Color.yellow },  65.       { "Venus", 6052.0, 0, false, Color.yellow },  66.       { "Earth", 6378.0, 1, false, Color.blue },  67.       { "Mars", 3397.0, 2, false, Color.red },  68.       { "Jupiter", 71492.0, 16, true, Color.orange },  69.       { "Saturn", 60268.0, 18, true, Color.orange },  70.       { "Uranus", 25559.0, 17, true, Color.blue },  71.       { "Neptune", 24766.0, 8, true, Color.blue },  72.       { "Pluto", 1137.0, 1, false, Color.black }  73.    };  74.  75.    private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous", "Color" };  76.  77.    private static final int DEFAULT_WIDTH = 400;  78.    private static final int DEFAULT_HEIGHT = 200;  79. }  80.  81. /**  82.    This table model takes an existing table model and produces a new model that sorts  the rows  83.    so that the entries in a given column are sorted.  84. */  85. class SortFilterModel extends AbstractTableModel  86. {  87.    /**  88.       Constructs a sort filter model.  89.       @param m the table model whose rows should be sorted  90.    */  91.    public SortFilterModel(TableModel m)  92.    {  93.       model = m;  94.       rows = new Row[model.getRowCount()];  95.       for (int i = 0; i < rows.length; i++)  96.       {  97.          rows[i] = new Row();  98.          rows[i].index = i;  99.       } 100.    } 101. 102.    /** 103.       Sorts the rows. 104.       @param c the column that should become sorted 105.    */ 106.    public void sort(int c) 107.    { 108.       sortColumn = c; 109.       Arrays.sort(rows); 110.       fireTableDataChanged(); 111.    } 112. 113.    // Compute the moved row for the three methods that access model elements 114. 115.    public Object getValueAt(int r, int c) { return model.getValueAt(rows[r].index, c); } 116. 117.    public boolean isCellEditable(int r, int c) { return model.isCellEditable(rows[r] .index, c); } 118. 119.    public void setValueAt(Object aValue, int r, int c) 120.    { 121.       model.setValueAt(aValue, rows[r].index, c); 122.    } 123. 124.    // delegate all remaining methods to the model 125. 126.    public int getRowCount() { return model.getRowCount(); } 127.    public int getColumnCount() { return model.getColumnCount(); } 128.    public String getColumnName(int c) { return model.getColumnName(c); } 129.    public Class getColumnClass(int c) { return model.getColumnClass(c); } 130. 131.    /** 132.       This inner class holds the index of the model row 133.       Rows are compared by looking at the model row entries 134.       in the sort column. 135.    */ 136.    private class Row implements Comparable<Row> 137.    { 138.       public int index; 139.       public int compareTo(Row other) 140.       { 141.          Object a = model.getValueAt(index, sortColumn); 142.          Object b = model.getValueAt(other.index, sortColumn); 143.          if (a instanceof Comparable) 144.             return ((Comparable) a).compareTo(b); 145.          else 146.             return a.toString().compareTo(b.toString()); 147.       } 148.    } 149. 150.    private TableModel model; 151.    private int sortColumn; 152.    private Row[] rows; 153. } 


 javax.swing.table.TableModel 1.2 

  • int getRowCount()

  • int getColumnCount()

    get the number of rows and columns in the table model.

  • Object getValueAt(int row, int column)

    gets the value at the given row and column.

  • void setValueAt(Object newValue, int row, int column)

    sets a new value at the given row and column.

  • boolean isCellEditable(int row, int column)

    returns true if the cell at the given row and column is editable.

  • String getColumnName(int column)

    gets the column title.


 javax.swing.table.AbstractTableModel 1.2 

  • void fireTableDataChanged()

    notifies all table model listeners that the table data has changed.


 javax.swing.JTable 1.2 

  • JTableHeader getTableHeader()

    returns the table header component of this table.

  • int columnAtPoint(Point p)

    returns the number of the table column that falls under the pixel position p.

  • int convertColumnIndexToModel(int tableColumn)

    returns the model index of the column with the given index. This value is different from tableColumn if some of the table columns are moved or hidden.

Cell Rendering and Editing

In the next example, we again display our planet data, but this time, we want to give the table more information about the column types. If you define the method

 Class getColumnClass(int columnIndex) 

of your table model to return the class that describes the column type, then the JTable class picks an appropriate renderer for the class. Table 6-1 shows how the JTable class renders types by default.

Table 6-1. Default Rendering Actions

Type

Rendered As

Icon

Image

Boolean

Checkbox

Object

String


You can see the checkboxes and images in Figure 6-33. (Thanks to Jim Evins, http://www.snaught.com/JimsCoolIcons/Planets, for providing the planet images!)

Figure 6-33. A table with cell renderers


For other types, you can supply your own cell renderers. Table cell renderers are similar to the tree cell renderers that you saw earlier. They implement the TableCellRenderer interface, which has a single method

 Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,    boolean hasFocus, int row, int column) 

That method is called when the table needs to draw a cell. You return a component whose paint method is then invoked to fill the cell area.

To display a cell of type Color, you can simply return a panel with a background color you set to the color object stored in the cell. The color is passed as the value parameter.

[View full width]

class ColorTableCellRenderer extends JPanel implements TableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { setBackground((Color) value); if (hasFocus) setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); else setBorder(null); } }

As you can see, the renderer installs a border when the cell has focus. (We ask the UIManager for the correct border. To find the lookup key, we peeked into the source code of the DefaultTableCellRenderer class.)

Generally, you will also want to set the background color of the cell to indicate whether it is currently selected. We skip this step because it would interfere with the displayed color. The ListRenderingTest example on page 334 shows how to indicate the selection status in a renderer.

TIP

If your renderer simply draws a text string or an icon, you can extend the DefaultTableCellRenderer class. It takes care of rendering the focus and selection status for you.


You need to tell the table to use this renderer with all objects of type Color. The setDefaultRenderer method of the JTable class lets you establish this association. You supply a Class object and the renderer:

 table.setDefaultRenderer(Color.class, new ColorTableCellRenderer()); 

That renderer is now used for all objects of the given type.

Cell Editing

To enable cell editing, the table model must indicate which cells are editable by defining the isCellEditable method. Most commonly, you will want to make certain columns editable. In the example program, we allow editing in four columns.

 public boolean isCellEditable(int r, int c) {    return c == PLANET_COLUMN || c == MOONS_COLUMN || c == GASEOUS_COLUMN || c == COLOR_COLUMN; } private static final int PLANET_COLUMN = 0; private static final int MOONS_COLUMN = 2; private static final int GASEOUS_COLUMN = 3; private static final int COLOR_COLUMN = 4; 

NOTE

The AbstractTableModel defines the isCellEditable method to always return false. The DefaultTableModel overrides the method to always return TRue.


If you run the program in Example 6-13, note that you can click on the checkboxes in the Gaseous column and turn the check marks on and off. If you click on a cell in the Moons column, a combo box appears (see Figure 6-34). You will shortly see how to install such a combo box as a cell editor.

Figure 6-34. A cell editor


Finally, click on a cell in the first column. The cell gains focus. You can start typing and the cell contents change.

What you just saw in action are the three variations of the DefaultCellEditor class. A DefaultCellEditor can be constructed with a JTextField, a JCheckBox, or a JComboBox. The JTable class automatically installs a checkbox editor for Boolean cells and a text field editor for all editable cells that don't supply their own renderer. The text fields let the user edit the strings that result from applying toString to the return value of the getValueAt method of the table model.

When the edit is complete, the edited value is retrieved by calling the getCellEditorValue method of your editor. That method should return a value of the correct type (that is, the type returned by the getColumnType method of the model).

To get a combo box editor, you set a cell editor manuallythe JTable component has no idea what values might be appropriate for a particular type. For the Moons column, we wanted to enable the user to pick any value between 0 and 20. Here is the code for initializing the combo box.

 JComboBox moonCombo = new JComboBox(); for (int i = 0; i <= 20; i++)    moonCombo.addItem(i); 

To construct a DefaultCellEditor, supply the combo box in the constructor:

 TableCellEditor moonEditor = new DefaultCellEditor(moonCombo); 

Next, we need to install the editor. Unlike the color cell renderer, this editor does not depend on the object typewe don't necessarily want to use it for all objects of type Integer. Instead, we need to install it into a particular column.

The JTable class stores information about table columns in objects of type TableColumn. A TableColumnModel object manages the columns. (Figure 6-35 shows the relationships among the most important table classes.) If you don't want to insert or remove columns dynamically, you won't use the table column model much. However, to get a particular TableColumn object, you need to get the column model to ask it for the column object:

 TableColumnModel columnModel = table.getColumnModel() TableColumn moonColumn = columnModel.getColumn(PlanetTableModel.MOONS_COLUMN); 

Figure 6-35. Relationship between Table classes


Finally, you can install the cell editor:

 moonColumn.setCellEditor(moonEditor); 

If your cells are taller than the default, you also want to set the row height:

 table.setRowHeight(height); 

By default, all rows of the table have the same height. You can set the heights of individual rows with the call

 table.setRowHeight(row, height); 

The actual row height equals the row height that has been set with these methods, reduced by the row margin. The default row margin is 1, but you can change it with the call

 table.setRowMargin(margin); 

To display an icon in the header, set the header value:

 moonColumn.setHeaderValue(new ImageIcon("Moons.gif")); 

However, the table header isn't smart enough to choose an appropriate renderer for the header value. You have to install the renderer manually. For example, to show an image icon in a column header, call

 moonColumn.setHeaderRenderer(table.getDefaultRenderer(ImageIcon.class)); 

Custom Editors

Run the example program again and click on a color. A color chooser pops up to let you pick a new color for the planet. Select a color and click OK. The cell color is updated (see Figure 6-36).

Figure 6-36. Editing the cell color with a color chooser


The color cell editor is not a standard table cell editor but a custom implementation. To create a custom cell editor, you implement the TableCellEditor interface. That interface is a bit tedious, and as of JDK 1.3, an AbstractCellEditor class is provided to take care of the event handling details.

The getTableCellEditorComponent method of the TableCellEditor interface requests a component to render the cell. It is exactly the same as the getTableCellRendererComponent method of the TableCellRenderer interface, except that there is no focus parameter. Because the cell is being edited, it is presumed to have focus. The editor component temporarily replaces the renderer when the editing is in progress. In our example, we return a blank panel that is not colored. This is an indication to the user that the cell is currently being edited.

Next, you want to have your editor pop up when the user clicks on the cell.

The JTable class calls your editor with an event (such as a mouse click) to find out if that event is acceptable to initiate the editing process. The AbstractCellEditor class defines the method to accept all events.

 public boolean isCellEditable(EventObject anEvent) {    return true; } 

However, if you override this method to false, then the table would not go through the trouble of inserting the editor component.

Once the editor component is installed, the shouldSelectCell method is called, presumably with the same event. You should initiate editing in this method, for example, by popping up an external edit dialog box.

 public boolean shouldSelectCell(EventObject anEvent) {    colorDialog.setVisible(true);    return true; } 

If the user cancels the edit, the table calls the cancelCellEditing method. If the user has clicked on another table cell, the table calls the stopCellEditing method. In both cases, you should hide the dialog box. When your stopCellEditing method is called, the table would like to use the partially edited value. You should return TRue if the current value is valid. In the color chooser, any value is valid. But if you edit other data, you can ensure that only valid data is retrieved from the editor.

Also, you should call the superclass methods that take care of event firingotherwise, the editing won't be properly canceled.

 public void cancelCellEditing() {    colorDialog.setVisible(false);    super.cancelCellEditing(); } 

Finally, you need to supply a method that yields the value that the user supplied in the editing process:

 public Object getCellEditorValue() {    return colorChooser.getColor(); } 

To summarize, your custom editor should do the following:

  1. Extend the AbstractCellEditor class and implement the TableCellEditor interface.

  2. Define the getTableCellEditorComponent method to supply a component. This can either be a dummy component (if you pop up a dialog box) or a component for in-place editing such as a combo box or text field.

  3. Define the shouldSelectCell, stopCellEditing, and cancelCellEditing methods to handle the start, completion, and cancellation of the editing process. The stopCellEditing and cancelCellEditing should call the superclass methods to ensure that listeners are notified.

  4. Define the getCellEditorValue method to return the value that is the result of the editing process.

Finally, you indicate when the user is finished editing by calling the stopCellEditing and cancelCellEditing methods. When constructing the color dialog box, we install accept and cancel callbacks that fire these events.

 colorDialog = JColorChooser.createDialog(null, "Planet Color", false, colorChooser,    new       ActionListener() // OK button listener       {          public void actionPerformed(ActionEvent event)          {             stopCellEditing();          }       },    new       ActionListener() // Cancel button listener       {          public void actionPerformed(ActionEvent event)          {             cancelCellEditing();          }       }); 

Also, when the user closes the dialog box, editing should be canceled. This is achieved by installation of a window listener:

 colorDialog.addWindowListener(new    WindowAdapter()    {       public void windowClosing(WindowEvent event)       {          cancelCellEditing();       }    }); 

This completes the implementation of the custom editor.

You now know how to make a cell editable and how to install an editor. There is one remaining issuehow to update the model with the value that the user edited. When editing is complete, the JTable class calls the following method of the table model:

 void setValueAt(Object value, int r, int c) 

You need to override the method to store the new value. The value parameter is the object that was returned by the cell editor. If you implemented the cell editor, then you know the type of the object that you return from the getCellEditorValue method. In the case of the DefaultCellEditor, there are three possibilities for that value. It is a Boolean if the cell editor is a checkbox, a string if it is a text field. If the value comes from a combo box, then it is the object that the user selected.

If the value object does not have the appropriate type, you need to convert it. That happens most commonly when a number is edited in a text field. In our example, we populated the combo box with Integer objects so that no conversion is necessary.

Example 6-13. TableCellRenderTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.util.*;   4. import javax.swing.*;   5. import javax.swing.border.*;   6. import javax.swing.event.*;   7. import javax.swing.table.*;   8.   9. /**  10.    This program demonstrates cell rendering and editing  11.    in a table.  12. */  13. public class TableCellRenderTest  14. {  15.    public static void main(String[] args)  16.    {  17.       JFrame frame = new TableCellRenderFrame();  18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  19.       frame.setVisible(true);  20.    }  21. }  22.  23. /**  24.    This frame contains a table of planet data.  25. */  26. class TableCellRenderFrame extends JFrame  27. {  28.    public TableCellRenderFrame()  29.    {  30.       setTitle("TableCellRenderTest");  31.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  32.  33.       TableModel model = new PlanetTableModel();  34.       JTable table = new JTable(model);  35.       table.setRowSelectionAllowed(false);  36.  37.       // set up renderers and editors  38.  39.       table.setDefaultRenderer(Color.class, new ColorTableCellRenderer());  40.       table.setDefaultEditor(Color.class, new ColorTableCellEditor());  41.  42.       JComboBox moonCombo = new JComboBox();  43.       for (int i = 0; i <= 20; i++)  44.          moonCombo.addItem(i);  45.  46.       TableColumnModel columnModel = table.getColumnModel();  47.       TableColumn moonColumn = columnModel.getColumn(PlanetTableModel.MOONS_COLUMN);  48.       moonColumn.setCellEditor(new DefaultCellEditor(moonCombo));  49.       moonColumn.setHeaderRenderer(table.getDefaultRenderer(ImageIcon.class));  50.       moonColumn.setHeaderValue(new ImageIcon("Moons.gif"));  51.  52.       // show table  53.  54.       table.setRowHeight(100);  55.       add(new JScrollPane(table), BorderLayout.CENTER);  56.    }  57.  58.    private static final int DEFAULT_WIDTH = 600;  59.    private static final int DEFAULT_HEIGHT = 400;  60. }  61.  62. /**  63.    The planet table model specifies the values, rendering  64.    and editing properties for the planet data.  65. */  66. class PlanetTableModel extends AbstractTableModel  67. {  68.    public String getColumnName(int c) { return columnNames[c]; }  69.    public Class getColumnClass(int c) { return cells[0][c].getClass(); }  70.    public int getColumnCount() { return cells[0].length; }  71.    public int getRowCount() { return cells.length; }  72.    public Object getValueAt(int r, int c) { return cells[r][c]; }  73.    public void setValueAt(Object obj, int r, int c) { cells[r][c] = obj; }  74.    public boolean isCellEditable(int r, int c)  75.    {  76.       return c == PLANET_COLUMN || c == MOONS_COLUMN || c == GASEOUS_COLUMN || c ==  COLOR_COLUMN;  77.    }  78.  79.    public static final int PLANET_COLUMN = 0;  80.    public static final int MOONS_COLUMN = 2;  81.    public static final int GASEOUS_COLUMN = 3;  82.    public static final int COLOR_COLUMN = 4;  83.  84.    private Object[][] cells =  85.    {  86.       { "Mercury", 2440.0,  0, false, Color.yellow, new ImageIcon("Mercury.gif") },  87.       { "Venus", 6052.0, 0, false, Color.yellow, new ImageIcon("Venus.gif") },  88.       { "Earth", 6378.0, 1, false, Color.blue, new ImageIcon("Earth.gif") },  89.       { "Mars", 3397.0, 2, false, Color.red, new ImageIcon("Mars.gif") },  90.       { "Jupiter", 71492.0, 16, true, Color.orange, new ImageIcon("Jupiter.gif") },  91.       { "Saturn", 60268.0, 18, true, Color.orange, new ImageIcon("Saturn.gif") },  92.       { "Uranus", 25559.0, 17, true, Color.blue, new ImageIcon("Uranus.gif") },  93.       { "Neptune", 24766.0, 8, true, Color.blue, new ImageIcon("Neptune.gif") },  94.       { "Pluto", 1137.0, 1, false, Color.black, new ImageIcon("Pluto.gif") }  95.    };  96.  97.    private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous", "Color",  "Image" };  98. }  99. 100. /** 101.    This renderer renders a color value as a panel with the 102.    given color. 103. */ 104. class ColorTableCellRenderer extends JPanel implements TableCellRenderer 105. { 106.    public Component getTableCellRendererComponent(JTable table, Object value, boolean  isSelected, 107.       boolean hasFocus, int row, int column) 108.    { 109.       setBackground((Color) value); 110.       if (hasFocus) 111.          setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); 112.       else 113.          setBorder(null); 114.       return this; 115.    } 116. } 117. 118. /** 119.    This editor pops up a color dialog to edit a cell value 120. */ 121. class ColorTableCellEditor extends AbstractCellEditor implements TableCellEditor 122. { 123.    public ColorTableCellEditor() 124.    { 125.       panel = new JPanel(); 126.       // prepare color dialog 127. 128.       colorChooser = new JColorChooser(); 129.       colorDialog = JColorChooser.createDialog(null, "Planet Color", false, colorChooser, 130.          new 131.             ActionListener() // OK button listener 132.             { 133.                public void actionPerformed(ActionEvent event) { stopCellEditing(); } 134.             }, 135.          new 136.             ActionListener() // Cancel button listener 137.             { 138.                public void actionPerformed(ActionEvent event) { cancelCellEditing(); } 139.             }); 140.       colorDialog.addWindowListener(new 141.          WindowAdapter() 142.          { 143.             public void windowClosing(WindowEvent event) { cancelCellEditing(); } 144.          }); 145.    } 146. 147.    public Component getTableCellEditorComponent(JTable table, 148.       Object value, boolean isSelected, int row, int column) 149.    { 150.       // this is where we get the current Color value. We store it in the dialog in  case the user 151.       // starts editing 152.       colorChooser.setColor((Color) value); 153.       return panel; 154.    } 155. 156.    public boolean shouldSelectCell(EventObject anEvent) 157.    { 158.       // start editing 159.       colorDialog.setVisible(true); 160. 161.       // tell caller it is ok to select this cell 162.       return true; 163.    } 164. 165.    public void cancelCellEditing() 166.    { 167.       // editing is canceled--hide dialog 168.       colorDialog.setVisible(false); 169.       super.cancelCellEditing(); 170.    } 171. 172.    public boolean stopCellEditing() 173.    { 174.       // editing is complete--hide dialog 175.       colorDialog.setVisible(false); 176.       super.stopCellEditing(); 177. 178.       // tell caller is is ok to use color value 179.       return true; 180.    } 181. 182.    public Object getCellEditorValue() 183.    { 184.       return colorChooser.getColor(); 185.    } 186. 187.    private Color color; 188.    private JColorChooser colorChooser; 189.    private JDialog colorDialog; 190.    private JPanel panel; 191. } 


 javax.swing.JTable 1.2 

  • void setRowHeight(int height)

    sets the height of all rows of the table to height pixels.

  • void setRowHeight(int row, int height)

    sets the height of the given row of the table to height pixels.

  • void setRowMargin(int margin)

    sets the amount of empty space between cells in adjacent rows.

  • int getRowHeight()

    gets the default height of all rows of the table.

  • int getRowHeight(int row)

    gets the height of the given row of the table.

  • int getRowMargin()

    gets the amount of empty space between cells in adjacent rows.

  • Rectangle getCellRect(int row, int column, boolean includeSpacing)

    returns the bounding rectangle of a table cell.

    Parameters:

    row, column

    The row and column of the cell

     

    includeSpacing

    true if the space around the cell should be included


  • Color getSelectionBackground()

  • Color getSelectionForeground()

    return the background and foreground colors to use for selected cells.

  • TableCellRenderer getDefaultRenderer(Class<?> type)

    gets the default renderer for the given type.

  • TableCellEditor getDefaultEditor(Class<?> type)

    gets the default editor for the given type.


 javax.swing.table.TableModel 1.2 

  • Class getColumnClass(int columnIndex)

    gets the class for the values in this column. This information is used by the cell renderer and editor.


 javax.swing.table.TableCellRenderer 1.2 

  • Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column)

    returns a component whose paint method is invoked to render a table cell.

    Parameters:

    table

    The table containing the cell to be rendered

     

    value

    The cell to be rendered

     

    selected

    true if the cell is currently selected

     

    hasFocus

    true if the cell currently has focus

     

    row, column

    The row and column of the cell



 javax.swing.table.TableColumnModel 1.2 

  • TableColumn getColumn(int index)

    gets the table column object that describes the column with the given index.


 javax.swing.table.TableColumn 1.2 

  • void setCellEditor(TableCellEditor editor)

  • void setCellRenderer(TableCellRenderer renderer)

    set the cell editor or renderer for all cells in this column.

  • void setHeaderRenderer(TableCellRenderer renderer)

    sets the cell renderer for the header cell in this column.

  • void setHeaderValue(Object value)

    sets the value to be displayed for the header in this column.


 javax.swing.DefaultCellEditor 1.2 

  • DefaultCellEditor(JComboBox comboBox)

    constructs a cell editor that presents the combo box for selecting cell values.


 javax.swing.CellEditor 1.2 

  • boolean isCellEditable(EventObject event)

    returns true if the event is suitable for initiating the editing process for this cell.

  • boolean shouldSelectCell(EventObject anEvent)

    starts the editing process. Returns TRue if the edited cell should be selected. Normally, you want to return true, but you can return false if you don't want the editing process to change the cell selection.

  • void cancelCellEditing()

    cancels the editing process. You can abandon partial edits.

  • boolean stopCellEditing()

    stops the editing process, with the intent of using the result. Returns TRue if the edited value is in a proper state for retrieval.

  • Object getCellEditorValue()

    returns the edited result.

  • void addCellEditorListener(CellEditorListener l)

  • void removeCellEditorListener(CellEditorListener l)

    add and remove the obligatory cell editor listener.


 javax.swing.table.TableCellEditor 1.2 

  • Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row, int column)

    returns a component whose paint method renders a table cell.

    Parameters:

    table

    The table containing the cell to be rendered

     

    value

    The cell to be rendered

     

    selected

    TRue if the cell is currently selected

     

    row, column

    The row and column of the cell


Working with Rows and Columns

In this subsection, you will see how to manipulate the rows and columns in a table. As you read through this material, keep in mind that a Swing table is quite asymmetricthere are different operations that you can carry out on rows and columns. The table component was optimized to display rows of information with the same structure, such as the result of a database query, not an arbitrary two-dimensional grid of objects. You will see this asymmetry throughout this subsection.

Resizing Columns

The TableColumn class gives you control over the resizing behavior of columns. You can set the preferred, minimum, and maximum width with the methods

 void setPreferredWidth(int width) void setMinWidth(int width) void setMaxWidth(int width) 

This information is used by the table component to lay out the columns.

Use the method

 void setResizable(boolean resizable) 

to control whether the user is allowed to resize the column.

You can programmatically resize a column with the method

 void setWidth(int width) 

When a column is resized, the default is to leave the total size of the table unchanged. Of course, the width increase or decrease of the resized column must then be distributed over other columns. The default behavior is to change the size of all columns to the right of the resized column. That's a good default because it allows a user to adjust all columns to a desired width, moving from left to right.

You can set another behavior from Table 6-2 by using the method

 void setAutoResizeMode(int mode) 

Table 6-2. Resize Modes

Mode

Behavior

AUTO_RESIZE_OFF

Don't resize other columns; change the table size

AUTO_RESIZE_NEXT_COLUMN

Resize the next column only

AUTO_RESIZE_SUBSEQUENT_COLUMNS

Resize all subsequent columns equally; this is the default behavior

AUTO_RESIZE_LAST_COLUMN

Resize the last column only

AUTO_RESIZE_ALL_COLUMNS

Resize all columns in the table; this is not a good choice because it poses challenges to the user to adjust multiple columns to a desired size


of the JTable class.

Selecting Rows, Columns, and Cells

Depending on the selection mode, the user can select rows, columns, or individual cells in the table. By default, row selection is enabled. Clicking inside a cell selects the entire row (see Figure 6-37). Call

 table.setRowSelectionAllowed(false) 

Figure 6-37. Selecting a row


to disable row selection.

When row selection is enabled, you can control whether the user is allowed to select a single row, a contiguous set of rows, or any set of rows. You need to retrieve the selection model and use its setSelectionMode method:

 table.getSelectionModel().setSelectionMode(mode); 

Here, mode is one of the three values:

 ListSelectionModel.SINGLE_SELECTION ListSelectionModel.SINGLE_INTERVAL_SELECTION ListSelectionModel.MULTIPLE_INTERVAL_SELECTION 

Column selection is disabled by default. You turn it on with the call

 table.setColumnSelectionAllowed(true) 

Enabling both row and column selection is equivalent to enabling cell selection. The user then selects ranges of cells (see Figure 6-38). You can also enable that setting with the call

 table.setCellSelectionEnabled(true) 

Figure 6-38. Selecting a range of cells


NOTE

In early versions of the Swing toolkit, when you set both row and column selections, every mouse click selected a "+" shaped area consisting of both the row and the column containing the cursor.


You can find out which rows and columns are selected by calling the getSelectedRows and getSelectedColumns methods. Both return an int[] array of the indexes of the selected items.

You can run the program in Example 6-14 to watch cell selection in action. Enable row, column, or cell selection in the Selection menu and watch how the selection behavior changes.

Hiding and Displaying Columns

The removeColumn method of the JTable class removes a column from the table view. The column data is not actually removed from the modelit is just hidden from view. The removeColumn method takes a TableColumn argument. If you have the column number (for example, from a call to getSelectedColumns), you need to ask the table model for the actual table column object:

 TableColumnModel columnModel = table.getColumnModel(); TableColumn column = columnModel.getColumn(i); table.removeColumn(column); 

If you remember the column, you can later add it back in:

 table.addColumn(column); 

This method adds the column to the end. If you want it to appear elsewhere, you call the moveColumn method.

You can also add a new column that corresponds to a column index in the table model, by adding a new TableColumn object:

 table.addColumn(new TableColumn(modelColumnIndex)); 

You can have multiple table columns that view the same column of the model.

There are no JTable methods for hiding or showing rows. If you want to hide rows, you can create a filter model similar to the sort filter that you saw earlier.

Adding and Removing Rows in the Default Table Model

The DefaultTableModel class is a concrete class that implements the TableModel interface. It stores a two-dimensional grid of objects. If your data are already in a tabular arrangement, then there is no point in copying all the data into a DefaultTableModel, but the class is handy if you quickly need to make a table from a small data set. The DefaultTableModel class has methods for adding rows and columns, and for removing rows.

The addRow and addColumn methods add a row or column of new data. You supply an Object[] array or a vector that holds the new data. With the addColumn method, you also supply a name for the new column. These methods add the new data to the end of the grid. To insert a row in the middle, use the insertRow method. There is no method for inserting a column in the middle of the grid.

Conversely, the removeRow method removes a row from the model. There is no method for removing a column.

Because the JTable object registers itself as a table model listener, the model notifies the table when data are inserted or removed. At that time, the table refreshes the display.

The program in Example 6-14 shows both selection and editing at work. A default table model contains a simple data set (a multiplication table). The Edit menu contains these commands:

  • Hide all selected columns.

  • Show all columns that you've ever hidden.

  • Remove selected rows from the model.

  • Add a row of data to the end of the model.

This example concludes the discussion of Swing tables. Tables are conceptually a bit easier to grasp than trees because the underlying data modela grid of objectsis easy to visualize. However, under the hood, the table component is actually quite a bit more complex than the tree component. Column headers, resizable columns, and column-specific renderers and editors all add to the complexity. In this section, we focused on those topics that you are most likely to encounter in practice: displaying database information, sorting, and custom cell rendering and editing. If you have special advanced needs, we once again refer you to Core Java Foundation Classes by Kim Topley and Graphic Java 2 by David Geary.

Example 6-14. TableSelectionTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.util.*;   4. import java.text.*;   5. import javax.swing.*;   6. import javax.swing.table.*;   7.   8. /**   9.    This program demonstrates selection, addition, and removal of rows and columns.  10. */  11. public class TableSelectionTest  12. {  13.    public static void main(String[] args)  14.    {  15.       JFrame frame = new TableSelectionFrame();  16.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  17.       frame.setVisible(true);  18.    }  19. }  20.  21. /**  22.    This frame shows a multiplication table and has menus for setting the row/column /cell selection  23.    modes, and for adding and removing rows and columns.  24. */  25. class TableSelectionFrame extends JFrame  26. {  27.    public TableSelectionFrame()  28.    {  29.       setTitle("TableSelectionTest");  30.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  31.  32.       // set up multiplication table  33.  34.       model = new DefaultTableModel(10, 10);  35.  36.       for (int i = 0; i < model.getRowCount(); i++)  37.          for (int j = 0; j < model.getColumnCount(); j++)  38.             model.setValueAt((i + 1) * (j + 1), i, j);  39.  40.       table = new JTable(model);  41.  42.       add(new JScrollPane(table), "Center");  43.  44.       removedColumns = new ArrayList<TableColumn>();  45.  46.       // create menu  47.  48.       JMenuBar menuBar = new JMenuBar();  49.       setJMenuBar(menuBar);  50.  51.       JMenu selectionMenu = new JMenu("Selection");  52.       menuBar.add(selectionMenu);  53.  54.       final JCheckBoxMenuItem rowsItem = new JCheckBoxMenuItem("Rows");  55.       final JCheckBoxMenuItem columnsItem = new JCheckBoxMenuItem("Columns");  56.       final JCheckBoxMenuItem cellsItem = new JCheckBoxMenuItem("Cells");  57.  58.       rowsItem.setSelected(table.getRowSelectionAllowed());  59.       columnsItem.setSelected(table.getColumnSelectionAllowed());  60.       cellsItem.setSelected(table.getCellSelectionEnabled());  61.  62.       rowsItem.addActionListener(new  63.          ActionListener()  64.          {  65.             public void actionPerformed(ActionEvent event)  66.             {  67.                table.clearSelection();  68.                table.setRowSelectionAllowed(rowsItem.isSelected());  69.                cellsItem.setSelected(table.getCellSelectionEnabled());  70.             }  71.          });  72.       selectionMenu.add(rowsItem);  73.  74.       columnsItem.addActionListener(new  75.          ActionListener()  76.          {  77.             public void actionPerformed(ActionEvent event)  78.             {  79.                table.clearSelection();  80.                table.setColumnSelectionAllowed(columnsItem.isSelected());  81.                cellsItem.setSelected(table.getCellSelectionEnabled());  82.             }  83.          });  84.       selectionMenu.add(columnsItem);  85.  86.       cellsItem.addActionListener(new  87.          ActionListener()  88.          {  89.             public void actionPerformed(ActionEvent event)  90.             {  91.                table.clearSelection();  92.                table.setCellSelectionEnabled(cellsItem.isSelected());  93.                rowsItem.setSelected(table.getRowSelectionAllowed());  94.                columnsItem.setSelected(table.getColumnSelectionAllowed());  95.             }  96.          });  97.       selectionMenu.add(cellsItem);  98.  99.       JMenu tableMenu = new JMenu("Edit"); 100.       menuBar.add(tableMenu); 101. 102.       JMenuItem hideColumnsItem = new JMenuItem("Hide Columns"); 103.       hideColumnsItem.addActionListener(new 104.          ActionListener() 105.          { 106.             public void actionPerformed(ActionEvent event) 107.             { 108.                int[] selected = table.getSelectedColumns(); 109.                TableColumnModel columnModel = table.getColumnModel(); 110. 111.                // remove columns from view, starting at the last 112.                // index so that column numbers aren't affected 113. 114. 115.                for (int i = selected.length - 1; i >= 0; i--) 116.                { 117.                   TableColumn column = columnModel.getColumn(selected[i]); 118.                   table.removeColumn(column); 119. 120.                   // store removed columns for "show columns" command 121. 122.                   removedColumns.add(column); 123.                } 124.             } 125.          }); 126.       tableMenu.add(hideColumnsItem); 127. 128.       JMenuItem showColumnsItem = new JMenuItem("Show Columns"); 129.       showColumnsItem.addActionListener(new 130.          ActionListener() 131.          { 132.             public void actionPerformed(ActionEvent event) 133.             { 134.                // restore all removed columns 135.                for (TableColumn tc : removedColumns) 136.                   table.addColumn(tc); 137.                removedColumns.clear(); 138.             } 139.          }); 140.       tableMenu.add(showColumnsItem); 141. 142.       JMenuItem addRowItem = new JMenuItem("Add Row"); 143.       addRowItem.addActionListener(new 144.          ActionListener() 145.          { 146.             public void actionPerformed(ActionEvent event) 147.             { 148.                // add a new row to the multiplication table in 149.                // the model 150. 151.                Integer[] newCells = new Integer[model.getColumnCount()]; 152.                for (int i = 0; i < newCells.length; i++) 153.                   newCells[i] = (i + 1) * (model.getRowCount() + 1); 154.                model.addRow(newCells); 155.             } 156.          }); 157.       tableMenu.add(addRowItem); 158. 159.       JMenuItem removeRowsItem = new  JMenuItem("Remove Rows"); 160.       removeRowsItem.addActionListener(new 161.          ActionListener() 162.          { 163.             public void actionPerformed(ActionEvent event) 164.             { 165.                int[] selected = table.getSelectedRows(); 166. 167.                for (int i = selected.length - 1; i >= 0; i--) 168.                   model.removeRow(selected[i]); 169.             } 170.          }); 171.       tableMenu.add(removeRowsItem); 172. 173.       JMenuItem clearCellsItem = new  JMenuItem("Clear Cells"); 174.       clearCellsItem.addActionListener(new 175.          ActionListener() 176.          { 177.             public void actionPerformed(ActionEvent event) 178.             { 179.                for (int i = 0; i < table.getRowCount(); i++) 180.                   for (int j = 0; j < table.getColumnCount(); j++) 181.                      if (table.isCellSelected(i, j)) 182.                         table.setValueAt(0, i, j); 183.             } 184.          }); 185.       tableMenu.add(clearCellsItem); 186.    } 187. 188.    private DefaultTableModel model; 189.    private JTable table; 190.    private ArrayList<TableColumn> removedColumns; 191. 192.    private static final int DEFAULT_WIDTH = 400; 193.    private static final int DEFAULT_HEIGHT = 300; 194. } 


 javax.swing.JTable 1.2 

  • void setAutoResizeMode(int mode)

    sets the mode for automatic resizing of table columns.

    Parameters:

    mode

    One of AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS


  • ListSelectionModel getSelectionModel()

    returns the list selection model. You need that model to choose between row, column, and cell selection.

  • void setRowSelectionAllowed(boolean b)

    If b is true, then rows can be selected when the user clicks on cells.

  • void setColumnSelectionAllowed(boolean b)

    If b is TRue, then columns can be selected when the user clicks on cells.

  • void setCellSelectionEnabled(boolean b)

    If b is true, then individual cells are selected. This is equivalent to calling both setRowSelectionAllowed(b) and setColumnSelectionAllowed(b).

  • boolean getRowSelectionAllowed()

    returns TRue if row selection is allowed.

  • boolean getColumnSelectionAllowed()

    returns TRue if column selection is allowed.

  • boolean getCellSelectionEnabled()

    returns TRue if both row and column selection are allowed.

  • void clearSelection()

    unselects all selected rows and columns.

  • void addColumn(TableColumn column)

    adds a column to the table view.

  • void moveColumn(int from, int to)

    moves the column at table index from so that its index becomes to. Only the view is affected.

  • void removeColumn(TableColumn column)

    removes the given column from the view.


 javax.swing.table.TableColumn 1.2 

  • TableColumn(int modelColumnIndex)

    constructs a table column for viewing the model column with the given index.

  • void setPreferredWidth(int width)

  • void setMinWidth(int width)

  • void setMaxWidth(int width)

    set the preferred, minimum, and maximum width of this table column to width.

  • void setWidth(int width)

    sets the actual width of this column to width.

  • void setResizable(boolean b)

    If b is TRue, this column is resizable.


 javax.swing.ListSelectionModel 1.2 

  • void setSelectionMode(int mode)

    Parameters:

    mode

    One of SINGLE_SELECTION, SINGLE_INTERVAL_SELECTION, and MULTIPLE_INTERVAL_SELECTION



 javax.swing.table.DefaultTableModel 1.2 

  • void addRow(Object[] rowData)

  • void addColumn(Object columnName, Object[] columnData)

    add a row or column of data to the end of the table model.

  • void insertRow(int row, Object[] rowData)

    adds a row of data at index row.

  • void removeRow(int row)

    removes the given row from the model.

  • void moveRow(int start, int end, int to)

    moves all rows with indexes between start and end to a new location starting at to.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net