How can I obtain a multiline JTable header where the header column correctly enlarges to fit some text and then wraps to a new line?
Something like shown below:
Here is another approach. This solution has the following advantages:
It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.
The basic idea is that you wrap the column names in an tags, and, crucially, every TableColumn gets its own TableCellRenderer object.
I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.
Here is a quick-and-dirty proof-of-concept:
package scratch;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
@SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {
class DemoTableModel extends AbstractTableModel {
private ArrayList wrappedColumnNames = new ArrayList();
private int numRows;
DemoTableModel(List columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("" + name + "");
this.numRows = numRows;
}
public int getRowCount() {
return numRows;
}
public int getColumnCount() {
return wrappedColumnNames.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
}
public String getColumnName(int column) {
return wrappedColumnNames.get(column);
}
public Class> getColumnClass(int columnIndex) {
return Integer.class;
}
}
public WordWrappingTableHeaderDemo() {
DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
public void addColumn(TableColumn column) {
// This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
super.addColumn(column);
}
};
JTable table = new JTable();
table.setFillsViewportHeight(true);;
table.setColumnModel(tableColumnModel);
table.setModel(
new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
getContentPane().add(new JScrollPane(table));
}
public static void createAndShowGUI() {
WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationByPlatform(true);
app.pack();
app.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {createAndShowGUI();});
}
}