|
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 TableAs 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 tableAs 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
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 columnTo 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 columnsUsers 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.java1. 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
Table ModelsIn 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 investmentThe 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.java1. 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 RecordsProbably 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 tableIn 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.java1. 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 FilterThe 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 tableHowever, 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,
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 filterThere 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
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
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
javax.swing.table.AbstractTableModel 1.2
javax.swing.JTable 1.2
Cell Rendering and EditingIn 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.
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 renderersFor 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.
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
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 EditingTo 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
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 editorFinally, 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 classesFinally, 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 EditorsRun 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 chooserThe 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:
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
javax.swing.table.TableModel 1.2
javax.swing.table.TableCellRenderer 1.2
javax.swing.table.TableColumnModel 1.2
javax.swing.table.TableColumn 1.2
javax.swing.DefaultCellEditor 1.2
javax.swing.CellEditor 1.2
javax.swing.table.TableCellEditor 1.2
Working with Rows and ColumnsIn 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 ColumnsThe 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)
of the JTable class. Selecting Rows, Columns, and CellsDepending 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 rowto 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 cellsNOTE
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 ColumnsThe 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 ModelThe 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:
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
javax.swing.table.TableColumn 1.2
javax.swing.ListSelectionModel 1.2
javax.swing.table.DefaultTableModel 1.2
|
|