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:
Currently searching for the above requirements returns a lot of solutions of which none really solves the problem:
http://www.javarichclient.com/multiline-column-header/
Creating multi-line header for JTable
Java JTable header word wrap
The above solutions all propose using HTML code, for instance:
String[] columnNames = {
"<html><center>Closing<br>Date</html>",
"<html><center>Open<br>Price</html>",
"<html>Third<br>column</html>"
};
That solution is not elegant for a couple of reasons, mainly because in the case of variable columns names I need to pass the string to a function which strips spaces and subtitutes them with <br>
symbols, however if the column text contains very short text that appears in a line of its own.
I would need to decide a minimum and a maximum length of a column and then be able to make text centering possible, the above solution quickly becomes overengineered and unmanageable.
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm
Above solutions require manually creating a header array with words already correctly split up as in:
public static Object[][] tableHeaders = new Object[][] {
new String[] { "Currency" },
new String[] { "Yesterday's", "Rate" },
new String[] { "Today's", "Rate" },
new String[] { "Rate", "Change" } };
-or-
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(
new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });
Still not elegant because variable text in the column names would not be feasible.
How to change JTable header height?
Manually setting the header height as in the above solutions is only half of what I want to do, because then text would still not correctly wrap and deciding the height is still not feasible.
Currently all I was able was to create a custom TableCellRenderer but yet no solution:
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;
/**
* @version 1.0 11/09/98
*/
public class MultiLineHeaderExample extends JFrame
{
MultiLineHeaderExample()
{
super("Multi-Line Header Example");
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]
{
{
"a", "b", "c"
},
{
"A", "B", "C"
}
},
new Object[]
{
"My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
});
JTable table = new JTable(dm);
MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
Enumeration enumK = table.getColumnModel().getColumns();
while (enumK.hasMoreElements())
{
((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
getContentPane().add(scroll);
setSize(400, 110);
setVisible(true);
}
public static void main(String[] args)
{
MultiLineHeaderExample frame = new MultiLineHeaderExample();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{
public MultiLineHeaderRenderer()
{
ListCellRenderer renderer = getCellRenderer();
((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
setCellRenderer(renderer);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
BufferedReader br = new BufferedReader(new StringReader(str));
String line;
Vector v = new Vector();
try
{
while ((line = br.readLine()) != null)
{
v.addElement(line);
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
setListData(v);
return this;
}
}
This here also uses JTextArea
and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);
class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
public MultiLineTableHeaderRenderer() {
setEditable(false);
setLineWrap(true);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
LookAndFeel.installBorder(this, "TableHeader.cellBorder");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int width = table.getColumnModel().getColumn(column).getWidth();
setText((String)value);
setSize(width, getPreferredSize().height);
return this;
}
}
you need a Conponent that is able to wordwrap its content like JTextArea. I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.
class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
public MultiLineHeaderRenderer()
{
setAlignmentY(JLabel.CENTER);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLACK),
BorderFactory.createEmptyBorder(3,3,3,3)
));
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
setText(str);
int columnWidth= getColumnWidth();
setRows(str.length()/columnWidth);
return this;
}
}
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 <html>
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 TableColumn
s 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<String> wrappedColumnNames = new ArrayList<String>();
private int numRows;
DemoTableModel(List<String> columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("<html>" + name + "</html>");
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();});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With