The final touch is to add a few methods to handle some common tasks associated with a JTable.
1 2 package com.vizitsolutions.identitytable; 3 4 import javax.swing.ActionMap; 5 import javax.swing.table.DefaultTableColumnModel; 6 import javax.swing.InputMap; 7 import javax.swing.JComponent; 8 import javax.swing.JScrollPane; 9 import javax.swing.JTable; 10 import javax.swing.JViewport; 11 import java.awt.event.KeyEvent; 12 import javax.swing.KeyStroke; 13 import javax.swing.ListSelectionModel; 14 import javax.swing.table.TableCellRenderer; 15 import javax.swing.table.TableColumn; 16 import javax.swing.table.TableColumnModel; 17 18 /** 19 * <p>$Id$</p> 20 * 21 * <p> 22 * This class maps a TableModel into two JTables, and places 23 * them into a JScrollPane. The first JTable contains n fixed 24 * columns, and represents what is commonly called a rowheader. 25 * This is placed in the rowheader area of the JScrollPane. These 26 * columns will not scroll horizontally. This is particularly 27 * useful when you have one or more columns that identify an object 28 * and several aditional columns that contain attributes of the 29 * object. Placing the identifying columns in these fixed columns 30 * ensures that they are always visible while the attributes of the 31 * object are scrolled into view. 32 * </p> 33 * <p> 34 * The second JTable contains the remaining columns and is placed in 35 * the main viewport and will scroll normally. 36 * </p> 37 * 38 * @author Alex Kluge 39 * @version $Revision$, $Date$ 40 */ 41 public class IdentityTable5 extends JScrollPane 42 { 43 // These names follow a similar pattern to those used in JTable UI delegate. 44 public static final String LEFT_ARROW_ACTION_NAME = "selectPreviousColumn"; 45 public static final String RIGHT_ARROW_ACTION_NAME = "selectNextColumn"; 46 public static final String SHIFT_TAB_ACTION_NAME = "selectPreviousColumnCell"; 47 public static final String TAB_ACTION_NAME = "selectNextColumnCell"; 48 49 private JTable mainTable; 50 private TableColumnModel mainColumnModel = new DefaultTableColumnModel(); 51 private JTable rowHeaderTable; 52 private TableColumnModel rowHeaderColumnModel = new DefaultTableColumnModel(); 53 54 /** 55 * Creates a new instance of IdentityTable 56 */ 57 public IdentityTable5(IdentityTableModel baseTableModel) 58 { 59 // Start out life as an empty scrollpane. 60 super(); 61 62 // Loop over each of the fixed columns, and create columns in the row 63 // header column model for them. 64 TableColumn column; 65 int columnCount = baseTableModel.getColumnCount(); 66 int currentColumn; 67 int identityColumnCount = baseTableModel.getIdentityColumnCount(); 68 69 for (currentColumn = 0; currentColumn<identityColumnCount; currentColumn++) 70 { 71 column = new TableColumn(currentColumn); 72 column.setHeaderValue(baseTableModel.getColumnName(currentColumn)); 73 74 rowHeaderColumnModel.addColumn(column); 75 } 76 77 // Now loop over the remaining columns, and add them to the main table's column model. 78 for (currentColumn = identityColumnCount; currentColumn<columnCount; currentColumn++) 79 { 80 column = new TableColumn(currentColumn); 81 column.setHeaderValue(baseTableModel.getColumnName(currentColumn)); 82 83 mainColumnModel.addColumn(column); 84 } 85 86 // Providing a column model here atomatically sets the 87 // autoCreateColumnsFromModel flag to false. 88 rowHeaderTable = new JTable(baseTableModel, rowHeaderColumnModel); 89 rowHeaderTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 90 91 mainTable = new JTable(baseTableModel, mainColumnModel); 92 mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 93 94 // Ensure that both tables have common selections. 95 // Save the row selection model for later. 96 ListSelectionModel rowSelectionModel = mainTable.getSelectionModel(); 97 rowHeaderTable.setSelectionModel(rowSelectionModel); 98 rowHeaderTable.setPreferredScrollableViewportSize(rowHeaderTable.getPreferredSize()); 99 100 // This puts the column headers in the upper left corner above the row 101 // header columns. This is the same thing that happens when a JTable configures 102 // the containing JScrollPane. 103 setCorner(JScrollPane.UPPER_LEFT_CORNER, rowHeaderTable.getTableHeader()); 104 105 JViewport rowHeaderViewPort = new JViewport(); 106 rowHeaderViewPort.setView(rowHeaderTable); 107 setRowHeader(rowHeaderViewPort); 108 109 // Listen for resize events on the row header, and rexize the main table as appropriate. 110 RowHeaderResizeListener rowHeaderResizeListener = new RowHeaderResizeListener(this, rowHeaderViewPort, 111 rowHeaderTable, mainTable); 112 rowHeaderTable.addComponentListener(rowHeaderResizeListener); 113 114 setViewportView(mainTable); 115 116 installKeybordActions(rowHeaderTable, mainTable, 117 rowSelectionModel, rowHeaderColumnModel.getSelectionModel(), 118 mainColumnModel.getSelectionModel()); 119 } 120 121 protected void installKeybordActions(JTable rowHeaderTable, final JTable mainTable, 122 ListSelectionModel rowSelectionModel, ListSelectionModel headerColumnSelectionModel, 123 ListSelectionModel mainColumnSelectionModel) 124 { 125 InputMap rowHeaderInputMap 126 = rowHeaderTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 127 ActionMap rowHeaderActionmap = rowHeaderTable.getActionMap(); 128 ActionMap mainActionMap = mainTable.getActionMap(); 129 130 131 // Tab action handler, shift forward one cell, with wrapping 132 TabKeyActionHandler tabKeyActionHandler 133 = new TabKeyActionHandler(rowHeaderTable, mainTable, 134 rowSelectionModel, headerColumnSelectionModel, 135 mainColumnSelectionModel, 1, 136 true); 137 // Put the action handler into the appropriate place for both the 138 // row header and the main table. 139 rowHeaderActionmap.put(TAB_ACTION_NAME, tabKeyActionHandler); 140 mainActionMap.put(TAB_ACTION_NAME, tabKeyActionHandler); 141 142 // Right arrow action handler, shift forward one cell, no wrapping. 143 tabKeyActionHandler 144 = new TabKeyActionHandler(rowHeaderTable, mainTable, 145 rowSelectionModel, headerColumnSelectionModel, 146 mainColumnSelectionModel, 1, 147 false); 148 149 rowHeaderActionmap.put(RIGHT_ARROW_ACTION_NAME, tabKeyActionHandler); 150 mainActionMap.put(RIGHT_ARROW_ACTION_NAME, tabKeyActionHandler); 151 152 // Shift-Tab action handler, shift backward one cell, with wrapping 153 tabKeyActionHandler 154 = new TabKeyActionHandler(rowHeaderTable, mainTable, 155 rowSelectionModel, headerColumnSelectionModel, 156 mainColumnSelectionModel, -1, 157 true); 158 159 rowHeaderActionmap.put(SHIFT_TAB_ACTION_NAME, tabKeyActionHandler); 160 mainActionMap.put(SHIFT_TAB_ACTION_NAME, tabKeyActionHandler); 161 162 // Left arrow action handler, shift backward one cell, no wrapping. 163 tabKeyActionHandler 164 = new TabKeyActionHandler(rowHeaderTable, mainTable, 165 rowSelectionModel, headerColumnSelectionModel, 166 mainColumnSelectionModel, -1, 167 false); 168 169 rowHeaderActionmap.put(LEFT_ARROW_ACTION_NAME, tabKeyActionHandler); 170 mainActionMap.put(LEFT_ARROW_ACTION_NAME, tabKeyActionHandler); 171 172 // map the row header page down to the main table's page down. 173 Object actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); 174 rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey))); 175 176 // and map the page up too 177 actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); 178 rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey))); 179 } 180 181 /** 182 * Return the index of the selected row. Both tables have the same selection 183 * model, so the index is consistent. 184 * @return An int giving the index of the selected row in the identity table. 185 */ 186 public int getSelectedRow() 187 { 188 return mainTable.getSelectedRow(); 189 } 190 191 /** 192 * Return the row header table. 193 * @return A Jtable containing the identity columns from the original table 194 * model. 195 */ 196 public JTable getRowHeaderTable() 197 { 198 return rowHeaderTable; 199 } 200 201 /** 202 * Fetch the table containing the horizontally scrolling columns. 203 * @return A JTable containing the columns from the initial table model 204 * other than the identity columns. 205 */ 206 public JTable getMainTable() 207 { 208 return mainTable; 209 } 210 211 /** 212 * Sets a default cell renderer for both the row header and the main tables. Be careful 213 * as it is also possible to obtain the tables individually and set different default 214 * renderers on each. 215 * 216 * @param columnClass A class, which is matched against the columntype. Columns whose types match 217 * the class will use the given renderer. 218 * @param renderer A TableCellRenderer used to render cells whose column type matches the given class. 219 */ 220 public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer) 221 { 222 mainTable.setDefaultRenderer(columnClass, renderer); 223 rowHeaderTable.setDefaultRenderer(columnClass, renderer); 224 } 225 226 /** 227 * Return the default cell renderer for the given class from the main table. Be careful 228 * as it is also possible to obtain the tables individually and set different default 229 * renderers on each. 230 */ 231 public TableCellRenderer getDefaultRenderer(Class columnClass) 232 { 233 return mainTable.getDefaultRenderer(columnClass); 234 } 235 236 /** 237 * 238 * @return A Table model containing the column model for the main table. 239 */ 240 public TableColumnModel getMainColumnModel() 241 { 242 return mainColumnModel; 243 } 244 245 /** 246 * 247 * @return A TableColumnModel containing the column model for the row header. 248 */ 249 public TableColumnModel getRowHeaderColumnModel() 250 { 251 return rowHeaderColumnModel; 252 } 253 } 254 255