View Javadoc
1   package com.jsql.view.swing.table;
2   
3   import javax.swing.*;
4   import javax.swing.event.ChangeEvent;
5   import javax.swing.event.ChangeListener;
6   import javax.swing.table.DefaultTableModel;
7   import javax.swing.table.TableColumn;
8   import javax.swing.table.TableColumnModel;
9   import java.beans.PropertyChangeEvent;
10  import java.beans.PropertyChangeListener;
11  
12  /**
13   *  Prevent the specified number of columns from scrolling horizontally in
14   *  the scroll pane. The table must already exist in the scroll pane.
15   *
16   *  The functionality is accomplished by creating a second JTable (fixed)
17   *  that will share the TableModel and SelectionModel of the main table.
18   *  This table will be used as the row header of the scroll pane.
19   *
20   *  The fixed table created can be accessed by using the getFixedTable()
21   *  method. will be returned from this method. It will allow you to:
22   *
23   *  You can change the model of the main table and the change will be
24   *  reflected in the fixed model. However, you cannot change the structure
25   *  of the model.
26   */
27  public class FixedColumnTable implements ChangeListener, PropertyChangeListener {
28      
29      private JTable mainTable;
30      private JTable fixedTable;
31      private JScrollPane scrollPane;
32  
33      /**
34       *  Specify the number of columns to be fixed and the scroll pane
35       *  containing the table.
36       */
37      public void fixColumnSize(int fixedColumns, JScrollPane scrollPane) {
38          this.scrollPane = scrollPane;
39  
40          this.mainTable = (JTable) scrollPane.getViewport().getView();
41          this.mainTable.setAutoCreateColumnsFromModel(false);
42          this.mainTable.addPropertyChangeListener(this);
43  
44          //  Use the existing table to create a new table sharing
45          //  the DataModel and ListSelectionModel
46          this.fixedTable = new JTable() {
47              @Override
48              public boolean isCellEditable(int row,int column) {
49                  return false;
50              }
51          };
52          
53          this.fixedTable.setAutoCreateColumnsFromModel(false);
54          
55          final DefaultTableModel modelFixedTable = new DefaultTableModel() {
56              @Override
57              public int getColumnCount() {
58                  return 2;
59              }
60              @Override
61              public boolean isCellEditable(int row, int col) {
62                  return false;
63              }
64              @Override
65              public int getRowCount() {
66                  return FixedColumnTable.this.mainTable.getRowCount();
67              }
68              @Override
69              public Class<?> getColumnClass(int colNum) {
70                  Class<?> columnClass;
71                  if (colNum == 0) {
72                      columnClass = String.class;
73                  } else {
74                      columnClass = super.getColumnClass(colNum);
75                  }
76                  return columnClass;
77              }
78          };
79          
80          this.fixedTable.setModel(modelFixedTable);
81          this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
82          
83          this.fixedTable.setRowHeight(20);
84          this.fixedTable.setFocusable(false);
85          this.fixedTable.getTableHeader().setReorderingAllowed(false);
86          
87          //  Remove the fixed columns from the main table
88          //  and add them to the fixed table
89          TableColumnModel columnModel = this.mainTable.getColumnModel();
90          for (var i = 0 ; i < fixedColumns ; i++) {
91              TableColumn column = columnModel.getColumn(i);
92              column.setMinWidth(0);
93              column.setMaxWidth(0);
94              this.fixedTable.getColumnModel().addColumn(new TableColumn(i));
95          }
96  
97          this.fixedTable.getColumnModel().getColumn(0).setCellRenderer(new RowHeaderRenderer());
98          this.fixedTable.getColumnModel().getColumn(0).setResizable(false);
99          this.fixedTable.getColumnModel().getColumn(0).setPreferredWidth(38);
100         this.fixedTable.getColumnModel().getColumn(1).setCellRenderer(new RowHeaderRenderer());
101         this.fixedTable.getColumnModel().getColumn(1).setResizable(false);
102         this.fixedTable.getColumnModel().getColumn(1).setPreferredWidth(38);
103 
104         this.mainTable.getRowSorter().addRowSorterListener(rowSorterEvent -> {
105             modelFixedTable.fireTableDataChanged();
106             // Copy data from hidden column in main table
107             for (var i = 0; i < this.mainTable.getRowCount() ; i++) {
108                 this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 0), i, 0);
109                 this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 1), i, 1);
110             }
111         });
112         
113         this.mainTable.getSelectionModel().addListSelectionListener(listSelectionEvent ->
114             modelFixedTable.fireTableRowsUpdated(0, modelFixedTable.getRowCount() - 1)
115         );
116         
117         // Copy data from first column of main table to fixed column
118         for (var i = 0 ; i < this.mainTable.getRowCount() ; i++) {
119             this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 0), i, 0);
120             this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 1), i, 1);
121         }
122         
123         //  Add the fixed table to the scroll pane
124         this.fixedTable.setPreferredScrollableViewportSize(this.fixedTable.getPreferredSize());
125         scrollPane.setRowHeaderView(this.fixedTable);
126         scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, this.fixedTable.getTableHeader());
127 
128         // Synchronize scrolling of the row header with the main table
129         scrollPane.getRowHeader().addChangeListener(this);
130     }
131 
132     /**
133      * Implement the ChangeListener
134      */
135     @Override
136     public void stateChanged(ChangeEvent e) {
137         //  Sync the scroll pane scrollbar with the row header
138         JViewport viewport = (JViewport) e.getSource();
139         this.scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
140     }
141     
142     /**
143      * Implement the PropertyChangeListener
144      */
145     @Override
146     public void propertyChange(PropertyChangeEvent e) {
147         //  Keep the fixed table in sync with the main table
148         if ("selectionModel".equals(e.getPropertyName())) {
149             this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
150         }
151 
152         if ("model".equals(e.getPropertyName())) {
153             this.fixedTable.setModel(this.mainTable.getModel());
154         }
155     }
156 }