Custom TableCellRenderer -- Won't display more than 1 line of text (SSCCE)

I wrote a simple custom table cell renderer for a subclass of JTable, the purpose of which is to enable two or more lines of text to appear in the table header. I use a comma in the header title to split the name into two or more Strings, and each String is put into a JLabel and each JLabel is put into the renderer component.

I actually have this working on a different application, but when I tried to do it for another app, I can't get it to work properly, and I've looked and looked and can't see the code difference from the app that works. The example below uses a BoxLayout. With this layout manager, the first line of text only shows up. When I tried using a BorderLayout, the two lines of the header name would over-write each other. IT seems that the header row can't expand its size, even though I try setting it explicitly to a sufficiently large size. THe header text is also not center-aligned even though I set this, which may be a clue as to what's happening.

Here's my SSCCE. Just execute the main method, and you'll see the table with the header rows as described above.

import java.awt.Component;

import java.awt.Dimension;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import java.util.Vector;

import javax.swing.Box;

import javax.swing.BoxLayout;

import javax.swing.JButton;

import javax.swing.JDialog;

import javax.swing.JFrame;

import javax.swing.JLabel;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTable;

import javax.swing.SwingConstants;

import javax.swing.UIManager;

import javax.swing.WindowConstants;

import javax.swing.table.DefaultTableModel;

import javax.swing.table.JTableHeader;

import javax.swing.table.TableCellRenderer;

import javax.swing.table.TableModel;

publicclass HeaderRendererTestimplements ActionListener{

privateint WIDTH = 800;

privateint HEIGHT = 600;

private JDialog dialog;

public HeaderRendererTest(){

JButton closeButton =new JButton("Close");

closeButton.addActionListener(this);

closeButton.setActionCommand("close");

dialog =new JDialog(new JFrame(),true);

JPanel panel =new JPanel();

RCBTable table =new RCBTable(new RCBTableModel());

JScrollPane scrollPane =new JScrollPane(table);

scrollPane.setMinimumSize(new Dimension(WIDTH, HEIGHT));

panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

panel.add(scrollPane);

JPanel buttonPanel =new JPanel();

buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));

buttonPanel.add(Box.createRigidArea(new Dimension(5,0)));

buttonPanel.add(closeButton);

buttonPanel.add(Box.createHorizontalGlue());

panel.add(Box.createRigidArea(new Dimension(0,5)));

panel.add(buttonPanel);

panel.add(Box.createRigidArea(new Dimension(0,5)));

dialog.add(panel);

dialog.setSize(WIDTH, HEIGHT);

populateFields();

dialog.setLocationRelativeTo(null);

dialog.setResizable(false);

dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

dialog.addWindowListener(new WindowAdapter(){

publicvoid windowClosing(WindowEvent e){

doCancel();

}

});

dialog.setVisible(true);

}

publicvoid actionPerformed(ActionEvent e){

if (e.getActionCommand().equals("close")){

dialog.dispose();

}

}

privatevoid doCancel(){

dialog.dispose();

}

privatevoid populateFields(){

}

/*** Custom table class ***/

privateclass RCBTableextends JTable{

public RCBTable(){

super();

}

public RCBTable(TableModel model){

super(model);

setRowHeight(22);

}

public JTableHeader createDefaultTableHeader(){

returnnew JTableHeader(this.getColumnModel()){

public TableCellRenderer getDefaultRenderer(){

returnnew RCBHeaderRenderer();

}

};

}

}

/*** Custom Header Renderer class ***/

privateclass RCBHeaderRendererextends JPanelimplements TableCellRenderer{

public RCBHeaderRenderer(){

super();

}

public Component getTableCellRendererComponent(

JTable table, Object value,

boolean isSelected,boolean hasFocus,

int row,int column){

JLabel label;

this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));

setOpaque(true);

removeAll();

this.setBorder(UIManager.getBorder("TableHeader.cellBorder"));

this.setBackground(table.getTableHeader().getBackground());

String[] result = ((String)value).split(",");

for (int i = 0; i < result.length; i++){

label =new JLabel(result[i]);

label.setBackground(table.getTableHeader().getBackground());

label.setHorizontalAlignment(SwingConstants.CENTER);

label.setFont(table.getTableHeader().getFont());

this.add(label);

}

returnthis;

}

}

/*** Custom tablemodel class ***/

