FixedColumnTable.java
package com.jsql.view.swing.table;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* Prevent the specified number of columns from scrolling horizontally in
* the scroll pane. The table must already exist in the scroll pane.
*
* The functionality is accomplished by creating a second JTable (fixed)
* that will share the TableModel and SelectionModel of the main table.
* This table will be used as the row header of the scroll pane.
*
* The fixed table created can be accessed by using the getFixedTable()
* method. will be returned from this method. It will allow you to:
*
* You can change the model of the main table and the change will be
* reflected in the fixed model. However, you cannot change the structure
* of the model.
*/
public class FixedColumnTable implements ChangeListener, PropertyChangeListener {
private JTable mainTable;
private JTable fixedTable;
private JScrollPane scrollPane;
/**
* Specify the number of columns to be fixed and the scroll pane
* containing the table.
*/
public void fixColumnSize(int fixedColumns, JScrollPane scrollPane) {
this.scrollPane = scrollPane;
this.mainTable = (JTable) scrollPane.getViewport().getView();
this.mainTable.setAutoCreateColumnsFromModel(false);
this.mainTable.addPropertyChangeListener(this);
// Use the existing table to create a new table sharing
// the DataModel and ListSelectionModel
this.fixedTable = new JTable() {
@Override
public boolean isCellEditable(int row,int column) {
return false;
}
};
this.fixedTable.setAutoCreateColumnsFromModel(false);
final DefaultTableModel modelFixedTable = new DefaultTableModel() {
@Override
public int getColumnCount() {
return 2;
}
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
@Override
public int getRowCount() {
return FixedColumnTable.this.mainTable.getRowCount();
}
@Override
public Class<?> getColumnClass(int colNum) {
Class<?> columnClass;
if (colNum == 0) {
columnClass = String.class;
} else {
columnClass = super.getColumnClass(colNum);
}
return columnClass;
}
};
this.fixedTable.setModel(modelFixedTable);
this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
this.fixedTable.setRowHeight(20);
this.fixedTable.setFocusable(false);
this.fixedTable.getTableHeader().setReorderingAllowed(false);
this.fixedTable.setGridColor(Color.LIGHT_GRAY);
this.fixedTable.getTableHeader().setDefaultRenderer(new RowHeaderRenderer() {
@Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column
) {
JComponent label = (JComponent) super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column
);
label.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 1, Color.LIGHT_GRAY));
return label;
}
});
// Remove the fixed columns from the main table
// and add them to the fixed table
TableColumnModel columnModel = this.mainTable.getColumnModel();
for (var i = 0 ; i < fixedColumns ; i++) {
TableColumn column = columnModel.getColumn(i);
column.setMinWidth(0);
column.setMaxWidth(0);
this.fixedTable.getColumnModel().addColumn(new TableColumn(i));
}
this.fixedTable.getColumnModel().getColumn(0).setCellRenderer(new RowHeaderRenderer());
this.fixedTable.getColumnModel().getColumn(0).setResizable(false);
this.fixedTable.getColumnModel().getColumn(0).setPreferredWidth(38);
this.fixedTable.getColumnModel().getColumn(1).setCellRenderer(new RowHeaderRenderer());
this.fixedTable.getColumnModel().getColumn(1).setResizable(false);
this.fixedTable.getColumnModel().getColumn(1).setPreferredWidth(38);
this.mainTable.getRowSorter().addRowSorterListener(rowSorterEvent -> {
modelFixedTable.fireTableDataChanged();
// Copy data from hidden column in main table
for (var i = 0 ; i < FixedColumnTable.this.mainTable.getRowCount() ; i++) {
FixedColumnTable.this.fixedTable.setValueAt(FixedColumnTable.this.mainTable.getValueAt(i, 0), i, 0);
FixedColumnTable.this.fixedTable.setValueAt(FixedColumnTable.this.mainTable.getValueAt(i, 1), i, 1);
}
});
this.mainTable.getSelectionModel().addListSelectionListener(listSelectionEvent ->
modelFixedTable.fireTableRowsUpdated(0, modelFixedTable.getRowCount() - 1)
);
// Copy data from first column of main table to fixed column
for (var i = 0 ; i < this.mainTable.getRowCount() ; i++) {
this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 0), i, 0);
this.fixedTable.setValueAt(this.mainTable.getValueAt(i, 1), i, 1);
}
// Add the fixed table to the scroll pane
this.fixedTable.setPreferredScrollableViewportSize(this.fixedTable.getPreferredSize());
scrollPane.setRowHeaderView(this.fixedTable);
scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, this.fixedTable.getTableHeader());
// Synchronize scrolling of the row header with the main table
scrollPane.getRowHeader().addChangeListener(this);
}
/**
* Implement the ChangeListener
*/
@Override
public void stateChanged(ChangeEvent e) {
// Sync the scroll pane scrollbar with the row header
JViewport viewport = (JViewport) e.getSource();
this.scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
}
/**
* Implement the PropertyChangeListener
*/
@Override
public void propertyChange(PropertyChangeEvent e) {
// Keep the fixed table in sync with the main table
if ("selectionModel".equals(e.getPropertyName())) {
this.fixedTable.setSelectionModel(this.mainTable.getSelectionModel());
}
if ("model".equals(e.getPropertyName())) {
this.fixedTable.setModel(this.mainTable.getModel());
}
}
}