You may notice that we do not specify the names of the actions this time, we look them up in the InputMap. This is another, more robust solution. As this is a training example, both methods are shown.
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.TableColumn; 15 import javax.swing.table.TableColumnModel; 16 17 /** 18 * <p>$Id$</p> 19 * 20 * <p> 21 * This class maps a TableModel into two JTables, and places 22 * them into a JScrollPane. The first JTable contains n fixed 23 * columns, and represents what is commonly called a rowheader. 24 * This is placed in the rowheader area of the JScrollPane. These 25 * columns will not scroll horizontally. This is particularly 26 * useful when you have one or more columns that identify an object 27 * and several additional columns that contain attributes of the 28 * object. Placing the identifying columns in these fixed columns 29 * ensures that they are always visible while the attributes of the 30 * object are scrolled into view. 31 * </p> 32 * <p> 33 * The second JTable contains the remaining columns and is placed in 34 * the main viewport and will scroll normally. 35 * </p> 36 * 37 * @author Alex Kluge 38 * @version $Revision$, $Date$ 39 */ 40 public class IdentityTable5 extends JScrollPane 41 { 42 // These names follow a similar pattern to those used in JTable UI delegate. 43 public static final String LEFT_ARROW_ACTION_NAME = "selectPreviousColumn"; 44 public static final String RIGHT_ARROW_ACTION_NAME = "selectNextColumn"; 45 public static final String SHIFT_TAB_ACTION_NAME = "selectPreviousColumnCell"; 46 public static final String TAB_ACTION_NAME = "selectNextColumnCell"; 47 48 private JTable mainTable; 49 private TableColumnModel mainColumnModel = new DefaultTableColumnModel(); 50 private JTable rowHeaderTable; 51 private TableColumnModel rowHeaderColumnModel = new DefaultTableColumnModel(); 52 53 /** 54 * Creates a new instance of IdentityTable 55 */ 56 public IdentityTable5(IdentityTableModel baseTableModel) 57 { 58 // Start out life as an empty scrollpane. 59 super(); 60 61 // Loop over each of the fixed columns, and create columns in the row 62 // header column model for them. 63 TableColumn column; 64 int columnCount = baseTableModel.getColumnCount(); 65 int currentColumn; 66 int identityColumnCount = baseTableModel.getIdentityColumnCount(); 67 68 for (currentColumn = 0; currentColumn<identityColumnCount; currentColumn++) 69 { 70 column = new TableColumn(currentColumn); 71 column.setHeaderValue(baseTableModel.getColumnName(currentColumn)); 72 73 rowHeaderColumnModel.addColumn(column); 74 } 75 76 // Now loop over the remaining columns, and add them to the main table's column model. 77 for (currentColumn = identityColumnCount; currentColumn<columnCount; currentColumn++) 78 { 79 column = new TableColumn(currentColumn); 80 column.setHeaderValue(baseTableModel.getColumnName(currentColumn)); 81 82 mainColumnModel.addColumn(column); 83 } 84 85 // Providing a column model here automatically sets the 86 // autoCreateColumnsFromModel flag to false. 87 rowHeaderTable = new JTable(baseTableModel, rowHeaderColumnModel); 88 rowHeaderTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 89 90 mainTable = new JTable(baseTableModel, mainColumnModel); 91 mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 92 93 // Ensure that both tables have common selections. 94 // Save the row selection model for later. 95 ListSelectionModel rowSelectionModel = mainTable.getSelectionModel(); 96 rowHeaderTable.setSelectionModel(rowSelectionModel); 97 rowHeaderTable.setPreferredScrollableViewportSize(rowHeaderTable.getPreferredSize()); 98 99 // This puts the column headers in the upper left corner above the row 100 // header columns. This is the same thing that happens when a JTable configures 101 // the containing JScrollPane. 102 setCorner(JScrollPane.UPPER_LEFT_CORNER, rowHeaderTable.getTableHeader()); 103 104 JViewport rowHeaderViewPort = new JViewport(); 105 rowHeaderViewPort.setView(rowHeaderTable); 106 setRowHeader(rowHeaderViewPort); 107 108 // Listen for resize events on the row header, and resize the main table as appropriate. 109 RowHeaderResizeListener rowHeaderResizeListener = new RowHeaderResizeListener(this, rowHeaderViewPort, 110 rowHeaderTable, mainTable); 111 rowHeaderTable.addComponentListener(rowHeaderResizeListener); 112 113 setViewportView(mainTable); 114 115 installKeybordActions(rowHeaderTable, mainTable, 116 rowSelectionModel, rowHeaderColumnModel.getSelectionModel(), 117 mainColumnModel.getSelectionModel()); 118 } 119 120 protected void installKeybordActions(JTable rowHeaderTable, final JTable mainTable, 121 ListSelectionModel rowSelectionModel, ListSelectionModel headerColumnSelectionModel, 122 ListSelectionModel mainColumnSelectionModel) 123 { 124 InputMap rowHeaderInputMap 125 = rowHeaderTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 126 ActionMap rowHeaderActionmap = rowHeaderTable.getActionMap(); 127 ActionMap mainActionMap = mainTable.getActionMap(); 128 129 130 // Tab action handler, shift forward one cell, with wrapping 131 TabKeyActionHandler tabKeyActionHandler 132 = new TabKeyActionHandler(rowHeaderTable, mainTable, 133 rowSelectionModel, headerColumnSelectionModel, 134 mainColumnSelectionModel, 1, 135 true); 136 // Put the action handler into the appropriate place for both the 137 // row header and the main table. 138 rowHeaderActionmap.put(TAB_ACTION_NAME, tabKeyActionHandler); 139 mainActionMap.put(TAB_ACTION_NAME, tabKeyActionHandler); 140 141 // Right arrow action handler, shift forward one cell, no wrapping. 142 tabKeyActionHandler 143 = new TabKeyActionHandler(rowHeaderTable, mainTable, 144 rowSelectionModel, headerColumnSelectionModel, 145 mainColumnSelectionModel, 1, 146 false); 147 148 rowHeaderActionmap.put(RIGHT_ARROW_ACTION_NAME, tabKeyActionHandler); 149 mainActionMap.put(RIGHT_ARROW_ACTION_NAME, tabKeyActionHandler); 150 151 // Shift-Tab action handler, shift backward one cell, with wrapping 152 tabKeyActionHandler 153 = new TabKeyActionHandler(rowHeaderTable, mainTable, 154 rowSelectionModel, headerColumnSelectionModel, 155 mainColumnSelectionModel, -1, 156 true); 157 158 rowHeaderActionmap.put(SHIFT_TAB_ACTION_NAME, tabKeyActionHandler); 159 mainActionMap.put(SHIFT_TAB_ACTION_NAME, tabKeyActionHandler); 160 161 // Left arrow action handler, shift backward one cell, no wrapping. 162 tabKeyActionHandler 163 = new TabKeyActionHandler(rowHeaderTable, mainTable, 164 rowSelectionModel, headerColumnSelectionModel, 165 mainColumnSelectionModel, -1, 166 false); 167 168 rowHeaderActionmap.put(LEFT_ARROW_ACTION_NAME, tabKeyActionHandler); 169 mainActionMap.put(LEFT_ARROW_ACTION_NAME, tabKeyActionHandler); 170 171 // map the row header page down to the main table's page down. 172 Object actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); 173 rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey))); 174 175 // and map the page up too 176 actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); 177 rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey))); 178 } 179 180 public TableColumnModel getMainColumnModel() 181 { 182 return mainColumnModel; 183 } 184 185 public TableColumnModel getRowHeaderColumnModel() 186 { 187 return rowHeaderColumnModel; 188 } 189 } 190 191