privateclass RCBTableModelextends DefaultTableModel{

private Object[] object ={new String(),new String(),new Boolean(null),

new Boolean(null),new String(),new Object(),new Object()};

private String[] columnNames ={"Common Name","Expires","Write to,Clipboard?",

"Write to,File? *","File Path","Display","Remove"};

public Vector data;

publicint getRowCount(){

if (data ==null){

return 0;

}

else{

return data.size();

}

}

publicint getColumnCount(){

if (columnNames ==null)

return 0;

else{

return columnNames.length;

}

}

public String getColumnName(int col){

return columnNames[col];

}

public Class getColumnClass(int col){

return object[col].getClass();

}

public Object getValueAt (int row,int col){

Object[] array = (Object[])(data.elementAt(row));

return array[(col)];

}

publicvoid setValueAt(Object value,int row,int col){

((Vector)data.get(row)).setElementAt(value,(col));

fireTableCellUpdated(row,col);

}

public String[] getColumnNames(){

return this.columnNames;

}

publicint getNumRows(){

return data.size();

}

}

/*** Main Method ***/

publicstaticvoid main (String[] args){

new HeaderRendererTest();

}

}

[13541 byte] By [MidnightJavaa] at [2007-11-27 3:05:04]
# 1

> Here's my SSCCE.

Not a bad example, but not really a SSCCE. A SSCCE is a demo with only the code required to show the problem. Your problem is with a header renderer. You don't need a custom table or a custom TableModel, or buttons on your GUI to demonstrate the problem. Thats why you remove that code to eliminate them as potential causes of the problem.

As it turns out, the height of the table header is determined by the height of the first column it renderers. So if your first column header is a single line all columns header will render as a single line. Here is a SSCCE that demonstrates this:

import java.awt.*;

import javax.swing.*;

import javax.swing.table.*;

public class TableHeaderLines extends JFrame

{

public TableHeaderLines()

{

Object[] columnNames =

{

//"One line, ,",

"One line",

"two,lines",

"another line",

"three,more,lines"

};

DefaultTableModel model = new DefaultTableModel(columnNames, 5);

JTable table = new JTable(model);

table.getTableHeader().setDefaultRenderer( new LineRenderer() );

table.setPreferredScrollableViewportSize(table.getPreferredSize());

JScrollPane scrollPane = new JScrollPane( table );

getContentPane().add( scrollPane );

}

class LineRenderer extends JPanel implements TableCellRenderer

{

public LineRenderer()

{

setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

setOpaque( false );

setBorder(UIManager.getBorder("TableHeader.cellBorder"));

}

public Component getTableCellRendererComponent(

JTable table, Object value, boolean isSelected,

boolean hasFocus, final int row, final int column)

{

removeAll();

String[] result = ((String)value).split(",");

for (int i = 0; i < result.length; i++)

{

JLabel label = new JLabel(result[i]);

label.setFont( table.getTableHeader().getFont() );

label.setAlignmentX(0.5f);

add(label);

}

return this;

}

}

public static void main(String[] args)

{

TableHeaderLines frame = new TableHeaderLines();

frame.setDefaultCloseOperation( EXIT_ON_CLOSE );

frame.pack();

frame.setLocationRelativeTo( null );

frame.setVisible(true);

}

}

a) run the code as is and only a single line displays

b) uncomment the first column name and rerun and two lines are displayed

I don't know a proper fix for this. A workaround would be to pass in the table when you create the renderer. Then you could iterate though all the columns to find the column with the greatest number of lines. Then you could build the panel and add the correct number of labels to the panel. Then your renderering code would simple use the setText() method of each label added to the panel. If there is no text for each label then it would be set to " " (a single space).

camickra at 2007-7-12 3:50:14 > top of Java-index,Desktop,Core GUI APIs...
# 2

Thanks for the explanation and some good ideas on solutions. It certainly explains why I couldn't find the problem by comparing code between my two apps, as it didn't occur to me that the first column's header would determine the size of the others.

I think what you coded in your example will suit my need, since I don't expect the table header formats to change. But the workaround you discussed is a good approach if I need it to work in a dynamic context.

I accept your comments about my SCCE vice SSCCE. I should have given more thought to what was truly essential to the problem. I'll be sure to warrant the extra S in my next example.

-Mark

MidnightJavaa at 2007-7-12 3:50:14 > top of Java-index,Desktop,Core GUI APIs...