The core problem is the way that the JLabel
(which the DefaultTableCellRenderer
is using) is trying to format the HTML, it's allowing the HTML to wrap when it's available width is to short to accommodate the text. This is the default behaviour for JLabel
Why this seems to only happen after the cell is selected is one of those wonderful mysterious of Swing...cause it "should" be happening all the time...
One solution might be to use a layout manager which will prevent (or discourage) the JLabel
from wrapping at the "available" width point...This, however, would require to provide your own TableCellRenderer
, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import sun.swing.DefaultLookup;
public class TestTable extends JPanel {
public TestTable() {
setLayout(new BorderLayout());
Object[][] rows = {
{"<html><font color=red>1 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>2 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=blue>3 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=red>4 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>5 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},};
Object[] columns = {"Column"};
DefaultTableModel model = new DefaultTableModel(rows, columns) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new HTMLRenderer());
table.setRowHeight(table.getFont().getSize() * 2);
add(new JScrollPane(table));
add(new JLabel(String.format("%s, %s, JRE %s (%s)",
System.getProperty("os.name"), System.getProperty("os.arch"),
System.getProperty("java.version"), Locale.getDefault().toString())),
BorderLayout.SOUTH);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static class HTMLRenderer extends JPanel implements TableCellRenderer {
private JLabel label;
private static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER;
public HTMLRenderer() {
label = new DefaultTableCellRenderer();
// setOpaque(false);
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
add(label);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table == null) {
return this;
}
Color fg = null;
Color bg = null;
JTable.DropLocation dropLocation = table.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsertRow()
&& !dropLocation.isInsertColumn()
&& dropLocation.getRow() == row
&& dropLocation.getColumn() == column) {
fg = UIManager.getColor("Table.dropCellForeground");
bg = UIManager.getColor("Table.dropCellBackground");
isSelected = true;
}
if (isSelected) {
super.setForeground(fg == null ? table.getSelectionForeground()
: fg);
super.setBackground(bg == null ? table.getSelectionBackground()
: bg);
} else {
Color background = table.getBackground();
if (background == null || background instanceof javax.swing.plaf.UIResource) {
Color alternateColor = UIManager.getColor("Table.alternateRowColor");
if (alternateColor != null && row % 2 != 0) {
background = alternateColor;
}
}
super.setForeground(table.getForeground());
super.setBackground(background);
}
setFont(table.getFont());
if (hasFocus) {
Border border = null;
if (isSelected) {
border = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
}
if (border == null) {
border = UIManager.getBorder("Table.focusCellHighlightBorder");
}
setBorder(border);
if (!isSelected && table.isCellEditable(row, column)) {
Color col;
col = UIManager.getColor("Table.focusCellForeground");
if (col != null) {
super.setForeground(col);
}
col = UIManager.getColor("Table.focusCellBackground");
if (col != null) {
super.setBackground(col);
}
}
} else {
setBorder(getNoFocusBorder());
}
label.setText(value == null ? "" : value.toString());
return this;
}
protected Border getNoFocusBorder() {
Border border = UIManager.getBorder("Table.cellNoFocusBorder");
if (System.getSecurityManager() != null) {
if (border != null) return border;
return SAFE_NO_FOCUS_BORDER;
} else if (border != null) {
if (noFocusBorder == null || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) {
return border;
}
}
return noFocusBorder;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestTable panel = new TestTable();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
Updated...
I had a nice dig through the JTable
and BasicTableUI
code and the TableCellRenderer
component is been "sized" to the requirements of the individual cell, meaning that when the JLabel
is rendered, it is automatically wrapping the text without consideration, why this then causes issues with the layout may have to do with the fact that the default verticalAlignment
...
Updated with alternative...
Another alternative might be to set the verticalAlignment
to JLabel.TOP
of a DefaultTableCellRenderer
, which is backed by a JLabel
, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TestTable extends JPanel {
public TestTable() {
setLayout(new BorderLayout());
Object[][] rows = {
{"<html><font color=red>1 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>2 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=blue>3 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=red>4 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>5 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},};
Object[] columns = {"Column"};
DefaultTableModel model = new DefaultTableModel(rows, columns) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new HTMLRenderer());
table.setRowHeight(table.getFont().getSize() * 2);
add(new JScrollPane(table));
add(new JLabel(String.format("%s, %s, JRE %s (%s)",
System.getProperty("os.name"), System.getProperty("os.arch"),
System.getProperty("java.version"), Locale.getDefault().toString())),
BorderLayout.SOUTH);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static class HTMLRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setVerticalAlignment(JLabel.TOP);
return comp;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestTable panel = new TestTable();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
But this will come down to your individual needs...
table.setRowHeight(table.getFont().getSize() * 2);
will just permit two lines if there's no space left from the top border. What I'm guessing is that positioning of the baseline of the first row depends on the way the focus enters the cell. If some space is left from the top, only one row is placed, otherwise two rows are squeezed in. - Suggestion: reduce the factor 2 to 1.5:table.setRowHeight(table.getFont().getSize() * 1.5);
– DerbentJLabel
presents different versions of a string. – Lactone<nobr>
which seems to work OK. Thanks for you input! – LactonegetPreferredSize
of what? – LactoneJLabel
. I noticed that JLabel'sview.getPreferredSpan(View.Y_AXIS)
doubles because the numbers ofParagraphView
children is 2 instead of 1. But insideFlowView
andFlowStrategy
it is hard to pinpoint what makes the change. I should just let it go ;) – Lactone