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      
31      private JTable fixedTable;
32      
33      private JScrollPane scrollPane;
34  
35      /**
36       *  Specify the number of columns to be fixed and the scroll pane
37       *  containing the table.
38       */
39      public void fixColumnSize(int fixedColumns, JScrollPane scrollPane) {
40          this.scrollPane = scrollPane;
41  
42          this.mainTable = (JTable) scrollPane.getViewport().getView();
43          this.mainTable.setAutoCreateColumnsFromModel(false);
44          this.mainTable.addPropertyChangeListener(this);
45  
46          //  Use the existing table to create a new table sharing
47          //  the DataModel and ListSelectionModel
48          this.fixedTable = new JTable() {
49              @Override
50              public boolean isCellEditable(int row,int column) {
51                  return false;
52              }
53          };
54          
55          this.fixedTable.setAutoCreateColumnsFromModel(false);
56          
57          final DefaultTableModel modelFixedTable = new DefaultTableModel() {
58              @Override
59              public int getColumnCount() {
60                  return 2;
61              }
62              @Override
63              public boolean isCellEditable(int row, int col) {
64                  return false;
65              }
66              @Override
67              public int getRowCount() {
68                  return FixedColumnTable.this.mainTable.getRowCount();
69              }
70              @Override
71              public Class<?> getColumnClass(int colNum) {
72                  Class<?> columnClass;
73                  if (colNum == 0) {
74                      columnClass = String.class;
75                  } else {
76                      columnClass = super.getColumnClass(colNum);
77                  }
78                  return columnClass;
79              }
80          };
81          
82          this.fixedTable.setModel(modelFixedTable);
83          this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
84          
85          this.fixedTable.setRowHeight(20);
86          this.fixedTable.setFocusable(false);
87          this.fixedTable.getTableHeader().setReorderingAllowed(false);
88          
89          //  Remove the fixed columns from the main table
90          //  and add them to the fixed table
91          TableColumnModel columnModel = this.mainTable.getColumnModel();
92          for (var i = 0 ; i < fixedColumns ; i++) {
93              TableColumn column = columnModel.getColumn(i);
94              column.setMinWidth(0);
95              column.setMaxWidth(0);
96              this.fixedTable.getColumnModel().addColumn(new TableColumn(i));
97          }
98  
99          this.fixedTable.getColumnModel().getColumn(0).setCellRenderer(new RowHeaderRenderer());
100         this.fixedTable.getColumnModel().getColumn(0).setResizable(false);
101         this.fixedTable.getColumnModel().getColumn(0).setPreferredWidth(38);
102         this.fixedTable.getColumnModel().getColumn(1).setCellRenderer(new RowHeaderRenderer());
103         this.fixedTable.getColumnModel().getColumn(1).setResizable(false);
104         this.fixedTable.getColumnModel().getColumn(1).setPreferredWidth(38);
105 
106         this.mainTable.getRowSorter().addRowSorterListener(rowSorterEvent -> {
107             modelFixedTable.fireTableDataChanged();
108             // Copy data from hidden column in main table
109             for (var i = 0; i < this.mainTable.getRowCount() ; i++) {
110                 this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 0), i, 0);
111                 this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 1), i, 1);
112             }
113         });
114         
115         this.mainTable.getSelectionModel().addListSelectionListener(listSelectionEvent ->
116             modelFixedTable.fireTableRowsUpdated(0, modelFixedTable.getRowCount() - 1)
117         );
118         
119         // Copy data from first column of main table to fixed column
120         for (var i = 0 ; i < this.mainTable.getRowCount() ; i++) {
121             this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 0), i, 0);
122             this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 1), i, 1);
123         }
124         
125         //  Add the fixed table to the scroll pane
126         this.fixedTable.setPreferredScrollableViewportSize(this.fixedTable.getPreferredSize());
127         scrollPane.setRowHeaderView(this.fixedTable);
128         scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, this.fixedTable.getTableHeader());
129 
130         // Synchronize scrolling of the row header with the main table
131         scrollPane.getRowHeader().addChangeListener(this);
132     }
133 
134     /**
135      * Implement the ChangeListener
136      */
137     @Override
138     public void stateChanged(ChangeEvent e) {
139         //  Sync the scroll pane scrollbar with the row header
140         JViewport viewport = (JViewport) e.getSource();
141         this.scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
142     }
143     
144     /**
145      * Implement the PropertyChangeListener
146      */
147     @Override
148     public void propertyChange(PropertyChangeEvent e) {
149         //  Keep the fixed table in sync with the main table
150         if ("selectionModel".equals(e.getPropertyName())) {
151             this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
152         }
153 
154         if ("model".equals(e.getPropertyName())) {
155             this.fixedTable.setModel(this.mainTable.getModel());
156         }
157     }
158 }